Правільнае параўнанне 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 і г.д.) часам трэба памяняць, дадаць ці выдаліць некаторыя ўласцівасці гэтага рэсурсу. Напрыклад, дадаць нататку, павялічыць або паменшыць колькасць рэплік.

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, калі працуеце з кластарамі праз кліенцкую бібліятэку для 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

Дадаць каментар