Има няколко варианта за обновяване на ресурсите в Kubernetes: apply, edit, patch и replace. Има объркване относно това какво прави всеки от тях и кога да се използват. Нека да разгледаме.

Ако фразата "kubernetes apply vs replace", ще намерите , който не е верен. "kubernetes apply vs patch" първият резултат – документация за kubectl patch, която не включва сравнение apply и patch. В тази статия ще разгледаме различните опции, а също и правилното им използване.
По време на жизнения цикъл на ресурс в Kubernetes (сервиз, deployment, ingress и т.н.) понякога е нужно да промените, добавите или премахнете определени свойства на ресурса. Например, да добавите бележка, да увеличите или намалите броя на репликите.
Kubernetes CLI
Ако вече работите с Kubernetes клъстери през CLI, то вече сте запознати с apply и edit. Командата apply чете спецификацията на ресурса от файл и извършва "upsert" в Kubernetes клъстера, т.е. създава ресурса, ако го няма, и го обновява, ако съществува. Командата edit чете ресурса чрез API, след което записва спецификацията на ресурса в локален файл, който след това се отваря в текстов редактор. След като редактирате и запазите файла, kubectl ще изпрати направените промени обратно чрез API, който внимателно ще приложи тези промени към ресурса.
Не всеки знае, че командата patch и replace. Командата patch позволява да промените част от спецификацията на ресурса, предоставяйки само променената част в командния ред. Командата replace работи по същия начин като edit, но всичко трябва да се направи ръчно: необходимо е да се изтегли текущата версия на спецификацията на ресурса, например, използвайки kubectl get -o yaml, да я редактирате, след това да използвате replace за обновяване на ресурса по променената спецификация. Командата replace няма да проработи, ако между четенето и заменянето на ресурса е имало някакви промени.
Kubernetes API
Вероятно сте запознати с методите CoreV1().Pods().Update(), replaceNamespacedService или patch_namespaced_deployment, ако работите с клъстери чрез използвайки някакъв език за програмиране. Библиотеката обработва тези методи с HTTP заявки, използвайки методите PUT и PATCH. В същото време актуализиране и replace използват PUT, а patch, колкото и банално да звучи, използва PATCH.
Струва си да се отбележи, че kubectl също работи с клъстери чрез API. С други думи, kubectl– е обвивка над клиентската библиотека за езика Go, осигуряваща в значителна степен възможност за предоставяне на подкоманди в по-компактен и четим вид на допълнение към вградените възможности на API. Например, както вече сте могли да забележите, методът apply не беше споменат по-горе в предишния абзац. В момента (май 2020, прим. преводача) цялата логика kubectl apply, т.е. създаване на несъществуващи ресурси и актуализиране на съществуващи, работи напълно на страната на кода kubectl. Предприемат се усилия apply на страната на API, но това все още е в бета тестове. Повече ще опиша по-долу.
Patch по подразбиране
Най-добре е да се прилага patch, ако желаете да актуализирате ресурс. Така работят както клиентските библиотеки над API Kubernetes, така и kubectl (не е изненадващо, тъй като той – е обвивка на клиентската библиотека, прим. преводача).
Да работим стратегически
Всички команди kubectl apply, edit и patch използват метода PATCH в HTTP заявки за актуализиране на съществуващ ресурс. Ако се задълбочите в реализацията на командите, ще видите, че във всички се използва подходът за актуализиране на ресурсите, въпреки че командата patch може да използва и други подходи (повече за това по-долу). Подходът strategic-merge patching се опитва да "направи всичко правилно" при обединяването на предоставената спецификация с съществуващата спецификация. По-конкретно, той се опитва да обедини както обекти, така и масиви, което означава, че промените, като правило, са адитивни. Например, изпълнението на команда patch с нова променлива среда в спецификацията на контейнера pod, води до добавянето на тази променлива среда към съществуващите променливи среда, а не до тяхното презаписване. За да изтриете с помощта на този подход, трябва да зададете параметъра на null в предоставената спецификация. Кои от командите kubectl за актуализиране е най-добре да се използват?
Ако създавате и управлявате ресурсите си с помощта на kubectl apply, при актуализиране е най-добре винаги да се използва kubectl applyза да може kubectl може да управлява конфигурацията и правилно да проследява исканите промени от прилагане на прилагане. Предимството на използването на apply е, че той проследява преди това приложената спецификация, позволявайки му да знае, когато свойствата на спецификацията и елементите на масива явно се изтриват. Това позволява да се използва apply за премахване на свойства и елементи от масив, докато обикновеното стратегическо сливане няма да работи. Командите edit и patch не актуализират бележките, които kubectl apply използва за проследяване на своите изменения, така че всякакви промени, които се проследяват и правят чрез API на Kubernetes, но направени чрез команди edit и patch, са невидими за следващите команди apply, тоест apply не ги изтрива, дори ако не се появяват в входната спецификация за apply (В документацията е посочено, че edit и patch правят актуализации на бележките, използвани apply, но на практика – не).
Ако не използвате команда apply, можете да използвате като edit, така и patch, избирате командата, която най-добре отговаря на направеното изменение. При добавяне и изменение на свойства в спецификацията, и двата подхода са приблизително еднакви. При изтриване на свойства от спецификацията или елементи от масив edit се държи като еднократно действие apply, включително проследява каква е била спецификацията преди и след редактиране, така че може ясно да изтриете свойства и елементи от масив от ресурса. Трябва ясно да зададете стойността на свойството на null в спецификацията за patch, за да го премахнете от ресурса. Изтриването на елемент от масив с използване на стратегическо сливане е по-сложно, тъй като изисква използване на директиви за сливане. Вижте други подходи за актуализиране по-долу, за да изберете по-приемливи алтернативи.
За да реализирате в клиентската библиотека методи за актуализиране, които се държат аналогично на горепосочените команди kubectl, трябва в заявките да зададете content-type в application/strategic-merge-patch+json. Ако искате да изтривате свойства в спецификацията, трябва явно да зададете техните стойности на null, подобно на kubectl patch. Ако трябва да изтривате елементи от масив, трябва да включите директиви за сливане в спецификацията за актуализиране или да използвате друг подход за актуализации.
Други подходи за актуализиране
В Kubernetes се поддържат два други подхода за актуализиране: и . Подходът JSON merge patch приема частична спецификация на Kubernetes като входни данни и поддържа сливане на обекти подобно на подхода strategic-merge patching. Разликата между тях е, че той поддържа само замяна на масиви, включително масива на контейнерите в спецификацията на pod. Това означава, че при използване на JSON merge patch трябва да предоставите пълни спецификации за всички контейнери в случай на промяна на свойство на който и да е контейнер. Така, този подход е полезен за премахване на елементи от масив в спецификацията. В командния ред можете да изберете JSON merge patch, използвайки kubectl patch --type=merge. При работа с API Kubernetes следва да се използва методът на запитване PATCH и инсталиране content-type в application/merge-patch+json.
Подходът JSON patch вместо да предоставя частична спецификация на ресурса, използва предоставяне на измененията, които искате да внесете в ресурса, под формата на масив, в който всеки елемент на масива представлява описание на промяната, която се внася в ресурса. Този подход е по-гъвкав и мощен начин за изразяване на вносимите промени, но за сметка на това, че списъкът на вносимите изменения е в отделен, не Kubernetes формат, вместо да изпрати частична спецификация на ресурса. В kubectl можете да изберете JSON patch, използвайки kubectl patch --type=json. При използване на API Kubernetes този подход работи с метода на запитване PATCH и инсталиране content-type в application/json-patch+json.
Нужна е сигурност — използваме replace
В някои случаи е необходима сигурност, че в ресурса няма да бъдат внесени промени между времето на прочитане на ресурса и неговото обновление. С други думи, трябва да се уверим, че всички промени ще бъдат атомарни. В този случай за обновление на ресурсите е добре да се използва replace. Например, ако имате ConfigMap с брояч, който се обновява от няколко източника, е нужно да се уверите, че двата източника не обновяват брояча едновременно, което ще доведе до загуба на обновлението. За демонстрация, представете си последователността на събитията, използвайки подхода patch:
- A и B получават актуалното състояние на ресурса от API
- Всеки от тях локално обновява спецификацията, увеличавайки брояча с единица, и също така добавя "A" или "B" съответно в бележката "updated-by"
- A обновява ресурса малко по-бързо
- B обновява ресурса
В резултат на това обновление A е изгубено. Последната операция patch печели, броячът се увеличава с единица вместо с две, а стойността на забележката "updated-by" завършва на "B" и не съдържа "A". Нека сравним казаното с това, което се случва, когато актуализациите се извършват с помощта на подхода replace:
- A и B получават актуалното състояние на ресурса от API
- Всеки от тях локално обновява спецификацията, увеличавайки брояча с единица, и също така добавя "A" или "B" съответно в бележката "updated-by"
- A обновява ресурса малко по-бързо
- B се опитва да обнови ресурса, но актуализацията е отхвърлена от API, тъй като версията на ресурса в спецификацията
replaceне съвпада с текущата версия на ресурса в Kubernetes, тъй като версията на ресурса е увеличена по време на операцията replace от A.
В горепосочения случай B ще трябва да изтегли ресурса отново, да направи промените в новото състояние и да опита отново replace. В резултат на това броячът ще бъде увеличен с две, а забележката "updated-by" ще съдържа "AB" в края.
Горният пример предполага, че при изпълнението на replace се извършва пълна замяна на целия ресурс. Спецификацията, използвана за replace, трябва да бъде не частична или на части, както в apply, а пълна, включително добавянето на resourceVersion в метаданните на спецификацията. Ако не сте включили resourceVersion или предоставената от вас версия не е текуща, замяната ще бъде отхвърлена. Така че, най-добрият подход за използване replace е да прочетете ресурса, да го актуализирате и незабавно да го замените. Използвайки kubectl, това може да изглежда така:
$ kubectl get deployment my-deployment -o json
| jq '.spec.template.spec.containers[0].env[1].value = "new value"'
| kubectl replace -f -С оглед на това е важно да се отбележи, че следните две команди, изпълнени последователно, ще бъдат успешни, тъй като deployment.yaml не съдържа свойство .metadata.resourceVersion
$ kubectl create -f deployment.yaml
$ kubectl replace -f deployment.yamlНа пръв поглед, това противоречи на казаното по-горе, т.е. "добавяне на resourceVersion в метаданните на спецификацията". Правилно ли е да твърдим така? Не, не е, тъй като ако kubectl забележи, че не сте посочили resourceVersion, той ще я прочете от ресурса и ще я добави в посочената от вас спецификация и едва след това ще изпълни replace. Тъй като това е потенциално опасно, ако разчитате на атомарността, магията работи изцяло отстрани на kubectl, не бива да разчитате на нея, когато използвате клиентски библиотеки, работещи с API. В този случай ще трябва да прочетете текущата спецификация на ресурса, да я актуализирате и след това да изпълните PUT заявка.
Не може да се направи patch – правим replace
Понякога е необходимо да внесем някои промени, които не могат да бъдат обработени от API. В тези случаи може принудително да заменим ресурса, като го премахнем и създадем отново. Това се прави с помощта на kubectl replace --force. Изпълнението на командата веднага премахва ресурсите и след това ги създава отново с предоставената спецификация. В API няма обработчик "принудително замени", а за да се направи това през API, е необходимо да се извършат две операции. Първо трябва да се премахне ресурсът, като се зададе за него gracePeriodSeconds на нула (0) и propagationPolicy на “Background”, а след това да се създаде отново този ресурс с желаната спецификация.
Внимание: този подход е потенциално опасен и може да доведе до неопределено състояние
Apply на страната на сървъра.
Както беше споменато по-горе, разработчиците на Kubernetes работят над внедряването на логиката apply от kubectl в API на Kubernetes. Логиката apply е налична в Kubernetes 1.18 чрез kubectl apply --server-side или чрез API, като се използва методът PATCH с content-type application/apply-patch+YAML.
Забележка: JSON също е валиден YAML, така че можете да изпратите спецификацията във вид на JSON, дори ако
content-typeще бъдеapplication/apply-patch+yaml.
Освен това, логиката kubectl става достъпна за всички чрез API, apply на страната на сървъра следи отговорниците за полетата в спецификацията, позволявайки безопасен многопотребителски достъп за безконфликтно редактиране. Иначе казано, ако apply на страната на сървъра получи по-широко разпространение, ще се появи универсален безопасен интерфейс за управление на ресурсите за различни клиенти, например kubectl, Pulumi или Terraform, GitOps, както и самостоятелни скриптове, използващи клиентски библиотеки.
Итог
Надявам се, че този кратък преглед на различните начини за обновяване на ресурсите в клъстери е бил полезен за вас. Полезно е да знаете, че противниците не просто "apply" срещу "replace", тъй като можете да обновите ресурс чрез apply, edit, patch или replace. Всеки подход има своята област на приложение. За атомарни промени предпочитан е replace, в противен случай е по-добре да се използва strategic-merge patch чрез apply. В краен случай се надявам, че сте разбрали, че не можете да се доверявате на Google или StackOverflow при търсене на "kubernetes apply vs replace". Най-малкото, докато тази статия не замени текущия отговор.

Източник: habr.com
