Dynamiczna budowa i wdrożenie obrazów Docker z werf na przykładzie strony z wersjonowaną dokumentacją

Już wielokrotnie opowiadaliśmy o naszym narzędziu GitOps werf, a tym razem chcielibyśmy podzielić się doświadczeniem tworzenia strony internetowej z dokumentacją samego projektu — werf.io (jego polska wersja — pl.werf.io). To zwykła strona statyczna, jednak jej budowa jest interesująca, ponieważ opiera się na dynamicznej liczbie artefaktów.

Dynamiczna budowa i wdrożenie obrazów Docker z werf na przykładzie strony z wersjonowaną dokumentacją

Nie będziemy wnikać w szczegóły struktury strony: generowanie ogólnego menu dla wszystkich wersji, stron z informacjami o wydaniach itp. — skupimy się zamiast tego na pytaniach i cechach dynamicznej budowy oraz nieco na towarzyszących procesach CI/CD.

Wprowadzenie: jak działa strona

Zacznijmy od tego, że dokumentacja dotycząca werf jest przechowywana razem z jego kodem. Stawia to określone wymagania dla rozwoju, które w zasadzie wykraczają poza ramy niniejszego artykułu, ale przynajmniej można stwierdzić, że:

  • Nowe funkcje werf nie powinny pojawiać się bez aktualizacji dokumentacji i odwrotnie, jakiekolwiek zmiany w dokumentacji oznaczają wydanie nowej wersji werf;
  • Projekt jest dość intensywnie rozwijany: nowe wersje mogą być wydawane kilka razy dziennie;
  • Jakiekolwiek ręczne operacje związane z publikowaniem strony z nową wersją dokumentacji są co najmniej nużące;
  • W projekcie przyjęto podejście semantycznego wersjonowania, z pięcioma kanałami stabilności. Proces wydania zakłada sekwencyjne przejście wersji przez kanały w porządku rosnącej stabilności: od alpha do rock-solid;
  • Strona ma polską wersję, która „żyje i rozwija się” (tj. jej zawartość jest aktualizowana) równolegle z wersją podstawową (tj. anglojęzyczną).

Aby ukryć przed użytkownikiem tę całą „wewnętrzną kuchnię”, oferując mu to, co „po prostu działa”, stworzyliśmy oddzielne narzędzie do instalacji i aktualizacji werf — to multiwerf. Wystarczy podać numer wydania i kanał stabilności, który jesteś gotów używać, a multiwerf sprawdzi, czy jest nowa wersja na kanale i pobierze ją w razie potrzeby.

W menu wyboru wersji na stronie dostępne są ostatnie wersje werf w każdym kanale. Domyślnie, pod adresem werf.io/documentation otwiera się wersja najbardziej stabilnego kanału dla ostatniego wydania — jest ona również indeksowana przez wyszukiwarki. Dokumentacja dla kanału jest dostępna pod oddzielnymi adresami (na przykład, werf.io/v1.0-beta/documentation dla beta-wydania 1.0).

Podsumowując, witryna ma dostępne następujące wersje:

  1. główna (otwierana domyślnie),
  2. dla każdego aktywnego kanału aktualizacji każdej wersji (np. werf.io/v1.0-beta).

Aby wygenerować konkretną wersję witryny, w zasadzie wystarczy wykonać jej kompilację przy użyciu Jekyll, uruchamiając w katalogu /docs repozytorium werf odpowiednią komendę (jekyll build), wcześniej przełączając się na znacznik Git wymaganej wersji.

Warto dodać, że:

  • do budowy używana jest sama narzędzie (werf);
  • procesy CI/CD są oparte na GitLab CI;
  • i to wszystko, rzecz jasna, działa w Kubernetes.

Zadania

Teraz sformułujemy zadania, uwzględniając całą opisaną specyfikę:

  1. Po zmianie wersji werf na dowolnym kanale aktualizacji dokumentacja na stronie powinna być automatycznie aktualizowana.
  2. Dla rozwoju należy mieć możliwość czasami przeglądania wersji próbnych witryny.

Rekompilację witryny należy przeprowadzać po zmianie wersji na dowolnym kanale z odpowiednich znaczników Git, ale w trakcie budowy obrazu uzyskamy następujące cechy:

  • Ponieważ lista wersji na kanałach się zmienia, należy ponownie budować tylko dokumentację dla kanałów, w których zmieniła się wersja. W końcu ponowne budowanie wszystkiego od nowa nie jest zbyt eleganckie.
  • Sam zestaw kanałów dla wydań może się zmieniać. W pewnym momencie, na przykład, może nie być wersji na kanałach stabilniejszych niż wydanie early-access 1.1, ale z czasem się pojawią — czy w tym przypadku naprawdę trzeba ręcznie zmieniać budowę?

Wynika z tego, że budowa zależy od zmieniających się danych zewnętrznych.

Realizacja

Wybór podejścia

Jako opcja, można uruchamiać każdą potrzebną wersję jako osobny pod w Kubernetes. Taka opcja zakłada większą liczbę obiektów w klastrze, która będzie rosła w miarę wzrastania liczby stabilnych wydań werf. A to z kolei wiąże się z bardziej skomplikowanym utrzymaniem: na każdą wersję powstaje własny serwer HTTP, przy czym z niewielkim obciążeniem. Oczywiście wiąże się to również z większymi kosztami zasobów.

My poszliśmy drogą kompilacji wszystkich potrzebnych wersji w jednym obrazie. Skompilowana statyka wszystkich wersji witryny znajduje się w kontenerze z NGINX, a ruch do odpowiedniego Deploymentu przychodzi przez NGINX Ingress. Prosta struktura — aplikacja stateless — pozwala łatwo skalować Deployment (w zależności od obciążenia) przy użyciu samego Kubernetes.

Aby być dokładnym, zbieramy dwa obrazy: jeden dla środowiska produkcyjnego, drugi jako dodatkowy, dla środowiska deweloperskiego. Dodatkowy obraz jest używany (uruchamiany) tylko w środowisku deweloperskim razem z głównym i zawiera wersję strony z komita przeglądu, a routowanie między nimi odbywa się przy użyciu zasobów Ingress.

werf vs git clone i artefakty

Jak już wspomniano, aby wygenerować statykę strony dla konkretnej wersji dokumentacji, należy wykonać zbiórkę, przełączając się na odpowiedni tag repozytorium. Można by to robić poprzez klonowanie repozytorium za każdym razem przy zbiórce, wybierając odpowiednie tagi z listy. Jednak jest to dość zasobożerna operacja, a ponadto wymaga napisania nietrywialnych instrukcji… Innym poważnym minusem jest to, że przy takim podejściu nie ma możliwości cokolwiek buforować podczas zbiórki.

Tu na pomoc przychodzi sama narzędzie werf, które realizuje inteligentne buforowanie i pozwala na użycie zewnętrznych repozytoriów. Użycie werf do dodania kodu z repozytorium znacznie przyspieszy zbiórkę, ponieważ werf zasadniczo klonuje repozytorium tylko raz, a następnie wykonuje tylko fetch w razie potrzeby. Ponadto, przy dodawaniu danych z repozytorium możemy wybrać tylko potrzebne katalogi (w naszym przypadku jest to katalog docs), co znacznie zmniejsza ilość dodawanych danych.

Ponieważ Jekyll to narzędzie przeznaczone do kompilacji statyki i nie jest potrzebne w końcowym obrazie, logiczne byłoby przeprowadzenie kompilacji w artefakcie werf, a do końcowego obrazu zaimportować tylko wynik kompilacji.

Pisząc werf.yaml

Zatem ustaliliśmy, że będziemy kompilować każdą wersję w osobnym artefakcie werf. Jednak nie wiemy, ile tych artefaktów będzie przy zbiorze, dlatego nie możemy napisać stałej konfiguracji zbiórki (ścisłe mówiąc, możemy, ale nie będzie to do końca efektywne).

werf pozwala na użycie szablonów Go w swoim pliku konfiguracyjnym (werf.yaml), a to daje możliwość generowania configu „na bieżąco” w zależności od zewnętrznych danych (to jest to, czego potrzebujemy!). Zewnętrznymi danymi w naszym przypadku jest informacja o wersjach i wydaniach, na podstawie której zbieramy potrzebną ilość artefaktów i otrzymujemy w rezultacie dwa obrazy: werf-doc i werf-dev do uruchomienia na różnych konturach.

Dane zewnętrzne są przekazywane przez zmienne środowiskowe. Oto ich skład:

  • WYDAWNI — ciąg z listą wydań i odpowiadającą im aktualną wersją werf, w postaci listy przez spacje wartości w formacie %. Przykład: 1.0%v1.0.4-beta.20
  • KANALY — ciąg z listą kanałów i odpowiadającą im aktualną wersją werf, w postaci listy przez spacje wartości w formacie %. Przykład: 1.0-beta%v1.0.4-beta.20 1.0-alpha%v1.0.5-alpha.22
  • WERSJA_ROOT — wersja wydania werf do wyświetlenia domyślnie na stronie (nie zawsze trzeba wyświetlać dokumentację dla najwyższego numeru wydania). Przykład: v1.0.4-beta.20
  • REVIEW_SHA — hash commit'a review, z którego należy zbudować wersję dla testowego konturu.

Te zmienne będą wypełniane w pipeline GitLab CI, a jak dokładnie — opisano poniżej.

Najpierw, dla wygody, zdefiniujemy w werf.yaml zmiennych szablonów Go, przypisując im wartości z zmiennych środowiskowych:

{{ $_ := set . "WerfVersions" (cat (env "KANALY") (env "WYDAWNI") | splitList " ") }}
{{ $Root := . }}
{{ $_ := set . "WerfRootVersion" (env "WERSJA_ROOT") }}
{{ $_ := set . "WerfReviewCommit" (env "REVIEW_SHA") }}

Opis artefaktu do kompilacji statyki wersji strony jest w zasadzie taki sam dla wszystkich wymaganych nam przypadków (w tym generacja wersji root, jak i wersji dla konturu dev). Dlatego wydzielimy go do osobnego bloku za pomocą funkcji define — do późniejszego ponownego użycia przy pomocy include. Szablonowi przekażemy następujące argumenty:

  • Wersja — generowaną wersję (nazwę tagu);
  • Channel — nazwę kanału aktualizacji, dla którego generowany jest artefakt;
  • Commit — hash commit'a, jeśli artefakt jest generowany dla commit'a review;
  • kontekst.

Opis szablonu artefaktu

{{- define "doc_artifact" -}}
{{- $Root := index . "Root" -}}
artifact: doc-{{ .Channel }}
from: jekyll/builder:3
mount:
- from: build_dir
  to: /usr/local/bundle
ansible:
  install:
  - shell: |
      export PATH=/usr/jekyll/bin/:$PATH
  - name: "Zainstaluj zależności"
    shell: bundle install
    args:
      executable: /bin/bash
      chdir: /app/docs
  beforeSetup:
{{- if .Commit }}
  - shell: echo "Przeglądaj SHA - {{ .Commit }}."
{{- end }}
{{- if eq .Channel "root" }}
  - name: "releases.yml HASH: {{ $Root.Files.Get "releases.yml" | sha256sum }}"
    copy:
      content: |
{{ $Root.Files.Get "releases.yml" | indent 8 }}
      dest:  /app/docs/_data/releases.yml
{{- else }}
  - file:
      path: /app/docs/_data/releases.yml
      state: touch
{{- end }}
  - file:
      path: "{{`{{ item }}`}}"
      state: directory
      mode: 0777
    with_items:
    - /app/main_site/
    - /app/ru_site/
  - file:
      dest: /app/docs/pages_ru/cli
      state: link
      src: /app/docs/pages/cli
  - shell: |
      echo -e "werfVersion: {{ .Version }}nwerfChannel: {{ .Channel }}" > /tmp/_config_additional.yml
      export PATH=/usr/jekyll/bin/:$PATH
{{- if and (ne .Version "review") (ne .Channel "root") }}
{{- $_ := set . "BaseURL" ( printf "v%s" .Channel ) }}
{{- else if ne .Channel "root" }}
{{- $_ := set . "BaseURL" .Channel }}
{{- end }}
      jekyll build -s /app/docs  -d /app/_main_site/{{ if .BaseURL }} --baseurl /{{ .BaseURL }}{{ end }} --config /app/docs/_config.yml,/tmp/_config_additional.yml
      jekyll build -s /app/docs  -d /app/_ru_site/{{ if .BaseURL }} --baseurl /{{ .BaseURL }}{{ end }} --config /app/docs/_config.yml,/app/docs/_config_ru.yml,/tmp/_config_additional.yml
    args:
      executable: /bin/bash
      chdir: /app/docs
git:
- url: https://github.com/flant/werf.git
  to: /app/
  owner: jekyll
  group: jekyll
{{- if .Commit }}
  commit: {{ .Commit }}
{{- else }}
  tag: {{ .Version }}
{{- end }}
  stageDependencies:
    install: ['docs/Gemfile','docs/Gemfile.lock']
    beforeSetup: '**/*'
  includePaths: 'docs'
  excludePaths: '**/*.sh'
{{- end }}

Nazwa artefaktu musi być unikalna. Możemy to osiągnąć, na przykład, poprzez dodanie nazwy kanału (wartość zmiennej .Channel) jako sufiksu nazwy artefaktu: artifact: doc-{{ .Channel }}. Należy jednak pamiętać, że przy imporcie z artefaktów należy odwoływać się do takich samych nazw.

Opisując artefakt, korzysta się z możliwości, jakie daje werf, takich jak montowanie. Montowanie z określeniem katalogu roboczego build_dir pozwala na zachowanie pamięci podręcznej Jekylla między uruchomieniami pipeline, co znacząco przyspiesza ponowne budowanie.

Zauważyłeś również użycie pliku releases.yml — to plik YAML z danymi o wersjach, pobierany z github.com (artefakt uzyskany podczas wykonywania pipeline). Jest potrzebny podczas kompilacji strony, ale w kontekście artykułu interesuje nas tym, że od jego stanu zależy ponowne budowanie tylko jednego artefaktu — artefaktu witryny wersji podstawowej (w innych artefaktach nie jest potrzebny).

Jest to realizowane za pomocą warunkowego operatora if szablonów Go oraz konstrukcji {{ $Root.Files.Get "releases.yml" | sha256sum }} na etapie stadiów. Działa to w następujący sposób: podczas budowania artefaktu dla wersji głównej (zmienna .Channel wynosi root) hasz pliku releases.yml wpływa na sygnaturę całego etapu, ponieważ stanowi część nazwy zadania Ansible (parametr name). W ten sposób, przy zmianie zawartości pliku releases.yml odpowiedni artefakt zostanie przebudowany.

Zwróć również uwagę na pracę z zewnętrznym repozytorium. W obraz artefaktu z repozytorium werf, dodawany jest tylko katalog /docs, przy czym w zależności od przekazanych parametrów dodawane są dane od razu potrzebnego tagu lub przeglądowej komity.

Aby użyć szablonu artefaktu do generowania opisu artefaktów przekazanych wersji kanałów i wydań, organizujemy pętlę po zmiennej .WerfVersions do werf.yaml:

{{ range .WerfVersions -}}
{{ $VersionsDict := splitn "%" 2 . -}}
{{ dict "Version" $VersionsDict._1 "Channel" $VersionsDict._0 "Root" $Root | include "doc_artifact" }}
---
{{ end -}}

Ponieważ pętla wygeneruje kilka artefaktów (mam nadzieję, że tak będzie), należy uwzględnić separator między nimi — sekwencję --- (więcej o składni pliku konfiguracyjnego w dokumentacji). Jak wcześniej ustalono, podczas wywoływania szablonu w pętli przekazujemy parametry wersji, URL i główny kontekst.

Analogicznie, ale już bez pętli, wywołujemy szablon artefaktu dla „specjalnych przypadków”: dla wersji głównej oraz wersji z przeglądowych komitów:

{{ dict "Version" .WerfRootVersion "Channel" "root" "Root" $Root  | include "doc_artifact" }}
---
{{- if .WerfReviewCommit }}
{{ dict "Version" "review" "Channel" "review" "Commit" .WerfReviewCommit "Root" $Root  | include "doc_artifact" }}
{{- end }}

Zwróć uwagę, że artefakt dla przeglądowego komitu zostanie zbudowany tylko wtedy, gdy zmienna .WerfReviewCommit.

Artefakty są gotowe — czas zająć się importem!

Finalny obraz, przeznaczony do uruchomienia w Kubernetes, stanowi standardowy NGINX, w którym dodano plik konfiguracyjny serwera nginx.conf i statykę z artefaktów. Oprócz artefaktu głównej wersji witryny musimy powtórzyć pętlę po zmiennej .WerfVersions w celu importu artefaktów wersji kanałów i wydań + przestrzegania zasady nazewnictwa artefaktów, którą wcześniej przyjęliśmy. Ponieważ każdy artefakt przechowuje wersje witryny dla dwóch języków, importujemy je do miejsc przewidzianych w konfiguracji.

Opis finalnego obrazu werf-doc

image: werf-doc
from: nginx:stable-alpine
ansible:
  setup:
  - name: "Setup /etc/nginx/nginx.conf"
    copy:
      content: |
{{ .Files.Get ".werf/nginx.conf" | indent 8 }}
      dest: /etc/nginx/nginx.conf
  - file:
      path: "{{`{{ item }}`}}"
      state: directory
      mode: 0777
    with_items:
    - /app/main_site/assets
    - /app/ru_site/assets
import:
- artifact: doc-root
  add: /app/_main_site
  to: /app/main_site
  before: setup
- artifact: doc-root
  add: /app/_ru_site
  to: /app/ru_site
  before: setup
{{ range .WerfVersions -}}
{{ $VersionsDict := splitn "%" 2 . -}}
{{ $Channel := $VersionsDict._0 -}}
{{ $Version := $VersionsDict._1 -}}
- artifact: doc-{{ $Channel }}
  add: /app/_main_site
  to: /app/main_site/v{{ $Channel }}
  before: setup
{{ end -}}
{{ range .WerfVersions -}}
{{ $VersionsDict := splitn "%" 2 . -}}
{{ $Channel := $VersionsDict._0 -}}
{{ $Version := $VersionsDict._1 -}}
- artifact: doc-{{ $Channel }}
  add: /app/_ru_site
  to: /app/ru_site/v{{ $Channel }}
  before: setup
{{ end -}}

Dodatkowy obraz, który uruchamia się razem z głównym w konturze dev, zawiera tylko dwie wersje strony: wersję z review-commitu oraz wersję podstawową strony (tam znajdują się wspólne zasoby oraz, jeśli pamiętasz, dane o wydaniach). Tak więc, dodatkowy obraz różni się od głównego jedynie sekcją importu (no i, oczywiście, nazwą):

image: werf-dev
...
import:
- artifact: doc-root
  add: /app/_main_site
  to: /app/main_site
  before: setup
- artifact: doc-root
  add: /app/_ru_site
  to: /app/ru_site
  before: setup
{{- if .WerfReviewCommit  }}
- artifact: doc-review
  add: /app/_main_site
  to: /app/main_site/review
  before: setup
- artifact: doc-review
  add: /app/_ru_site
  to: /app/ru_site/review
  before: setup
{{- end }}

Jak już wspomniano wcześniej, artefakt dla review-commitu będzie generowany tylko podczas aktywacji ustawionej zmiennej środowiskowej REVIEW_SHA. Można by w ogóle nie generować obrazu werf-dev, jeśli nie ma zmiennej środowiskowej REVIEW_SHA, ale aby czyszczenie według polityk obrazów Docker w werf działało dla obrazu werf-dev, pozostawimy go tylko do budowy z artefaktem wersji podstawowej (w końcu już został zbudowany), aby uprościć strukturę pipeline.

Budowa zakończona! Przechodzimy do CI/CD i ważnych niuansów.

Pipeline w GitLab CI oraz szczegóły dynamicznej budowy

Podczas uruchamiania budowy musimy ustawić zmienne środowiskowe używane w werf.yaml. Nie dotyczy to zmiennej REVIEW_SHA, którą ustawimy przy wywoływaniu pipeline z webhooka GitHub.

Generowanie niezbędnych danych zewnętrznych przeniesiemy do skryptu Bash generate_artifacts, który będzie generować dwa artefakty pipeline GitLab:

  • pliku releases.yml z danymi o wydaniach,
  • pliku common_envs.sh, zawierający zmienne środowiskowe do eksportu.

Zawartość pliku generate_artifacts znajdziesz w naszym repozytorium z przykładami. Samo pozyskiwanie danych nie jest tematem artykułu, ale plik common_envs.sh jest dla nas ważny, ponieważ zależy od niego działanie werf. Przykład jego zawartości:

export RELEASES='1.0%v1.0.6-4'
export CHANNELS='1.0-alpha%v1.0.7-1 1.0-beta%v1.0.7-1 1.0-ea%v1.0.6-4 1.0-stable%v1.0.6-4 1.0-rock-solid%v1.0.6-4'
export ROOT_VERSION='v1.0.6-4'

Można używać wyników tego skryptu, na przykład za pomocą funkcji Bash source.

A teraz najciekawsze. Aby zarówno budowa, jak i wdrożenie aplikacji działały poprawnie, należy upewnić się, że werf.yaml została są takie same przynajmniej w ramach jednego pipeline'u. Jeśli to założenie nie zostanie spełnione, to sygnatury etapów obliczane przez werf podczas budowy i, na przykład, wdrożenia, będą różne. Doprowadzi to do błędu wdrożenia, ponieważ wymagany obraz dla wdrożenia nie będzie dostępny.

Innymi słowy, jeśli podczas budowy obrazu strony informacje o wydaniach i wersjach będą jedne, a w momencie wdrożenia wydana zostanie nowa wersja i zmienne środowiskowe będą miały inne wartości, to wdrożenie zakończy się błędem: ponieważ artefakt nowej wersji jeszcze nie został zbudowany.

Jeśli generacja werf.yaml zależy od zewnętrznych danych (na przykład listy aktualnych wersji, jak w naszym przypadku), to skład i wartości takich danych powinny być ustalone w ramach pipeline'u. To szczególnie ważne, jeśli zewnętrzne parametry zmieniają się dość często.

Będziemy uzyskiwać i ustalać zewnętrzne dane na pierwszym etapie pipeline'u w GitLab (Prebuild) i przekazywać je dalej w postaci artefaktu GitLab CI. To pozwoli na uruchamianie i ponowne uruchamianie zadań pipeline'u (budowa, wdrożenie, czyszczenie) z taką samą konfiguracją w werf.yaml.

Zawartość etapu Prebuild pliku .gitlab-ci.yml:

Prebuild:
  stage: prebuild
  script:
    - bash .\/generate_artifacts 1> common_envs.sh
    - cat .\/common_envs.sh
  artifacts:
    paths:
      - releases.yml
      - common_envs.sh
    expire_in: 2 weeks

Po zarejestrowaniu zewnętrznych danych w artefakcie, można przeprowadzić budowę i wdrożenie, korzystając z standardowych etapów pipeline'u GitLab CI: Build i Deploy. Sam pipeline uruchamiamy za pomocą webhooków z repozytorium GitHub werf (tj. przy zmianach w repozytorium na GitHub). Dane do nich można znaleźć w właściwościach projektu GitLab w sekcji CI / CD Settings -> Pipeline triggers, a następnie utworzymy w GitHub odpowiedni Webhook (Settings -> Webhooks).

Etap budowy będzie wyglądać następująco:

Build:
  stage: build
  script:
    - type multiwerf && . $(multiwerf use 1.0 alpha --as-file)
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - source common_envs.sh
    - werf build-and-publish --stages-storage :local
  except:
    refs:
      - schedules
  dependencies:
    - Prebuild

GitLab doda do etapu budowy dwa artefakty z etapu Prebuild, więc eksportujemy zmienne z przygotowanymi danymi wejściowymi za pomocą konstrukcji source common_envs.shRozpoczynamy etap budowy w każdym przypadku, z wyjątkiem uruchamiania pipeline'u zgodnie z harmonogramem. Zgodnie z harmonogramem uruchomi się pipeline do czyszczenia — w tym przypadku nie jest konieczna budowa.

Na etapie wdrożenia opiszemy dwa zadania — osobno dla wdrożenia na konta produkcyjne i deweloperskie, z użyciem szablonu YAML:

.base_deploy: &base_deploy
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.0 alpha --as-file)
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - source common_envs.sh
    - werf deploy --stages-storage :local
  dependencies:
    - Prebuild
  except:
    refs:
      - schedules

Deploy to Production:
  <<: *base_deploy
  variables:
    WERF_KUBE_CONTEXT: prod
  environment:
    name: production
    url: werf.io
  only:
    refs:
      - master
  except:
    variables:
      - $REVIEW_SHA
    refs:
      - schedules

Deploy to Test:
  <<: *base_deploy
  variables:
    WERF_KUBE_CONTEXT: dev
  environment:
    name: test
    url: werf.test.flant.com
  except:
    refs:
      - schedules
  only:
    variables:
      - $REVIEW_SHA

Zadania różnią się zasadniczo tylko wskazaniem kontekstu klastra, w którym werf ma wykonać wdrożenie (WERF_KUBE_CONTEXT), oraz ustawieniem zmiennych środowiskowych konta (environment.name i environment.url), które są następnie wykorzystywane w szablonach Helm-charta. Nie będziemy przytaczać treści szablonów, ponieważ nie ma tam nic interesującego dla omawianego tematu, ale można je znaleźć w repozytorium artykułu.

Ostateczny szlif

Ponieważ wersje werf są wydawane dość często, nowe obrazy będą się często budować, a Docker Registry będzie stale rosnąć. Dlatego konieczne jest skonfigurowanie automatycznego czyszczenia obrazów zgodnie z politykami. Zrobienie tego jest bardzo proste.

Do realizacji będzie potrzebne:

  • Dodać etap czyszczenia w .gitlab-ci.yml;
  • Dodać okresowe wykonywanie zadania czyszczenia;
  • Skonfigurować zmienną środowiskową z tokenem dostępu do zapisu.

Dodajemy etap czyszczenia w .gitlab-ci.yml:

Cleanup:
  stage: cleanup
  script:
    - type multiwerf && . $(multiwerf use 1.0 alpha --as-file)
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - source common_envs.sh
    - docker login -u nobody -p ${WERF_IMAGES_CLEANUP_PASSWORD} ${WERF_IMAGES_REPO}
    - werf cleanup --stages-storage :local
  only:
    refs:
      - schedules

Prawie wszystko to już widzieliśmy wyżej — tylko dla czyszczenia należy najpierw zalogować się do Docker Registry z tokenem, który ma prawa do usuwania obrazów w Docker Registry (automatycznie wydawany token zadania GitLab CI nie ma takich praw). Token należy wcześniej założyć w GitLab i podać jego wartość w zmiennej środowiskowej WERF_IMAGES_CLEANUP_PASSWORD projektu (Ustawienia CI/CD -> Zmienne).

Dodanie zadania czyszczenia z wymaganym harmonogramem odbywa się w CI/CD ->
Harmonogramy
.

Teraz: projekt w Docker Registry nie będzie już stale rósł z powodu nieużywanych obrazów.

Na zakończenie części praktycznej przypominam, że pełne listy z artykułu są dostępne w Gitem:

Wynik

  1. Otrzymaliśmy logiczną strukturę budowy: jeden artefakt na jedną wersję.
  2. Budowa jest uniwersalna i nie wymaga ręcznych zmian przy wydaniu nowych wersji werf: dokumentacja na stronie jest automatycznie aktualizowana.
  3. Budowane są dwa obrazy dla różnych konturów.
  4. Działa szybko, ponieważ maksymalnie wykorzystuje pamięć podręczną — przy wydaniu nowej wersji werf lub wywołaniu webhooka GitHub dla commitów review — przeprowadzana jest ponowna budowa tylko odpowiedniego artefaktu z zmienioną wersją.
  5. Nie trzeba się martwić o usuwanie nieużywanych obrazów: czyszczenie zgodnie z politykami werf utrzyma porządek w Docker Registry.

Wnioski

  • Korzystanie z werf umożliwia szybką budowę dzięki pamięci podręcznej zarówno samej budowy, jak i pamięci podręcznej przy pracy z zewnętrznymi repozytoriami.
  • Praca z zewnętrznymi repozytoriami Git eliminuje konieczność klonowania repozytorium za każdym razem całkowicie lub wymyślania koła na nowo z wymyślną logiką optymalizacji. werf wykorzystuje pamięć podręczną i klonuje tylko raz, a następnie używa fetch i tylko w razie potrzeby.
  • Możliwość wykorzystania szablonów Go w pliku konfiguracyjnym budowy werf.yaml pozwala opisać budowę, której wynik zależy od danych zewnętrznych.
  • Korzystanie z montowania w werf znacznie przyspiesza budowę artefaktów — dzięki pamięci podręcznej, która jest wspólna dla wszystkich pipeline.
  • werf umożliwia łatwe skonfigurowanie czyszczenia, co jest szczególnie istotne przy dynamicznej budowie.

P.S.

Przeczytaj także na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster