Правильне порівняння Kubernetes Apply, Replace та Patch

Для Kubernetes є кілька варіантів оновлення ресурсів: apply, edit, patch і replace. З тим, що кожен із них робить і коли їх застосовувати, є плутанина. Давайте розберемося.

Правильне порівняння Kubernetes Apply, Replace та Patch

Якщо пошукати в Google фразу "kubernetes apply vs replace", знаходиться відповідь на StackOverflow, Що не вірний. При пошуку "kubernetes apply vs patch" перше ж посилання - документація на kubectl patch, яка не включає порівняння apply и patch. У цій статті будуть розглянуті різні варіанти та правильне використання кожного з них.

Протягом життєвого циклу ресурсу Kubernetes (сервісу, deployment, ingress тощо) іноді потрібно змінити, додати чи видалити деякі властивості цього ресурсу. Наприклад, додати примітку, збільшити чи зменшити кількість реплік.

CLI Kubernetes

Якщо ви вже працюєте з кластерами Kubernetes через CLI, то вже знайомі з apply и edit. команда apply читає специфікацію ресурсу з файлу і робить "upsert" кластер Kubernetes, тобто. створює ресурс, якщо його немає, та оновлює його, якщо він існує. Команда edit читає ресурс через API, після чого пише специфікацію ресурсу локальний файл, який потім відкривається в текстовому редакторі. Після того, як ви відредагуєте та збережете файл, kubectl відправить зроблені зміни через API, який дбайливо застосує ці зміни до ресурсу.

Не всі знають команди patch и replace. команда patch дозволяє змінити частину специфікації ресурсу, надаючи лише змінену частину командному рядку. Команда replace працює так само, як і edit, але тільки все треба зробити вручну: необхідно завантажити поточну версію специфікації ресурсу, наприклад, з використанням kubectl get -o yaml, редагувати її, потім використовувати replace для оновлення ресурсу за зміненою специфікацією. Команда replace не відпрацює, якщо між читанням та заміною ресурсу відбулися будь-які зміни.

API Kubernetes

Ви, мабуть, знайомі з методами CoreV1().Pods().Update(), replaceNamespacedService або patch_namespaced_deployment, якщо працюєте з кластерами через клієнтську бібліотеку для API Kubernetes із використанням деякої мови програмування. Бібліотека обробляє ці методи за допомогою запитів за протоколом HTTP, використовуючи методи PUT и PATCH. При цьому update и 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 для оновлення наявного ресурсу. Якщо вникнути детальніше у реалізацію команд, то у всіх використовується підхід strategic-merge patching для оновлення ресурсів, хоча команда 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видалити його з ресурсу. Видалення елемента масиву з використанням strategic-merge patching є складнішим, оскільки потрібне використання директив злиття. Дивіться інші підходи до оновлення нижче для вибору більш прийнятних альтернатив.

Щоб реалізувати в клієнтській бібліотеці методи оновлення, які ведуть себе аналогічно до наведених вище команд. kubectlслід у запитах виставляти content-type в application/strategic-merge-patch+json. Якщо ви хочете видалити властивості в специфікації, вам потрібно явно встановити їх значення null аналогічно kubectl patch. Якщо потрібно видаляти елементи масиву, слід включити директиви злиття у специфікацію оновлення або використовувати інший підхід до оновлень.

Інші підходи до оновлень

У Kubernetes підтримуються два інші підходи до оновлень: JSON merge patch и JSON patch. Підхід 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"
  • А трохи швидше оновлює ресурс
  • B оновлює ресурс

В результаті оновлення A втрачено. Остання операція patch виграє, лічильник збільшується на одиницю замість двох, а значення примітки "updated-by" закінчується "B" і не містить "A". Давайте порівняємо сказане вище з тим, що відбувається, коли оновлення виконуються з використанням підходу replace:

  • A та B отримують поточний стан ресурсу з API
  • Кожен з них локально оновлює специфікацію, збільшуючи лічильник на одиницю, а також додаючи "A" або "B" відповідно до примітки "updated-by"
  • А трохи швидше оновлює ресурс
  • 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 або StackOerflow при пошуку "kubernetes apply vs replace". Принаймні, поки ця стаття не замінить поточну відповідь.

Правильне порівняння Kubernetes Apply, Replace та Patch

Джерело: habr.com

Додати коментар або відгук