3-way merge у werf: дэплой у Kubernetes з Helm «на пазіцыі, метадалагічнай»

Здарылася тое, чаго мы (і не толькі мы) доўга чакалі: werf, наша Open Source-ўтыліта для зборкі прыкладанняў і іх дастаўкі ў Kubernetes, зараз падтрымлівае прымяненне змяненняў з дапамогай 3-way-merge-патчаў! У дадатак да гэтага, з'явілася магчымасць adoption'а існуючых K8s-рэсурсаў у Helm-рэлізы без перастварэння гэтых рэсурсаў.

3-way merge у werf: дэплой у Kubernetes з Helm «на пазіцыі, метадалагічнай»

Калі зусім сцісла, то ставім WERF_THREE_WAY_MERGE=enabled - атрымліваем дэплой «як у kubectl apply», сумяшчальны з існуючымі інсталяцыямі на Helm 2 і нават крыху больш.

Але давайце пачнем з тэорыі: што ўвогуле такое 3-way-merge-патчы, як людзі прыйшлі да падыходу з іх генерацыяй і чаму яны важныя ў CI/CD-працэсах з інфраструктурай на базе Kubernetes? А пасля гэтага - паглядзім, што ж уяўляе сабой 3-way-merge у werf, якія рэжымы выкарыстоўваюцца па змаўчанні і як гэтым кіраваць.

Што такое 3-way-merge-патч?

Такім чынам, пачнем з задачы выкату рэсурсаў, апісаных у YAML-маніфестах, у Kubernetes.

Для працы з рэсурсамі Kubernetes API прапануе такія асноўныя аперацыі: create, patch, replace і delete. Мяркуецца, што з іх дапамогай трэба сканструяваць зручны бесперапынны выкат рэсурсаў у кластар. Як?

Імператыўныя каманды kubectl

Першы падыход да кіравання аб'ектамі ў Kubernetes - выкарыстанне імператыўных каманд kubectl для стварэння, змены і выдаленні гэтых аб'ектаў. Прасцей кажучы:

  • камандай kubectl run можна запусціць Deployment або Job:
    kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE
  • камандай kubectl scale - памяняць колькасць рэплік:
    kubectl scale --replicas=3 deployment/mysql
  • і г.д.

Такі падыход можа здацца зручным з першага позірку. Аднак ёсць праблемы:

  1. Яго цяжка аўтаматызаваць.
  2. Як адлюстраваць канфігурацыю у Git? Як рабіць review зменаў, якія адбываюцца з кластарам?
  3. Як забяспечыць ўзнаўляльнасць канфігурацыі пры перазапуску?
  4. ...

Зразумела, што такі падыход дрэнна спалучаецца з захоўваннем разам з кодам прыкладання і інфраструктуры як кода (IaC; ці нават GitOps як больш сучаснага варыянту, які набірае папулярнасць у Kubernetes-экасістэме). Таму далейшага разьвіцьця гэтыя каманды ў kubectl не атрымалі.

Аперацыі create, get, replace і delete

З першасным стварэннем усё проста: адпраўляем маніфест у аперацыю create у kube api і рэсурс створаны. YAML-прадстаўленне маніфесту можна захоўваць у Git, а для стварэння - выкарыстоўваць каманду kubectl create -f manifest.yaml.

С выдаленнем таксама проста: падстаўляем той жа manifest.yaml з Git у каманду kubectl delete -f manifest.yaml.

Аперацыя replace дазваляе цалкам замяніць канфігурацыю рэсурса на новую, без перастварэння рэсурса. Гэта азначае, што перад тым, як рабіць змену ў рэсурс, лагічна запытаць бягучую версію аперацыяй get, змяніць яе і абнавіць аперацыяй replace. У kube apiserver убудаваны optimistic locking і, калі пасля аперацыі get аб'ект памяняўся, то аперацыя replace не пройдзе.

Каб захоўваць канфігурацыю ў Git і абнаўляць з дапамогай replace, трэба рабіць аперацыю get, смерціць канфіг з Git'а з тым, што мы атрымалі, і выконваць replace. Штатна kubectl дазваляе толькі карыстацца камандай kubectl replace -f manifest.yaml, Дзе manifest.yaml - ужо цалкам падрыхтаваны (у нашым выпадку - смяртэжаны) маніфест, які патрабуецца ўсталяваць. Атрымліваецца, карыстачу неабходна рэалізаваць merge маніфестаў, а гэтая справа нетрывіяльная…

Таксама варта адзначыць, што хаця manifest.yaml і захоўваецца ў Git, мы не можам ведаць загадзя, трэба ствараць аб'ект або абнаўляць яго - гэта павінен рабіць карыстацкі софт.

Разам: ці можам мы пабудаваць бесперапынны выкат толькі з дапамогай create, replace і delete, забяспечыўшы захоўванне канфігурацыі інфраструктуры ў Git'е разам з кодам і зручны CI/CD?

У прынцыпе, можам… Для гэтага спатрэбіцца рэалізаваць аперацыю merge маніфестаў і нейкую абвязку, якая:

  • правярае наяўнасць аб'екта ў кластары,
  • выконвае першаснае стварэнне рэсурсу,
  • абнаўляе ці выдаляе яго.

Пры абнаўленні трэба ўлічыць, што рэсурс мог памяняцца з часу апошняга get і аўтаматычна апрацоўваць выпадак optimistic locking - рабіць паўторныя спробы абнаўлення.

Аднак навошта вынаходзіць ровар, калі kube-apiserver прапануе іншы спосаб абнаўлення рэсурсаў: аперацыю patch, якая здымае з карыстальніка частку апісаных праблем?

пластыр

Вось мы і дабраліся да патчаў.

Патчы - гэта асноўны спосаб прымянення змяненняў да існуючых аб'ектаў у Kubernetes. Аперацыя patch працуе так, што:

  • карыстачу kube-apiserver патрабуецца паслаць патч у JSON-выглядзе і паказаць аб'ект,
  • а apiserver сам разбярэцца з бягучым станам аб'екта і прывядзе яго да патрабаванага ўвазе.

Optimistic locking у дадзеным выпадку не патрабуецца. Гэтая аперацыя больш дэкларатыўная ў параўнанні з replace, хаця спачатку можа здацца наадварот.

Такім чынам:

  • з дапамогай аперацыі create мы ствараем аб'ект па маніфесце з Git'а,
  • з дапамогай delete - выдаляем, калі аб'ект больш не патрабуецца,
  • з дапамогай patch - Змяняем аб'ект, прыводзячы яго да выгляду, апісанаму ў Git.

Аднак, каб гэта зрабіць, неабходна стварыць правільны патч!

Як працуюць патчы ў Helm 2: 2-way-merge

Пры першай усталёўцы рэлізу Helm выконвае аперацыю create для рэсурсаў чарта.

Пры абнаўленні рэлізу Helm для кожнага рэсурсу:

  • лічыць патч паміж версіяй рэсурсу з мінулага чарта і бягучай версіяй чарта,
  • ужывае гэты патч.

Такі патч мы будзем зваць 2-way-merge patch, таму што ў яго стварэнні ўдзельнічаюць 2 маніфесты:

  • маніфест рэсурсу з папярэдняга рэлізу,
  • маніфест рэсурсу з бягучага рэсурса.

Пры выдаленні аперацыя delete у kube apiserver выклікаецца для рэсурсаў, якія былі абвешчаныя ў мінулым рэлізе, але не абвешчаныя ў бягучым.

Падыход з 2 way merge patch мае праблему: ён прыводзіць да расінхрона рэальнага стану рэсурсу ў кластары і маніфесту ў Git.

Ілюстрацыя праблемы на прыкладзе

  • У Git, у чарце захоўваецца маніфест, у якім поле image у Deployment мае значэнне ubuntu:18.04.
  • Карыстальнік праз kubectl edit памяняў значэнне гэтага поля на ubuntu:19.04.
  • Пры паўторным дэплоі чарта Helm не генеруе патч, таму што поле image у папярэдняй версіі рэлізу і ў бягучым чарце аднолькавыя.
  • Пасля паўторнага дэплою image застаецца ubuntu:19.04, хоць у чарце напісана ubuntu:18.04.

Мы атрымалі рассінхранізацыю і страцілі дэкларатыўнасць.

Што такое сінхранізаваны рэсурс?

Наогул кажучы, поўнае адпаведнасць маніфеста рэсурсу ў які працуе кластары і маніфеста з Git атрымаць немагчыма. Таму што ў рэальным маніфесце могуць быць службовыя анатацыі/лэйблы, дадатковыя кантэйнеры і іншыя дадзеныя, якія дадаюцца і выдаляюцца з рэсурсу дынамічна нейкімі кантролерамі. Гэтыя дадзеныя мы не можам і не жадаем трымаць у Git. Аднак мы жадаем, каб пры выкаце тыя палі, якія мы відавочна паказалі ў Git'е, прымалі адпаведныя значэнні.

Атрымліваецца такое агульнае правіла сінхранізаванага рэсурсу: пры выкаце рэсурсу можна мяняць або выдаляць толькі тыя палі, якія відавочна прапісаны ў маніфесце з Git'а (ці былі прапісаны ў папярэдняй версіі, а зараз выдалены).

3-way-merge patch

Асноўная ідэя 3-way-merge patch: генеруем патч паміж апошняй ужытай версіяй маніфэсту з Git'а і мэтавай версіяй маніфэсту з Git'а з улікам бягучай версіі маніфэсту з працуючага кластара. Выніковы патч павінен адпавядаць правілу сінхранізаванага рэсурсу:

  • новыя палі, дададзеныя ў мэтавую версію, дадаюцца з дапамогай патча;
  • раней існавалыя палі ў апошняй ужытай версіі і не існыя ў мэтавай - абнуляюцца з дапамогай патча;
  • палі ў бягучай версіі аб'екта, адрозныя ад мэтавай версіі маніфеста, - абнаўляюцца з дапамогай патча.

Менавіта па такім прынцыпе генеруе патчы. kubectl apply:

  • апошняя прымененая версія маніфеста захоўваецца ў анатацыі самога аб'екта,
  • мэтавая - бярэцца з паказанага YAML-файла,
  • бягучая - з працуючага кластара.

Цяпер, калі разабраліся з тэорыяй, пара расказаць, што ж мы зрабілі ў werf.

Ужыванне змен у werf

Раней werf, як і Helm 2, выкарыстоўваў 2-way-merge-патчы.

Патч для рамонту

Для таго, каб перайсці на новы від патчаў – 3-way-merge, – першым крокам мы ўвялі так званыя repair-патчы.

Пры дэплоі выкарыстоўваецца стандартны 2-way-merge-патч, але werf дадаткова генеруе такі патч, які б сінхранізаваў рэальны стан рэсурсу з тым, што напісана ў Git (ствараецца такі патч з выкарыстаннем таго ж правілы сінхранізаванага рэсурсу, апісанага вышэй).

У выпадку ўзнікнення рассінхрона, у канцы дэплою карыстальнік атрымлівае WARNING з адпаведным паведамленнем і патчам, які трэба прымяніць, каб прывесці рэсурс да сінхранізаванаму ўвазе. Таксама гэты патч запісваецца ў адмысловую анатацыю werf.io/repair-patch. Мяркуецца, што карыстач рукамі сам прыменіць гэты патч: werf яго прымяняць не будзе прынцыпова.

Генерацыя repair-патчаў - гэта часовая мера, якая дазваляе выпрабаваць на справе стварэнне патчаў па прынцыпе 3-way-merge, але аўтаматычна гэтыя патчы не ўжываць. На дадзены момант такі рэжым працы ўключаны па змаўчанні.

3-way-merge patch толькі для новых рэлізаў

Пачынаючы з 1 снежня 2019 г. beta- і alpha-версіі werf пачынаюць па змаўчанні выкарыстоўваць паўнавартасныя 3-way-merge-патчы для ўжывання змен толькі для новых Helm-рэлізаў, якія выкочваюцца праз werf. Ужо існуючыя рэлізы працягнуць выкарыстоўваць падыход з 2-way-merge + repair-патчамі.

Дадзены рэжым працы можна ўключыць відавочна настройкай WERF_THREE_WAY_MERGE_MODE=onlyNewReleases ужо зараз.

Заўвага: фіча з'яўлялася ў werf на працягу некалькіх рэлізаў: у альфа-канале яна стала гатовай з версіі v1.0.5-alpha.19, а ў бэта-канале - з v1.0.4-beta.20.

3-way-merge patch для ўсіх рэлізаў

Пачынальна з 15 снежня 2019 г. beta- і alpha-версіі werf пачынаюць па змаўчанні выкарыстоўваць паўнавартасныя 3-way-merge-патчы для ўжывання змен для ўсіх рэлізаў.

Дадзены рэжым працы можна ўключыць відавочна настройкай WERF_THREE_WAY_MERGE_MODE=enabled ужо зараз.

Як быць з аўтамаштабаванне рэсурсаў?

У Kubernetes існуе 2 тыпу аўтамаштабавання: HPA (гарызантальны) і VPA (вертыкальны).

Гарызантальны аўтаматычна выбірае колькасць рэплік, вертыкальны - колькасць рэсурсаў. І колькасць рэплік, і патрабаванні да рэсурсаў указваюцца ў маніфесце рэсурсу (гл. spec.replicas або spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory и іншыя).

Праблема: калі карыстач сканфігуруе рэсурс у чарце так, што ў ім будуць паказаныя вызначаныя значэнні па рэсурсах ці рэплікам і для дадзенага рэсурсу будуць уключаныя аўтаскейлеры, то пры кожным дэплоі werf будзе скідаць гэтыя значэнні ў тое, што запісана ў маніфесце чарта.

Рашэнняў у праблемы два. Для пачатку лепш за ўсё адмовіцца ад відавочнага ўказання аўтамаштабаваных значэнняў у маніфесце чарта. Калі ж гэты варыянт па нейкіх прычынах не падыходзіць (напрыклад, таму што ў чарце зручна задаць пачатковыя абмежаванні рэсурсаў і колькасць рэплік), то werf прапануе наступныя анатацыі:

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

Пры наяўнасці такой анатацыі werf не будзе скідаць адпаведныя значэнні пры кожным дэплоі, а толькі ўсталюе іх пры першапачатковым стварэнні рэсурса.

Больш падрабязна - гл. у дакументацыі праекта па HPA и VPA.

Забараніць выкарыстанне 3-way-merge patch

Карыстальнік пакуль можа забараніць выкарыстанне новых патчаў у werf з дапамогай зменнай асяроддзя WERF_THREE_WAY_MERGE_MODE=disabled. Аднак пачынаючы з 1 сакавіка 2020 года гэтая забарона перастане працаваць і магчыма будзе толькі выкарыстанне 3-way-merge-патчаў.

Adoption рэсурсаў у werf

Засваенне метаду ўжывання змен 3-way-merge-патчамі дазволіла нам адразу рэалізаваць такую ​​фічу, як adoption існых у кластары рэсурсаў у Helm-рэліз.

Helm 2 мае праблему: нельга дадаць у маніфесты чарта рэсурс, які ўжо існуе ў кластары, без перастварэння з нуля гэтага рэсурсу (гл. #6031, #3275). Мы навучылі werf прымаць існуючыя рэсурсы ў выпуску. Для гэтага трэба ўсталяваць на бягучую версію рэсурсу з працуючага кластара анатацыю (напрыклад, з дапамогай kubectl edit):

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

Цяпер рэсурс трэба апісаць у чарце і пры наступным дэплоі werf'ом рэлізу з адпаведным імем існуючы рэсурс будзе прыняты ў гэты рэліз і застанецца пад яго кіраваннем. Больш за тое, у працэсе прыняцця рэсурсу ў рэліз werf прывядзе бягучы стан рэсурсу з працуючага кластара да стану, апісанаму ў чарце, выкарыстоўваючы тыя ж 3-way-merge-патчы і правіла сінхранізаванага рэсурсу.

Заўвага: ўстаноўка WERF_THREE_WAY_MERGE_MODE не ўплывае на adoption рэсурсаў - у выпадку adoption заўсёды выкарыстоўваецца 3-way-merge-патч.

Падрабязнасці - у дакументацыі.

Высновы і далейшыя планы

Спадзяюся, пасля гэтага артыкула стала больш зразумела, што такое 3-way-merge-патчы і чаму да іх прыйшлі. З практычнага пункта гледжання развіцця праекту werf іх рэалізацыя стала яшчэ адным крокам на шляхі паляпшэння Helm-падобнага дэплою. Зараз можна забыцца пра праблемы з сінхранізацыяй канфігурацыі, якія часта ўзнікалі пры выкарыстанні Helm 2. Разам з тым, была дададзеная новая карысная фіча adoption'а ўжо выпампаваных Kubernetes-рэсурсаў у Helm-рэліз.

У Helm-падобным дэплоі па-ранейшаму застаюцца некаторыя праблемы і цяжкасці, такія як выкарыстанне Go-шаблонаў, і мы працягнем іх вырашаць.

Інфармацыю аб метадах абнаўлення рэсурсаў і adoption'е можна таксама знайсці на гэтай старонцы дакументацыі.

Helm 3

Асобнай заўвагі вартая якая выйшла літаральна на днях новая мажорная версія Helm – v3, – якая таксама выкарыстоўвае 3-way-merge-патчы і пазбаўляецца ад Tiller. Новая версія Helm патрабуе міграцыі ужо існуючых установак, каб сканвертаваць іх у новы фармат захоўвання рэлізаў.

Werf са свайго боку на дадзены момант ужо пазбавіўся ад выкарыстання Tiller, пераключыўся на 3-way-merge і дадаў шмат іншага, пры гэтым застаўшыся сумяшчальным з ужо існуючымі інсталяцыямі на Helm 2 (ніякіх скрыптоў міграцыі выконваць не трэба). Таму, пакуль werf не пераключаны на Helm 3, карыстачы werf не губляюць асноўных пераваг Helm 3 перад Helm 2 (у werf яны таксама ёсць).

Тым не менш, пераключэнне werf на кодавую базу Helm 3 непазбежна і адбудзецца ў найбліжэйшай будучыні. Меркавана гэта будзе werf 1.1 ці werf 1.2 (на дадзены момант, галоўная версія werf – 1.0; падрабязней пра прыладу версіявання werf гл. тут). За гэты час Helm 3 паспее стабілізавацца.

PS

Чытайце таксама ў нашым блогу:

Крыніца: habr.com

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