Tagowanie oparte na zawartości w zbieraczu werf: po co i jak to działa?

Tagowanie oparte na zawartości w zbieraczu werf: po co i jak to działa?

werf — nasze narzędzie CLI GitOps z otwartym kodem źródłowym do budowania i dostarczania aplikacji w Kubernetes. W wydaniu v1.1 wprowadzono nową funkcję w budowniczym obrazów: tagowanie obrazów na podstawie zawartości lub tagowania opartego na zawartości. Dotychczas typowy schemat tagowania w werf zakładał tagowanie obrazów Docker na podstawie tagu Git, gałęzi Git lub commit'u Git. Jednak wszystkie te schematy mają wady, które są w pełni rozwiązywane przez nową strategię tagowania. Szczegóły na jej temat i co sprawia, że jest tak dobra — znajdują się poniżej.

Wydanie zestawu mikrousług z jednego repozytorium Git

Często zdarza się sytuacja, w której aplikacja jest podzielona na wiele bardziej lub mniej niezależnych usług. Wydania tych usług mogą odbywać się niezależnie: jednocześnie może być wydawana jedna lub kilka usług, podczas gdy pozostałe muszą działać bez żadnych zmian. Ale z punktu widzenia przechowywania kodu i zarządzania projektem wygodniej jest trzymać takie usługi aplikacji w jednym repozytorium.

Są sytuacje, gdy usługi są rzeczywiście niezależne i nie są związane z jedną aplikacją. W takim przypadku będą one zlokalizowane w oddzielnych projektach, a ich wydanie będzie realizowane za pomocą oddzielnych procesów CI/CD w każdym z projektów.

Jednak w rzeczywistości deweloperzy często dzielą jedno aplikację na kilka mikrousług, ale zakładanie oddzielnego repozytorium i projektu dla każdej z nich... — to oczywiste nadmiarowe działanie. To właśnie o tej sytuacji będzie mowa dalej: kilka takich mikrousług znajduje się w jednym repozytorium projektu, a wydania odbywają się za pomocą jednego procesu w CI/CD.

Tagowanie według gałęzi Git i tagu Git

Załóżmy, że używana jest najczęściej spotykana strategia tagowania — tag-or-branch. Dla gałęzi Git obrazy są tagowane nazwą gałęzi, dla jednej gałęzi w danym momencie istnieje tylko jeden opublikowany obraz o nazwie tej gałęzi. Dla tagów Git obrazy są tagowane zgodnie z nazwą tagu.

Przy tworzeniu nowego tagu Git — na przykład przy wydaniu nowej wersji — dla wszystkich obrazów projektu w Docker Registry zostanie utworzony nowy tag Docker:

  • myregistry.org/myproject/frontend:v1.1.10
  • myregistry.org/myproject/myservice1:v1.1.10
  • myregistry.org/myproject/myservice2:v1.1.10
  • myregistry.org/myproject/myservice3:v1.1.10
  • myregistry.org/myproject/myservice4:v1.1.10
  • myregistry.org/myproject/myservice5:v1.1.10
  • myregistry.org/myproject/database:v1.1.10

Te nowe nazwy obrazów trafiają przez szablony Helm do konfiguracji Kubernetes. Przy uruchomieniu wdrożenia poleceniem werf deploy następuje aktualizacja pola image w manifestach zasobów Kubernetes i ponowne uruchomienie odpowiednich zasobów w związku ze zmienioną nazwą obrazu.

Problem: w przypadku, gdy rzeczywiście zawartość obrazu nie zmieniła się od poprzedniego wdrożenia (tagu Git), a jedynie jego tag Docker, następuje niepotrzebne ponowne uruchomienie tej aplikacji i w związku z tym możliwy jest pewien przestój. Chociaż nie było żadnych rzeczywistych powodów do przeprowadzania tego ponownego uruchomienia.

W konsekwencji, przy obecnej schemacie tagowania musimy tworzyć kilka oddzielnych repozytoriów Git, co rodzi problem organizacji wdrożenia tych kilku repozytoriów. Ogólnie rzecz biorąc, taka schemat wydaje się być przeciążony i skomplikowany. Lepiej jest łączyć wiele usług w jedno repozytorium i tworzyć takie tagi Docker, aby uniknąć niepotrzebnych ponownych uruchomień.

Tagowanie przez commit Git

W werf również istnieje strategia tagowania związana z commitami Git.

Commit Git jest identyfikatorem zawartości repozytorium Git i zależy od historii edycji plików w repozytorium Git, dlatego wydaje się logiczne używać go do tagowania obrazów w Docker Registry.

Jednak tagowanie według commitów Git ma te same wady, co tagowanie według gałęzi Git lub tagów Git:

  • Mógł zostać utworzony pusty commit, który nie zmienia plików, a tag Docker obrazu zostanie zmieniony.
  • Mógł zostać utworzony commit merge, który nie zmienia plików, a tag Docker obrazu zostanie zmieniony.
  • Mógł zostać utworzony commit, który zmienia te pliki w Git, które nie są importowane do obrazu, a tag Docker obrazu znów zostanie zmieniony.

Tagowanie według nazwy gałęzi Git nie odzwierciedla wersji obrazu

Jest jeszcze jeden problem związany ze strategią tagowania według gałęzi Git.

Tagowanie według nazwy gałęzi działa, dopóki commity tej gałęzi są zbierane sukcesywnie w porządku chronologicznym.

Jeśli w obecnej schemacie użytkownik uruchomi ponowną kompilację starego commita powiązanego z daną gałęzią, to werf nadpisze obraz według odpowiedniego tagu Docker nowo skompilowaną wersją obrazu dla starego commita. Używające tego tagu wdrożenia od tego momentu narażają się na ryzyko, podczas ponownego uruchamiania podów, pobrania innej wersji obrazu, w wyniku czego nasza aplikacja straci połączenie z systemem CI, a synchronizacja ulegnie zniekształceniu.

Ponadto, podczas kolejnych pushów do jednej gałęzi z niewielkim odstępem czasu między nimi, starszy commit może zostać zbudowany później niż nowszy: starsza wersja obrazu nadpisze nową według tagu gałęzi Git. Takie problemy może rozwiązać system CI/CD (na przykład w GitLab CI dla serii commitów uruchamiany jest pipeline ostatniego). Nie wszystkie systemy to wspierają, dlatego potrzebny jest bardziej niezawodny sposób zapobiegania tej fundamentalnej problematyce.

Co to jest oznaczanie oparte na treści?

Zatem czym jest oznaczanie oparte na treści — tagowanie obrazów według zawartości.

Do tworzenia tagów Docker używane są nie prymitywy Gita (gałąź Git, tag Git…), lecz suma kontrolna związana z:

  • zawartością obrazu. Identyfikator-tagu obrazu odzwierciedla jego zawartość. Podczas budowy nowej wersji ten identyfikator nie zmieni się, jeśli w obrazie nie zmieniły się pliki;
  • historią tworzenia tego obrazu w Gicie. Obrazy związane z różnymi gałęziami Gita i różną historią budowy poprzez werf będą miały różne identyfikatory-tagów.

Jako taki identyfikator-tagu występuje tzw. sygnatura etapów obrazu.

Każdy obraz składa się z zestawu etapów: od, before-install, git-archive, install, imports-after-install, before-setup,… git-latest-patch i.t.d. Każdy etap ma identyfikator, który odzwierciedla jego zawartość, — sygnatura etapu (stage signature).

Finalny obraz, złożony z tych etapów, jest tagowany tzw. sygnaturą zestawu tych etapów — sygnatura etapów, — która jest uogólniająca dla wszystkich etapów obrazu.

Każdy obraz z konfiguracji werf.yaml w ogólnym przypadku będzie miał swoją sygnaturę i odpowiednio, tag Docker.

Sygnatura etapów rozwiązuje wszystkie wymienione problemy:

  • Jest odporna na puste commity Git.
  • Jest odporna na commity Git, które zmieniają pliki, które nie są istotne dla obrazu.
  • Nie prowadzi do problemu z nadpisywaniem aktualnej wersji obrazu podczas ponownego uruchamiania budów dla starszych commitów gałęzi.

Teraz to jest zalecana strategia tagowania i jest używana domyślnie w werf dla wszystkich systemów CI.

Jak włączyć i używać w werf

Odpowiednia opcja pojawiła się w poleceniu werf publish: --tag-by-stages-signature=true|false

W systemie CI strategię tagowania definiuje się poleceniem werf ci-env. Wcześniej dla niej definiowano parametr werf ci-env --tagging-strategy=tag-or-branch. Teraz, jeśli wskaźnik werf ci-env --tagging-strategy=stages-signature lub nie określać tej opcji, werf domyślnie użyje strategii tagowania stages-signature. Komenda werf ci-env automatycznie ustawi potrzebne flagi dla polecenia werf build-and-publish (lub werf publish), dlatego nie musisz podawać dodatkowych opcji dla tych poleceń.

Na przykład polecenie:

werf publish --stages-storage :local --images-repo registry.hello.com/web/core/system --tag-by-stages-signature

… może utworzyć następujące obrazy:

  • registry.hello.com/web/core/system/backend:4ef339f84ca22247f01fb335bb19f46c4434014d8daa3d5d6f0e386d
  • registry.hello.com/web/core/system/frontend:f44206457e0a4c8a54655543f749799d10a9fe945896dab1c16996c6

Tutaj 4ef339f84ca22247f01fb335bb19f46c4434014d8daa3d5d6f0e386d — to jest sygnatura etapów obrazu backend, a f44206457e0a4c8a54655543f749799d10a9fe945896dab1c16996c6 — sygnatura etapów obrazu frontend.

Przy użyciu specjalnych funkcji werf_container_image i werf_container_env w szablonach Helm nie trzeba nic zmieniać: te funkcje będą automatycznie generować prawidłowe nazwy obrazów.

Przykład konfiguracji w systemie CI:

type multiwerf && source <(multiwerf use 1.1 beta)
type werf && source <(werf ci-env gitlab)
werf build-and-publish|deploy

Więcej informacji na temat konfiguracji dostępne jest w dokumentacji:

Podsumowując

  • Nowa opcja werf publish --tag-by-stages-signature=true|false.
  • Nowa wartość opcji werf ci-env --tagging-strategy=stages-signature|tag-or-branch (jeśli nie podano, to domyślnie będzie stages-signature).
  • Jeśli wcześniej używano opcji tagowania według commitów Git (WERF_TAG_GIT_COMMIT lub opcja werf publish --tag-git-commit COMMIT), konieczne jest przełączenie na strategię tagowania stages-signature.
  • Nowe projekty najlepiej od razu przełączać na nową schemę tagowania.
  • Stare projekty przy migracji na werf 1.1 należy przełączać na nową schemę tagowania, jednak stara tag-or-branch nadal jest wspierana.

Tagowanie oparte na treści rozwiązuje wszystkie opisane w artykule problemy:

  • Odporność nazwy tagu Docker na puste commity Git.
  • Odporność nazwy tagu Docker na commity Git, które zmieniają irrelewantne dla obrazu pliki.
  • Nie prowadzi do problemu z nadpisywaniem aktualnej wersji obrazu podczas ponownego uruchamiania budów dla starych commitów Git dla gałęzi Git.

Korzystaj! I nie zapomnij zaglądać do nas na GitHub, aby stworzyć issue lub znaleźć już istniejące, postawić plus, stworzyć PR lub po prostu obserwować rozwój projektu.

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