Trójstronne połączenie z werf: wdrożenie na Kubernetes z Helmem „na sterydach”

Stało się to, na co od dawna czekaliśmy (i nie tylko my): werf, nasze narzędzie Open Source do tworzenia aplikacji i dostarczania ich do Kubernetes, obsługuje teraz wprowadzanie zmian za pomocą trójstronnych poprawek scalających! Oprócz tego możliwe jest adaptowanie istniejących zasobów K3 do wydań Helma bez konieczności przebudowywania tych zasobów.

Trójstronne połączenie z werf: wdrożenie na Kubernetes z Helmem „na sterydach”

Jeśli jest bardzo krótki, to kładziemy WERF_THREE_WAY_MERGE=enabled — otrzymujemy wdrożenie „jak w kubectl apply", kompatybilny z istniejącymi instalacjami Helm 2, a nawet trochę więcej.

Zacznijmy jednak od teorii: czym dokładnie są łatki 3-way-merge, jak ludzie wymyślili podejście do ich generowania i dlaczego są one ważne w procesach CI/CD z infrastrukturą opartą na Kubernetes? A potem zobaczmy, czym jest łączenie 3-kierunkowe w werf, jakie tryby są używane domyślnie i jak nim zarządzać.

Co to jest łatka łącząca 3-kierunkowo?

Zacznijmy więc od zadania wdrożenia zasobów opisanych w manifestach YAML do Kubernetesa.

Aby pracować z zasobami, Kubernetes API oferuje następujące podstawowe operacje: tworzenie, łatanie, zastępowanie i usuwanie. Zakłada się, że przy ich pomocy konieczne jest skonstruowanie wygodnego ciągłego dostarczania zasobów do klastra. Jak?

Polecenia imperatywne kubectl

Pierwsze podejście do zarządzania obiektami w Kubernetes polega na użyciu imperatywnych poleceń kubectl do tworzenia, modyfikowania i usuwania tych obiektów. Mówiąc najprościej:

  • według zespołu kubectl run możesz uruchomić Deployment lub Job:
    kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE
  • według zespołu kubectl scale — zmienić liczbę replik:
    kubectl scale --replicas=3 deployment/mysql
  • itd.

Takie podejście może na pierwszy rzut oka wydawać się wygodne. Istnieją jednak problemy:

  1. To trudne zautomatyzować.
  2. jak odzwierciedlają konfigurację w Gicie? Jak przeglądać zmiany zachodzące w klastrze?
  3. Jak zapewnić odtwarzalność konfiguracje po ponownym uruchomieniu?
  4. ...

Oczywiste jest, że takie podejście nie pasuje dobrze do przechowywania aplikacji i infrastruktury w postaci kodu (IaC lub nawet GitOps jako bardziej nowoczesna opcja, zyskująca popularność w ekosystemie Kubernetes). Dlatego te polecenia nie były dalej rozwijane w kubectl.

Tworzenie, pobieranie, zastępowanie i usuwanie operacji

Z podstawowym kreacja to proste: wyślij manifest do operacji create kube api i zasób został utworzony. Reprezentację manifestu YAML można przechowywać w Git i tworzyć za pomocą polecenia kubectl create -f manifest.yaml.

С usuwanie również proste: zamień to samo manifest.yaml z Gita do zespołu kubectl delete -f manifest.yaml.

operacja replace pozwala na całkowite zastąpienie konfiguracji zasobu nową, bez konieczności odtwarzania zasobu. Oznacza to, że przed dokonaniem zmiany w zasobie logiczne jest sprawdzenie bieżącej wersji operacji get, zmień go i zaktualizuj za pomocą operacji replace. kube apiserver jest wbudowany optymistyczne zamknięcie i jeśli po operacji get obiekt się zmienił, a następnie operacja replace nie przejdzie.

Aby zapisać konfigurację w Git i zaktualizować ją za pomocą funkcji zamiany, musisz wykonać operację get, połącz konfigurację z Git z tym, co otrzymaliśmy i wykonaj replace. Domyślnie kubectl pozwala tylko na użycie polecenia kubectl replace -f manifest.yamlGdzie manifest.yaml — już w pełni przygotowany (w naszym przypadku scalony) manifest, który należy zainstalować. Okazuje się, że użytkownik musi zaimplementować manifesty scalania, a to nie jest trywialna sprawa...

Warto również zauważyć, że chociaż manifest.yaml i jest przechowywany w Git, nie możemy z góry wiedzieć, czy konieczne jest utworzenie obiektu lub jego aktualizacja - musi to zrobić oprogramowanie użytkownika.

Razem: czy możemy zbudować ciągłe wdrożenie używając tylko tworzenia, zastępowania i usuwania, upewniając się, że konfiguracja infrastruktury jest przechowywana w Git wraz z kodem i wygodnym CI/CD?

W zasadzie możemy... W tym celu będziesz musiał zaimplementować operację scalania manifesty i pewnego rodzaju wiążące, które:

  • sprawdza obecność obiektu w klastrze,
  • wykonuje wstępne utworzenie zasobu,
  • aktualizuje je lub usuwa.

Podczas aktualizacji należy o tym pamiętać zasób mógł się zmienić od ostatniego get i automatycznie obsługuj przypadek blokowania optymistycznego - podejmuj wielokrotne próby aktualizacji.

Jednak po co wymyślać koło na nowo, skoro kube-apiserver oferuje inny sposób aktualizacji zasobów: operację patch, co uwalnia użytkownika od części opisanych problemów?

Łata

Teraz przechodzimy do patchy.

Poprawki to podstawowy sposób stosowania zmian w istniejących obiektach w Kubernetesie. Operacja patch to działa tak:

  • użytkownik kube-apiserver musi wysłać łatkę w formacie JSON i podać obiekt,
  • a apiserver sam zajmie się aktualnym stanem obiektu i doprowadzi go do wymaganej formy.

W tym przypadku nie jest wymagane optymistyczne ryglowanie. Ta operacja jest bardziej deklaratywna niż zamiana, choć na pierwszy rzut oka może się wydawać, że jest odwrotnie.

Tak więc:

  • za pomocą operacji create tworzymy obiekt zgodnie z manifestem z Gita,
  • przez delete — usunąć, jeśli obiekt nie jest już potrzebny,
  • przez patch — zmieniamy obiekt, doprowadzając go do postaci opisanej w Git.

Jednak aby to zrobić, musisz stworzyć poprawna łatka!

Jak działają poprawki w Helmie 2: łączenie dwukierunkowe

Podczas pierwszej instalacji wersji Helm wykonuje operację create dla zasobów wykresów.

Podczas aktualizowania wersji Helm dla każdego zasobu:

  • uwzględnia łatkę pomiędzy wersją zasobu z poprzedniego wykresu a aktualną wersją wykresu,
  • stosuje tę poprawkę.

Nazwiemy tę łatkę Dwukierunkowa łatka łącząca, ponieważ w jego powstanie zaangażowane są 2 manifesty:

  • manifest zasobów z poprzedniej wersji,
  • manifest zasobu z bieżącego zasobu.

Podczas usuwania operacji delete w kube apiserver jest wywoływany dla zasobów, które zostały zadeklarowane w poprzedniej wersji, ale nie zostały zadeklarowane w bieżącej.

Dwukierunkowe podejście do łączenia łatek ma problem: prowadzi do niezsynchronizowany z rzeczywistym stanem zasobu w klastrze i manifestem w Git.

Ilustracja problemu na przykładzie

  • W Git wykres przechowuje manifest, w którym pole image Wdrożenie ma znaczenie ubuntu:18.04.
  • Użytkownik przez kubectl edit zmienił wartość tego pola na ubuntu:19.04.
  • Podczas ponownego wdrażania wykresu Helm nie generuje łatki, bo pole image w poprzedniej wersji wydania i na obecnym wykresie są takie same.
  • Po ponownym wdrożeniu image szczątki ubuntu:19.04, chociaż wykres tak mówi ubuntu:18.04.

Doszło do desynchronizacji i utraty deklaratywności.

Co to jest zsynchronizowany zasób?

Ogólnie rzecz biorąc pełny Niemożliwe jest uzyskanie dopasowania pomiędzy manifestem zasobu w działającym klastrze a manifestem z Git. Ponieważ w prawdziwym manifeście mogą znajdować się adnotacje/etykiety usług, dodatkowe kontenery i inne dane, które niektórzy kontrolerzy dodają i usuwają dynamicznie z zasobu. Nie możemy i nie chcemy przechowywać tych danych w Git. Chcemy jednak, aby pola, które wyraźnie określiliśmy w Git, przyjęły odpowiednie wartości po wdrożeniu.

Okazuje się, że jest to takie ogólne zsynchronizowana reguła zasobów: podczas wdrażania zasobu możesz zmienić lub usunąć tylko te pola, które są wyraźnie określone w manifeście z Git (lub zostały określone w poprzedniej wersji, a teraz zostały usunięte).

Dwukierunkowa łatka łącząca

Główną ideą Dwukierunkowa łatka łącząca: generujemy łatkę pomiędzy ostatnio zastosowaną wersją manifestu z Git a docelową wersją manifestu z Git, biorąc pod uwagę aktualną wersję manifestu z działającego klastra. Powstała łatka musi być zgodna z regułą zsynchronizowanych zasobów:

  • nowe pola dodane do wersji docelowej dodawane są za pomocą łatki;
  • pola istniejące wcześniej w ostatniej zastosowanej wersji i nieistniejące w wersji docelowej są resetowane za pomocą łatki;
  • pola w aktualnej wersji obiektu różniące się od docelowej wersji manifestu są aktualizowane za pomocą łatki.

Na tej zasadzie generuje łatki kubectl apply:

  • ostatnia zastosowana wersja manifestu jest przechowywana w adnotacji samego obiektu,
  • target - pobrany z określonego pliku YAML,
  • bieżący pochodzi z działającego klastra.

Skoro już omówiliśmy teorię, czas opowiedzieć, co zrobiliśmy w Werf.

Stosowanie zmian w pliku werf

Wcześniej werf, podobnie jak Helm 2, używał łatek łączenia dwukierunkowego.

Poprawka naprawy

Aby przejść na nowy typ łatek - 3-way-merge - w pierwszym kroku wprowadziliśmy tzw. poprawki naprawcze.

Podczas wdrażania używana jest standardowa łatka 2-way-merge, ale werf dodatkowo generuje łatkę, która synchronizuje rzeczywisty stan zasobu z tym, co jest zapisane w Git (taka łatka jest tworzona przy użyciu tej samej reguły zsynchronizowanego zasobu opisanej powyżej) .

Jeśli nastąpi desynchronizacja, na końcu wdrożenia użytkownik otrzyma OSTRZEŻENIE z odpowiednim komunikatem i łatką, którą należy zastosować, aby przywrócić zasób do zsynchronizowanej postaci. Łatka ta jest również zapisana w specjalnej adnotacji werf.io/repair-patch. Zakłada się, że ręce użytkownika sam zastosuje tę poprawkę: werf w ogóle jej nie zastosuje.

Generowanie łatek naprawczych to rozwiązanie tymczasowe, które pozwala faktycznie przetestować tworzenie łatek w oparciu o zasadę łączenia 3-kierunkowego, ale nie stosuje ich automatycznie. W tej chwili ten tryb pracy jest domyślnie włączony.

Łatka 3-kierunkowego łączenia tylko dla nowych wydań

Od 1 grudnia 2019 r. rozpoczynają się wersje beta i alfa werf domyślnie użyj pełnoprawnych łatek łączących trójstronnie, aby zastosować zmiany tylko do nowych wydań Helma wydawanych przez werf. Istniejące wydania będą nadal korzystać z podejścia polegającego na łączeniu dwukierunkowym i poprawkach naprawczych.

Ten tryb pracy można włączyć bezpośrednio poprzez ustawienie WERF_THREE_WAY_MERGE_MODE=onlyNewReleases teraz.

Operacja: funkcja pojawiła się w werfie w kilku wydaniach: w kanale alfa była już gotowa wraz z wersją v1.0.5-alfa.19, a w kanale beta - z v1.0.4-beta.20.

Łatka 3-kierunkowego łączenia dla wszystkich wydań

Począwszy od 15 grudnia 2019 r. wersje beta i alfa werf zaczynają domyślnie używać pełnych poprawek łączenia 3-kierunkowego, aby zastosować zmiany do wszystkich wydań.

Ten tryb pracy można włączyć bezpośrednio poprzez ustawienie WERF_THREE_WAY_MERGE_MODE=enabled teraz.

Co zrobić z autoskalowaniem zasobów?

W Kubernetesie istnieją 2 typy autoskalowania: HPA (poziomo) i VPA (pionowo).

Poziomo automatycznie wybiera liczbę replik, pionowo - liczbę zasobów. Zarówno liczba replik, jak i wymagania dotyczące zasobów są określone w manifeście zasobów (patrz Manifest zasobów). spec.replicas lub spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory и inni).

Problem: jeśli użytkownik skonfiguruje zasób na wykresie tak, aby określał określone wartości dla zasobów lub replik i dla tego zasobu włączone są autoskalery, to przy każdym wdrożeniu werf zresetuje te wartości do tego, co jest zapisane w manifeście wykresu .

Istnieją dwa rozwiązania problemu. Na początek najlepiej unikać jawnego określania wartości autoskalowanych w manifeście wykresu. Jeśli z jakiegoś powodu ta opcja nie jest odpowiednia (na przykład dlatego, że wygodnie jest ustawić początkowe limity zasobów i liczbę replik na wykresie), wówczas werf oferuje następujące adnotacje:

  • werf.io/set-replicas-only-on-creation=true
  • werf.io/set-resources-only-on-creation=true

Jeśli taka adnotacja jest obecna, werf nie zresetuje odpowiednich wartości przy każdym wdrożeniu, ale ustawi je dopiero podczas początkowego tworzenia zasobu.

Więcej szczegółów można znaleźć w dokumentacji projektu dot HPA и VPA.

Zabroń używania łatki łączenia 3-kierunkowego

Użytkownik może obecnie zabronić używania nowych poprawek w werf za pomocą zmiennej środowiskowej WERF_THREE_WAY_MERGE_MODE=disabled. Jednak zaczynając Od 1 marca 2020 r. zakaz ten nie będzie już obowiązywał. i możliwe będzie jedynie użycie poprawek łączących 3-kierunkowo.

Przyjęcie zasobów w werf

Opanowanie sposobu stosowania zmian za pomocą poprawek typu 3-way-merge pozwoliło nam na natychmiastowe wdrożenie takiej funkcji, jak przejęcie zasobów istniejących w klastrze do wydania Helma.

Helm 2 ma problem: nie można dodać zasobu do manifestów wykresów, który już istnieje w klastrze, bez ponownego utworzenia tego zasobu od zera (patrz. #6031, #3275). Nauczyliśmy Werf akceptować istniejące zasoby do wydania. W tym celu należy zainstalować adnotację na aktualnej wersji zasobu z działającego klastra (np. za pomocą kubectl edit):

"werf.io/allow-adoption-by-release": RELEASE_NAME

Teraz należy opisać zasób na wykresie i następnym razem, gdy werf wdroży wydanie o odpowiedniej nazwie, istniejący zasób zostanie zaakceptowany w tym wydaniu i pozostanie pod jego kontrolą. Co więcej, w procesie akceptacji zasobu do wydania, werf doprowadzi bieżący stan zasobu z działającego klastra do stanu opisanego na wykresie, korzystając z tych samych poprawek łączenia 3-kierunkowego i reguły zsynchronizowanych zasobów.

Operacja: ustawienie WERF_THREE_WAY_MERGE_MODE nie wpływa na adopcję zasobów - w przypadku adopcji zawsze używana jest łatka łączenia 3-kierunkowego.

Szczegóły – w dokumentacja.

Wnioski i plany na przyszłość

Mam nadzieję, że po tym artykule stało się jaśniejsze, czym są łatki 3-way-merge i dlaczego do nich trafiły. Z praktycznego punktu widzenia rozwoju projektu werf ich wdrożenie było kolejnym krokiem w kierunku usprawnienia wdrożenia na wzór Helma. Teraz możesz zapomnieć o problemach z synchronizacją konfiguracji, które często pojawiały się podczas korzystania z Helma 2. Jednocześnie do wydania Helma dodano nową przydatną funkcję polegającą na przejmowaniu już pobranych zasobów Kubernetesa.

Nadal istnieją pewne problemy i wyzwania związane z wdrożeniami podobnymi do Helm, takimi jak użycie szablonów Go, którymi będziemy w dalszym ciągu się zajmować.

Informacje na temat metod aktualizacji zasobów i ich przyjęcia można również znaleźć pod adresem tę stronę dokumentacji.

Hełm 3

Godny szczególnej uwagi wydany niedawno pojawiła się nowa, główna wersja Helma – v3 – która również wykorzystuje łatki 3-way-merge i pozbywa się Tillera. Wymagana jest nowa wersja Helma migracja istniejących instalacji w celu przekonwertowania ich na nowy format przechowywania.

Ze swojej strony Werf pozbył się obecnie Tillera, przeszedł na łączenie 3-kierunkowe i dodał wiele więcej, pozostając jednocześnie kompatybilnym z istniejącymi instalacjami Helm 2 (nie trzeba wykonywać żadnych skryptów migracyjnych). Dlatego też, dopóki werf nie przełączy się na Helm 3, użytkownicy werf nie stracą głównych zalet Helma 3 w porównaniu z Helmem 2 (werf też je ma).

Jednakże przejście werf na bazę kodu Helm 3 jest nieuniknione i nastąpi w najbliższej przyszłości. Prawdopodobnie będzie to werf 1.1 lub werf 1.2 (w tej chwili główna wersja werf to 1.0; więcej informacji na temat urządzenia do wersjonowania werf znajdziesz w artykule tutaj). W tym czasie Helm 3 będzie miał czas na ustabilizowanie się.

PS

Przeczytaj także na naszym blogu:

Źródło: www.habr.com

Dodaj komentarz