Dla Kubernetes istnieje kilka opcji aktualizacji zasobów: apply, edit, patch i replace. Istnieje pewne zamieszanie co do tego, co każdy z nich robi i kiedy je stosować. Przyjrzyjmy się temu bliżej.

Jeśli frazy "kubernetes apply vs replace", która znajduje , która jest błędna. "kubernetes apply vs patch" pierwszym linkiem jest dokumentacja do kubectl patch, która nie zawiera porównania apply i patch. W tym artykule zostaną omówione różne opcje oraz prawidłowe użycie każdej z nich.
W trakcie cyklu życia zasobu Kubernetes (usługi, deployment, ingress itp.) czasami trzeba zmienić, dodać lub usunąć niektóre właściwości tego zasobu. Na przykład, dodać notatkę, zwiększyć lub zmniejszyć liczbę replik.
Kubernetes CLI
Jeśli już pracujesz z klastrami Kubernetes przez CLI, to znasz już apply i edit. Komenda apply odczytuje specyfikację zasobu z pliku i wykonuje "upsert" w klastrze Kubernetes, tzn. tworzy zasób, jeśli go nie ma, i aktualizuje go, jeśli istnieje. Komenda edit odczytuje zasób przez API, po czym zapisuje specyfikację zasobu w lokalnym pliku, który następnie otwierany jest w edytorze tekstu. Po edytowaniu i zapisaniu pliku, kubectl wyśle wprowadzone zmiany z powrotem przez API, które starannie zastosuje te zmiany do zasobu.
Nie wszyscy znają polecenia patch i replace. Komenda patch pozwala na zmianę części specyfikacji zasobu, dostarczając tylko zmienioną część w wierszu poleceń. Komenda replace działa tak samo, jak i edit, ale wszystko trzeba zrobić ręcznie: należy pobrać aktualną wersję specyfikacji zasobu, na przykład przy użyciu kubectl get -o yaml, edytować ją, a następnie użyć replace do zaktualizowania zasobu według zmienionej specyfikacji. Komenda replace nie zadziała, jeśli między odczytem a zastąpieniem zasobu zaszły jakiekolwiek zmiany.
Kubernetes API
Prawdopodobnie znasz metody CoreV1().Pods().Update(), replaceNamespacedService lub patch_namespaced_deployment, jeśli pracujesz z klastrami przez używając jakiegoś języka programowania. Biblioteka obsługuje te metody za pomocą żądań protokołu HTTP, używając metod Żądanie PUT: i PATCH. Przy tym update i replace używają Żądanie PUT:, a patch, chociaż może to brzmieć banalnie, używa PATCH.
Warto zauważyć, że kubectl również działa z klastrami przez API. Innymi słowy, kubectl– to wrapper nad biblioteką kliencką dla języka Go, która w dużej mierze umożliwia przedstawienie subkomend w bardziej zwięzły i czytelny sposób jako dodatek do standardowych możliwości API. Na przykład, jak już mogliście zauważyć, metoda apply nie została wspomniana w powyższym akapicie. Obecnie (maj 2020, przyp. tłumacza) cała logika kubectl apply, tzn. tworzenie nieistniejących zasobów i aktualizacja istniejących, działa w pełni po stronie kodu kubectl. Podejmowane są starania apply na stronę API, ale to wciąż jest w fazie beta testów. Szczegóły opiszę poniżej.
Domyślny patch
Najlepiej stosować patch, jeśli chcesz zaktualizować zasób. Tak działają zarówno biblioteki klienckie nad API Kubernetes, jak i kubectl (co nie powinno dziwić, gdyż jest to wrapper biblioteki klienckiej, przyp. tłumacza).
Działać strategicznie
Wszystkie polecenia kubectl apply, edit i patch używają metody PATCH w zapytaniach HTTP do aktualizacji istniejącego zasobu. Jeśli dokładniej przyjrzymy się realizacji poleceń, to we wszystkich używa się podejścia do aktualizacji zasobów, chociaż polecenie patch może wykorzystywać również inne podejścia (więcej na ten temat poniżej). Podejście strategic-merge patching stara się "zrobić wszystko dobrze" podczas łączenia dostarczonej specyfikacji z istniejącą specyfikacją. Bardziej konkretnie, stara się łączyć zarówno obiekty, jak i tablice, co oznacza, że zmiany są zazwyczaj addytywne. Na przykład, uruchomienie polecenia patch z nową zmienną środowiskową w specyfikacji kontenera pod, powoduje, że ta zmienna środowiskowa jest dodawana do istniejących zmiennych środowiskowych, a nie nadpisuje ich. Aby usunąć z użyciem tego podejścia, należy wymusić ustawienie wartości parametru na null w dostarczonej specyfikacji. Które z poleceń kubectl do aktualizacji są najlepsze?
Jeśli tworzysz i zarządzasz swoimi zasobami za pomocą kubectl apply, zawsze najlepiej jest używać kubectl apply, aby kubectl mógł zarządzać konfiguracją i prawidłowo śledzić żądane zmiany z zastosowania do zastosowania. Zaletą zawsze używania apply jest to, że śledzi wcześniej zastosowaną specyfikację, pozwalając mu wiedzieć, kiedy właściwości specyfikacji i elementy tablicy są jawnie usuwane. To umożliwia użycie apply Aby usunąć właściwości i elementy tablicy, podczas gdy zwykłe strategizowane scalanie nie będzie działać. Komendy edit i patch nie aktualizują notatek, które kubectl apply stosuje do śledzenia swoich zmian, dlatego wszelkie zmiany, które są śledzone i dokonywane przez API Kubernetes, ale wprowadzane za pomocą komend edit i patch, są niewidoczne dla kolejnych komend apply, czyli apply nie usuwa ich, nawet jeśli nie są wymienione w specyfikacji wejściowej dla apply (w dokumentacji podano, że edit i patch wprowadzają aktualizacje notatek, które są używane apply, ale w praktyce - nie).
Jeśli nie używasz komendy apply, można użyć jako edit, jak i patch, wybierając tę komendę, która najlepiej pasuje do wprowadzanej zmiany. Przy dodawaniu i zmianie właściwości specyfikacji oba podejścia są mniej więcej takie same. Przy usuwaniu właściwości specyfikacji lub elementów tablicy edit zachowuje się jak jednorazowe uruchomienie apply, w tym śledzi, jak wyglądała specyfikacja przed i po jej edytowaniu, dlatego można jawnie usuwać właściwości i elementy tablicy z zasobu. Należy jawnie ustawić wartość właściwości na null w specyfikacji dla patch, aby usunąć go z zasobu. Usuwanie elementu tablicy za pomocą strategizowanego scalania jest bardziej skomplikowane, ponieważ wymaga użycia dyrektyw scalania. Zobacz inne podejścia do aktualizacji poniżej, aby wybrać bardziej odpowiednie alternatywy.
Aby zaimplementować w bibliotece klienta metody aktualizacji, które zachowują się podobnie do powyższych komend kubectl, należy w zapytaniach ustawić content-type do application/strategic-merge-patch+json. Jeśli chcesz usunąć właściwości w specyfikacji, musisz jawnie ustawić ich wartości na null podobnie jak kubectl patch. Jeśli chcesz usunąć elementy tablicy, powinieneś dołączyć dyrektywy scalania do specyfikacji aktualizacji lub użyć innego podejścia do aktualizacji.
Inne podejścia do aktualizacji
W Kubernetes obsługiwane są dwa inne podejścia do aktualizacji: i . Podejście JSON merge patch przyjmuje częściową specyfikację Kubernetes jako dane wejściowe i wspiera scalanie obiektów podobnie jak podejście strategic-merge patching. Różnica między nimi polega na tym, że obsługuje tylko zamianę tablic, w tym tablicę kontenerów w specyfikacji pod. Oznacza to, że korzystając z JSON merge patch, musisz dostarczyć pełne specyfikacje dla wszystkich kontenerów w przypadku zmiany jakiejkolwiek właściwości dowolnego kontenera. W związku z tym podejście to jest użyteczne do usuwania elementów z tablicy w specyfikacji. W wierszu poleceń możesz wybrać JSON merge patch, używając kubectl patch --type=merge. Przy pracy z API Kubernetes należy używać metody żądania PATCH i ustawić content-type do application/merge-patch+json.
Podejście JSON patch zamiast dostarczania częściowej specyfikacji zasobu wykorzystuje przedstawienie zmian, które chcesz wprowadzić w zasobie, w postaci tablicy, w której każdy element tablicy reprezentuje opis zmiany wprowadzanej do zasobu. To podejście jest bardziej elastyczne i potężne w wyrażaniu wprowadzanych zmian, ale kosztem tego, że lista wprowadzanych zmian jest w osobnym, niewłaściwym formacie dla Kubernetes, zamiast wysyłać częściową specyfikację zasobu. W kubectl możesz wybrać JSON patch, używając kubectl patch --type=json. Przy korzystaniu z API Kubernetes to podejście działa z wykorzystaniem metody żądania PATCH i ustawić content-type do application/json-patch+json.
Potrzebna pewność — używamy replace
W niektórych przypadkach potrzebna jest pewność, że zasób nie zostanie zmieniony pomiędzy czasem odczytu a aktualizacją. Innymi słowy, należy upewnić się, że wszystkie zmiany będą atomowe. W takim przypadku warto użyć replace. Na przykład, jeśli istnieje ConfigMap z licznikami aktualizowanymi przez kilka źródeł, należy być pewnym, że dwa źródła nie będą aktualizować licznika jednocześnie, co doprowadziłoby do utraty aktualizacji. Dla ilustracji wyobraź sobie sekwencję zdarzeń używając podejścia patch:
- A i B pobierają bieżący stan zasobu z API.
- Każdy z nich lokalnie aktualizuje specyfikację, zwiększając licznik o jeden, a także dodaje "A" lub "B" odpowiednio w notatce "updated-by".
- A nieco szybciej aktualizuje zasób.
- B aktualizuje zasób.
W wyniku aktualizacji A utracono. Ostatnia operacja patch zyskuje, licznik zwiększa się o jeden zamiast dwóch, a wartość adnotacji "updated-by" kończy się na "B" i nie zawiera "A". Porównajmy powyższe z tym, co się dzieje, gdy aktualizacje są wykonywane za pomocą podejścia replace:
- A i B pobierają bieżący stan zasobu z API.
- Każdy z nich lokalnie aktualizuje specyfikację, zwiększając licznik o jeden, a także dodaje "A" lub "B" odpowiednio w notatce "updated-by".
- A nieco szybciej aktualizuje zasób.
- B próbuje zaktualizować zasób, ale aktualizacja jest odrzucana przez API, ponieważ wersja zasobu w specyfikacji
replacenie zgadza się z aktualną wersją zasobu w Kubernetes, ponieważ wersja zasobu została zwiększona podczas wykonywania operacji replace przez A.
W powyższym przypadku B będzie musiał ponownie pobrać zasób, wprowadzić zmiany w nowym stanie i spróbować ponownie zrobić replace. W rezultacie licznik zostanie zwiększony o dwa, a adnotacja "updated-by" będzie zawierała "AB" na końcu.
Powyższy przykład sugeruje, że podczas wykonywania replace następuje całkowita wymiana całego zasobu. Specyfikacja używana do replace, powinna być nie częściowa, czy też jako w apply, ale pełna, włączając dodanie resourceVersion do metadanych specyfikacji. Jeśli nie uwzględnisz resourceVersion lub dostarczona przez Ciebie wersja nie jest aktualna, wymiana zostanie odrzucona. Zatem najlepszym podejściem jest replace – odczytać zasób, zaktualizować go i natychmiast zastąpić. Używając kubectl, może to wyglądać następująco:
$ kubectl get deployment my-deployment -o json
| jq '.spec.template.spec.containers[0].env[1].value = "new value"'
| kubectl replace -f -Warto zauważyć, że następujące dwa polecenia, wykonane kolejno, zakończą się sukcesem, ponieważ deployment.yaml nie zawiera właściwości .metadata.resourceVersion
$ kubectl create -f deployment.yaml
$ kubectl replace -f deployment.yamlMożna by pomyśleć, że to jest sprzeczne z tym, co powiedziano wcześniej, tzn. "dodanie resourceVersion do metadanych specyfikacji". Czy to twierdzenie jest błędne? Nie, ponieważ jeśli kubectl zauważa, że nie podałeś resourceVersion, odczyta ją z zasobu i doda do specyfikacji, którą podałeś, a dopiero potem wykona replace. Ponieważ jest to potencjalnie niebezpieczne, polegając na atomowości, ta magia działa całkowicie po stronie kubectl, nie należy się na niej polegać przy używaniu bibliotek klienckich współpracujących z API. W takim przypadku będziesz musiał odczytać aktualną specyfikację zasobu, zaktualizować ją, a następnie wykonać Żądanie PUT: żądanie.
Nie można zastosować patch – trzeba zrobić replace
Czasami należy wprowadzić pewne zmiany, których nie można obsłużyć za pomocą API. W takich przypadkach można wymusić zastąpienie zasobu, usuwając go i ponownie tworząc. Robi się to za pomocą kubectl replace --force. Uruchomienie tej komendy natychmiast usuwa zasoby, a następnie je odtwarza na podstawie podanej specyfikacji. API nie ma obsługi "wymusić zastąpienie", a aby to zrobić przez API, należy wykonać dwie operacje. Najpierw trzeba usunąć zasób, ustawiając dla niego gracePeriodSeconds na zero (0) i propagationPolicy na “Background”, a następnie ponownie stworzyć ten zasób z pożądaną specyfikacją.
Uwaga: podejście to jest potencjalnie niebezpieczne, może prowadzić do nieokreślonego stanu
Apply po stronie serwera
Jak wspomniano wcześniej, programiści Kubernetes pracują nad wdrożeniem logiki apply z kubectl w API Kubernetes. Logika apply jest dostępna w Kubernetes 1.18 poprzez kubectl apply --server-side lub przez API, używając metody PATCH z content-type application/apply-patch+YAML.
Uwaga: JSON jest również poprawnym YAML, więc można wysłać specyfikację w formacie JSON, nawet jeśli
content-typebędzieapplication/apply-patch+yaml.
Oprócz tego, że logika kubectl staje się dostępna dla wszystkich przez API, apply po stronie serwera śledzi odpowiedzialnych za pola w specyfikacji, tym samym umożliwiając bezpieczny wspólny dostęp do jej edytowania bez konfliktów. Innymi słowy, jeśli apply po stronie serwera uzyska szersze zastosowanie, pojawi się uniwersalny bezpieczny interfejs zarządzania zasobami dla różnych klientów, takich jak kubectl, Pulumi lub Terraform, GitOps oraz skrypty własne korzystające z bibliotek klientów.
Podsumowanie
Mam nadzieję, że ten krótki przegląd różnych sposobów aktualizacji zasobów w klastrach był dla Ciebie pomocny. Warto wiedzieć, że przeciwnik nie ogranicza się jedynie do apply w przeciwieństwie do replace, ponieważ można zaktualizować zasób za pomocą apply, edit, patch lub replace. W zasadzie każdy z tych podejść ma swoje zastosowanie. Dla atomowych zmian preferowane jest replace, w przeciwnym razie warto użyć strategic-merge patch przez apply. W ostateczności liczę, że zrozumiałeś, że nie można ufać Google ani StackOverflow w poszukiwaniu "kubernetes apply vs replace". Przynajmniej dopóki ten artykuł nie zastąpi obecnej odpowiedzi.

Źródło: habr.com
