3-way merge v werf: nasazení do Kubernetes s Helm "na steroidech"

Stalo se to, na co jsme (a nejen my) dlouho čekali: werf, naše Open Source utilita pro sestavení aplikací a jejich nasazení do Kubernetes nyní podporuje aplikaci změn pomocí 3-way-merge patchů! Kromě toho jsme přidali možnost adopce stávajících K8s zdrojů do Helm release bez nutnosti jejich přetvoření.

3-way merge v werf: nasazení do Kubernetes s Helm "na steroidech"

Pokud to shrneme, nastavte WERF_THREE_WAY_MERGE=enabled — obdržíte nasazení „jako v kubectl apply“, kompatibilní se stávajícími instalacemi na Helm 2 a ještě více.

Ale začněme teorií: co vlastně jsou 3-way-merge patche, jak lidé dospěli k přístupu jejich generace a proč jsou důležité v CI/CD procesech s infrastrukturou postavenou na Kubernetes? A poté se podíváme, co 3-way-merge v werf představuje, jaké režimy jsou implicitně používány a jak je řídit.

Co je to 3-way-merge patch?

Takže začněme úkolem nasazení zdrojů popsaných v YAML manifestech do Kubernetes.

Pro práci se zdroji Kubernetes API nabízí tyto základní operace: create, patch, replace a delete. Předpokládá se, že s jejich pomocí je třeba konstruovat pohodlné kontinuální nasazení zdrojů do clusteru. Jak?

Imperativní příkazy kubectl

První přístup k řízení objektů v Kubernetes je použití imperativních příkazů kubectl pro vytváření, změnu a odstraňování těchto objektů. Jinými slovy:

  • příkazem kubectl run můžete spustit Deployment nebo Job:
    kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE
  • příkazem kubectl scale — změnit počet replik:
    kubectl scale --replicas=3 deployment/mysql
  • atd.

Tento přístup se na první pohled může zdát pohodlný. Nicméně jsou zde problémy:

  1. Jak těžké je automatizovat.
  2. Jak odrazit konfiguraci v Gitu? Jak provádět revizi změn, které probíhají s clusterem?
  3. Jak zajistit reprodukovatelnost konfigurace při restartu?
  4. …

Je jasné, že tento přístup špatně ladí s uchováváním spolu s kódem aplikace a infrastrukturou jako kódem (IaC; nebo dokonce GitOps jako modernější variantou, která získává popularitu v Kubernetes ekosystému). Proto tyto příkazy v kubectl nedostaly další vývoj.

Operace create, get, replace a delete

Se základním vytvořením je to jednoduché: odešlete manifest do operace – vytvoření instance, u kube api a zdroj je vytvořen. YAML reprezentace manifestu může být uložen na Gitu a pro vytvoření lze použít příkaz kubectl create -f manifest.yaml.

S odstranění je také jednoduché: použijte stejný manifest.yaml z Gitu v příkazu kubectl delete -f manifest.yaml.

Operace replace umožňuje úplnou výměnu konfigurace zdroje za novou, aniž by bylo potřeba vytvářet nový zdroj. To znamená, že před provedením změny na zdroji je logické vyžádat si aktuální verzi operací získat, změnit ji a aktualizovat pomocí operace replace. V kube apiserveru je zabudováno optimistické zamykání , a pokud se po operaci získat objekt změnil, tak operace replace neproběhne.

Abychom mohli uchovávat konfiguraci v Gitu a aktualizovat pomocí replace, musíme provést operaci získat, sloučit konfiguraci z Gitu s tím, co jsme obdrželi, a provést replace. Standardně kubectl umožňuje pouze používat příkaz kubectl replace -f manifest.yaml, kde manifest.yaml — již plně připravený (v našem případě — sloučený) manifest, který je třeba nainstalovat. Z toho vyplývá, že uživatel musí realizovat sloučení manifestů, což je úkol, který není triviální…

Také je třeba poznamenat, že i když manifest.yaml je uchováváno v Gitu, nemůžeme předem vědět, zda je třeba objekt vytvořit nebo aktualizovat — to musí provádět uživatelský software.

Celkem: můžeme-li budovat kontinuální nasazení pouze pomocí create, replace a delete, zajišťující uchovávání konfigurace infrastruktury v Gitu spolu s kódem a pohodlný CI/CD?

V zásadě můžeme… K tomu bude třeba realizovat operaci sloučení manifestů a nějaké obvaz, které:

  • kontroluje existenci objektu v clusteru,
  • provádí primární vytvoření zdroje,
  • aktualizuje nebo jej odstraňuje.

Při aktualizaci je třeba vzít v úvahu, že zdroj se mohl změnit od posledního získat a automaticky zpracovávat případ optimistického zamykání — provádět opakované pokusy o aktualizaci.

Nicméně, proč vynalézat kolo, když kube-apiserver nabízí jiný způsob, jak aktualizovat zdroje: operaci patch, která uživateli odstraní část popsaných problémů?

Patch

A jsme u patchů.

Patchy jsou hlavním způsobem, jak aplikovat změny na existující objekty v Kubernetes. Operace patch funguje tak, že:

  • uživatel kube-apiserver musí poslat patch ve formátu JSON a uvést objekt,
  • a apiserver sám zjistí aktuální stav objektu a přivede ho do požadovaného stavu.

Optimistické zamykání v tomto případě není vyžadováno. Tato operace je více deklarativní ve srovnání s replace, i když se to na první pohled může zdát naopak.

Tímto způsobem:

  • pomocí operace – vytvoření instance, vytváříme objekt podle manifestu z Gitu,
  • prostřednictvím odstranit — odstraňujeme, pokud objekt není více potřeba,
  • prostřednictvím patch — měníme objekt, přivádíme ho do podoby, jakou popisuje Git.

Aby to bylo možné, je nutné vytvořit správný patch!

Jak fungují patche v Helmu 2: dvoucestné sloučení

Při první instalaci vydání Helm provádí operaci – vytvoření instance, pro zdroje chartu.

Při aktualizaci vydání Helm pro každý zdroj:

  • zjistí patch mezi verzí zdroje z předchozího chartu a aktuální verzí chartu,
  • aplikuje tento patch.

Takový patch nazýváme dvoucestným sloučením, protože při jeho tvorbě se podílejí 2 manifesty:

  • manifest zdroje z předchozího vydání,
  • manifest zdroje z aktuálního zdroje.

Při odstranění se operace odstranit v kube apiserveru volá pro zdroje, které byly deklarovány v minulém vydání, ale nejsou uvedeny v aktuálním.

Přístup s dvoucestným sloučením má problém: vede k desynchronizaci skutečného stavu zdroje v clusteru a manifestu v Gitu.

Ilustrace problému na příkladu

  • V Gitu, v chartu se nachází manifest, v němž pole obrázek u nasazení má hodnotu ubuntu:18.04.
  • Uživatel prostřednictvím kubectl edit změnil hodnotu tohoto pole na ubuntu:19.04.
  • Při opětovném nasazení chartu Helm negeneruje patch, protože pole obrázek v předchozí verzi vydání a v aktuálním chartu jsou stejná.
  • Po opětovném nasazení obrázek zůstává ubuntu:19.04, i když v chartu je napsáno ubuntu:18.04.

Dostali jsme desynchronizaci a ztrátu deklarativnosti.

Co je to synchronizovaný zdroj?

Obecně řečeno, Úplné shodování manifestu zdroje v běžícím clusteru a manifestu z Gitu není možné. Protože v reálném manifestu mohou být servisní anotace/štítky, další kontejnery a další data, která jsou dynamicky přidávána a odstraňována z zdroje nějakými controllery. Tato data nemůžeme a ani nechceme mít v Git. Nicméně chceme, aby při nasazení ta pole, která jsme výslovně uvedli v Git, přijímala odpovídající hodnoty.

Vzniká tedy takové obecné pravidlo synchronizovaného zdroje: při nasazení zdroje lze měnit nebo odstraňovat pouze ta pole, která jsou výslovně uvedena v manifestu z Gitu (nebo byla uvedena v předchozí verzi a nyní byla odstraněna).

třícestné sloučení

Hlavní myšlenka třícestné sloučení: generujeme patch mezi poslední aplikovanou verzí manifestu z Gitu a cílovou verzí manifestu z Gitu s ohledem na aktuální verzi manifestu z běžícího clusteru. Konečný patch by měl vyhovovat pravidlu synchronizovaného zdroje:

  • nová pole, přidaná v cílové verzi, jsou přidána pomocí patch.
  • dříve existující pole ve poslední aplikované verzi a neexistující v cílové — se nulují pomocí patche;
  • pole v aktuální verzi objektu, která se liší od cílové verze manifestu, — se aktualizují pomocí patche.

Podle tohoto principu se generují patche kubectl apply:

  • poslední aplikovaná verze manifestu se ukládá v anotaci samotného objektu,
  • cílová — se bere z určeného souboru YAML,
  • aktuální — z běžícího clusteru.

Nyní, když jsme si vysvětlili teorii, je čas říci, co jsme udělali v werf.

Aplikace změn v werf

Dříve werf, stejně jako Helm 2, použil patche typu 2-way-merge.

Repair patch

Abychom přešli na nový typ patchů — 3-way-merge, prvním krokem jsme zavedli takzvané repair-patche.

Při nasazení se používá standardní patch typu 2-way-merge, ale werf navíc generuje takový patch, který by synchronizoval aktuální stav zdroje s tím, co je napsáno v Git (takový patch se vytváří podle stejných pravidel synchronizovaného zdroje, jak bylo popsáno výše).

V případě, že dojde k desynchronizaci, na konci nasazení uživatel obdrží WARNING s odpovídající zprávou a patchem, který je třeba aplikovat, aby se zdroj dostal do synchronizovaného stavu. Tento patch se také zapisuje do speciální anotace werf.io/repair-patch. Předpokládá se, že uživatel ručně sám aplikuje tento patch: werf jej principiálně aplikovat nebude.

Generování repair-patchů je dočasné opatření, které umožňuje v praxi vyzkoušet vytváření patchů podle principu 3-way-merge, ale automaticky tyto patche neuplatňovat. V současné době je tento pracovní režim ve výchozím nastavení zapnut.

3-way-merge patch pouze pro nové release

Od 1. prosince 2019 beta- a alpha-verze werf začínají ve výchozím nastavení používat plnohodnotné 3-way-merge-patche pro aplikaci změn pouze pro nové releasy Helm, které jsou nasazovány prostřednictvím werf. Již existující releasy budou pokračovat v používání přístupu s 2-way-merge + repair-patchy.

Tento pracovní režim lze výslovně aktivovat nastavením WERF_THREE_WAY_MERGE_MODE=onlyNewReleases už nyní.

Poznámka: funkce se objevovala v werf během několika releasů: v alpha kanálu byla připravena od verze v1.0.5-alpha.19, a v beta kanálu — od v1.0.4-beta.20.

3-way-merge patch pro všechny releasy

Od 15. prosince 2019 beta a alpha verze werf začnou používat plnohodnotné 3-way-merge patche pro aplikaci změn pro všechny verze.

Tento pracovní režim lze výslovně aktivovat nastavením WERF_THREE_WAY_MERGE_MODE=enabled už nyní.

Jak to bude s automěřením zdrojů?

V Kubernetes existují 2 typy automěření: HPA (horizontální) a VPA (vertikální).

Horizontální automaticky vybírá počet replik, vertikální — množství zdrojů. Jak počet replik, tak požadavky na zdroje jsou uvedeny v manifestu zdroje (viz spec.replicas nebo spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory a další).

Problém: pokud si uživatel nakonfiguruje zdroj v chartu tak, že v něm budou uvedeny konkrétní hodnoty pro zdroje nebo repliky, a pro tento zdroj budou povoleni autoscaleři, pak při každém nasazení werf resetuje tyto hodnoty na to, co je zapsáno v manifestu chartu.

Existují dvě řešení pro tento problém. Nejprve je nejlepší se zřeknout jasného uvedení automatizovaně měřitelných hodnot v manifestu chartu. Pokud tento variant z jakýchkoli důvodů není vhodný (například protože je v chartu pohodlné nastavit počáteční limity zdrojů a počet replik), werf nabízí následující anotace:

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

Pokud je taková anotace přítomna, werf nebude resetovat odpovídající hodnoty při každém nasazení, ale pouze je nastaví při prvotním vytvoření zdroje.

Více informací — viz dokumentace projektu k HPA a VPA.

Zakázat používání 3-way-merge patch

Uživatel může prozatím zakázat používání nových patchů v werf pomocí proměnné prostředí WERF_THREE_WAY_MERGE_MODE=disabled. Nicméně od 1. března 2020 tento zákaz přestane platit a bude možné používat pouze 3-way-merge patche.

Adopce zdrojů v werf

Osvojení metody aplikace změn 3-way-merge patche nám umožnilo okamžitě implementovat funkci adopce existujících zdrojů v clusterech do Helm release.

Helm 2 má problém: nelze přidat do manifestů chartu zdroj, který již existuje v clusteru, bez jeho kompletního znovu vytvoření (viz #6031, #3275). Naučili jsme werf přijímat existující zdroje do release. K tomu je potřeba nastavit na aktuální verzi zdroje z běžícího clustera anotaci (například pomocí kubectl edit):

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

Nyní je třeba popsat zdroj v chartě a při příštím nasazení s werf releasem s odpovídajícím názvem bude existující zdroj přijat do tohoto releasu a zůstane pod jeho správou. Navíc, během procesu přijetí zdroje do releasu werf přivede aktuální stav zdroje z funkčního clusteru do stavu, popsaného v chartě, pomocí stejných patchů 3-way-merge a pravidel synchronizovaného zdroje.

Poznámka: nastavení WERF_THREE_WAY_MERGE_MODE nemá vliv na adopci zdrojů — v případě adopce se vždy používá patch 3-way-merge.

Podrobnosti — v dokumentaci.

Závěry a další plány

Doufám, že po tomto článku je jasnější, co jsou 3-way-merge patche a proč k nim došlo. Z praktického hlediska vývoje projektu werf se jejich implementace stala dalším krokem směrem k vylepšení nasazení podobného Helm. Nyní lze zapomenout na problémy se synchronizací konfigurace, které často nastávaly při používání Helm 2. Současně byla přidána nová užitečná funkce adopce již stažených Kubernetes zdrojů do Helm releasu.

V nasazení podobném Helmu stále zůstávají některé problémy a potíže, jako je používání Go šablon, a budeme je i nadále řešit.

Informace o metodách aktualizace zdrojů a adopci lze také nalézt na této stránce dokumentace.

Helm 3

Samostatné zmínky si zaslouží nově vydaná doslova před několika dny nová major verze Helmu — v3, — která také používá 3-way-merge patche a zbavuje se Tillera. Nová verze Helmu vyžaduje migraci již existující instalace, které je třeba převést do nového formátu uložení releasů.

Werf se z jeho strany prozatím již zbavil používání Tillera, přešel na 3-way-merge a přidal mnoho dalších, přičemž zůstal kompatibilní s již existujícími instalacemi na Helmu 2 (není třeba vykonávat žádné migrační skripty). Takže, dokud werf nepřejde na Helm 3, uživatelé werf neztrácejí hlavní výhody Helmu 3 ve srovnání s Helm 2 (ty jsou také přítomny ve werfu).

Nicméně, přechod werfu na kódovou základnu Helmu 3 je nevyhnutelný a uskuteční se v blízké budoucnosti. Předpokládá se, že bude werf 1.1 nebo werf 1.2 (v současnosti je hlavní verzí werfu — 1.0; podrobnosti o verzi werfu viz zde). Během této doby stihne Helm 3 stabilizovat.

P.S.

Přečtěte si také náš blog:

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster