3-vejs sammensmeltning i werf: implementering i Kubernetes med Helm "på steroider"

Det, vi (og ikke kun vi) har ventet længe på, er sket: werf, vores Open Source-værktøj til opbygning af applikationer og levering til Kubernetes, understøtter nu anvendelse af ændringer ved hjælp af 3-way-merge-patches! Derudover er det blevet muligt at adoptere eksisterende K8s-ressourcer i Helm-udgivelser uden at skulle genoprette disse ressourcer.

3-vejs sammensmeltning i werf: implementering i Kubernetes med Helm "på steroider"

Kort sagt, så sætter vi WERF_THREE_WAY_MERGE=enabled — og får en deploy "som i kubectl apply", som er kompatibel med eksisterende installationer på Helm 2, og lidt mere.

Men lad os starte med teorien: hvad er 3-way-merge-patches overhovedet, hvordan kom folk på idéen med at generere dem, og hvorfor er de vigtige i CI/CD-processer med infrastruktur baseret på Kubernetes? Herefter vil vi se på, hvad 3-way-merge i werf indebærer, hvilke standardindstillinger der bruges, og hvordan man styrer dem.

Hvad er et 3-way-merge-patch?

Lad os starte med opgaven at implementere ressourcer, der er beskrevet i YAML-manifester, i Kubernetes.

For at arbejde med ressourcer tilbyder Kubernetes API de grundlæggende operationer: create, patch, replace og delete. Det forventes, at disse anvendes til at konstruere en behagelig kontinuerlig implementering af ressourcer i klyngen. Hvordan?

Imperative kubectl-kommandoer

Den første tilgang til at styre objekter i Kubernetes er at bruge imperative kubectl-kommandoer til at oprette, ændre og slette disse objekter. Kort sagt:

  • med kommandoen kubectl run kan bruges til at starte en Deployment eller Job:
    kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE
  • med kommandoen kubectl scale — ændre antallet af replikaer:
    kubectl scale --replicas=3 deployment/mysql
  • osv.

Denne tilgang kan virke praktisk ved første øjekast. Men der er problemer:

  1. Det er svært at automatisere.
  2. siger spejle konfigurationen i Git? Hvordan foretager man review af ændringer, der sker med klyngen?
  3. Hvordan sikrer man reproducerbarhed af konfigurationen ved genstart?
  4. …

Det står klart, at denne tilgang hænger dårligt sammen med lagring af koden sammen med applikationen og infrastruktur som kode (IaC; eller endda GitOps som et mere moderne alternativ, der vinder popularitet i Kubernetes-økosystemet). Derfor fik disse kommandoer i kubectl ikke yderligere udvikling.

Operationer: create, get, replace og delete

Med den første oprettelse er det hele simpelt: vi sender manifestet til operationen create til kube API, og ressourcen er oprettet. YAML-repræsentationen af manifestet kan opbevares i Git, og til oprettelse kan vi bruge kommandoen kubectl create -f manifest.yaml.

Med sletning er også simpel: vi indsætter det samme manifest.yaml fra Git i kommandoen kubectl delete -f manifest.yaml.

Operation replace gør det muligt at helt erstatte en ressourcekonfiguration med en ny, uden at genskabe ressourcen. Det betyder, at før man ændrer ressourcen, giver det mening at anmode om den nuværende version med en operation get, ændre den og opdatere med en operation replace. I kube apiserver er der indbygget optimistic locking , og hvis objektet er ændret efter operationen get , vil operationen replace ikke gå igennem.

For at gemme konfigurationen i Git og opdatere ved hjælp af replace, skal du udføre operationen get, merge konfigurationen fra Git med det, vi har modtaget, og udføre replace. Standard kubectl tillader kun at bruge kommandoen kubectl replace -f manifest.yaml, hvor manifest.yaml — allerede helt forberedt (i vores tilfælde — merged) manifest, som skal installeres. Det vil sige, at brugeren skal implementere merge af manifester, og det er en kompliceret opgave...

Det skal også nævnes, at selvom manifest.yaml opbevares i Git, kan vi ikke vide på forhånd, om vi skal oprette objektet eller opdatere det — det skal gøres af brugerdefineret software.

I alt: kan vi bygge en kontinuerlig implementering kun ved hjælp af create, replace og delete, sikrende opbevaring af infrastrukturens konfiguration i Git sammen med koden og en praktisk CI/CD?

I princippet kan vi... For dette skal vi implementere en merge operation af manifester og en eller anden indpakning, der:

  • kontrollerer eksistensen af objektet i klyngen,
  • udfører den første oprettelse af ressourcen,
  • opdaterer eller sletter den.

Ved opdatering skal det tages i betragtning, at ressourcen kan være ændret siden sidst get og automatisk håndtere optimistisk låsning — forsøge at opdatere igen.

Men hvorfor opfinde hjulet igen, når kube-apiserver tilbyder en anden måde at opdatere ressourcer: operationen patch, som fjerner nogle af de beskrevne problemer fra brugeren?

Patch

Så er vi nået til patches.

Patches er den primære måde at anvende ændringer på eksisterende objekter i Kubernetes. Operationen patch funktionerer således, at:

  • brugeren af kube-apiserver skal sende et patch i JSON-format og angive objektet,
  • og apiserver vil selv finde ud af den nuværende tilstand af objektet og bringe det til den ønskede tilstand.

Optimistic locking er ikke nødvendig i dette tilfælde. Denne operation er mere deklarativ i forhold til replace, selvom det ved første øjekast kan virke omvendt.

Dermed:

  • ved hjælp af operationen create opretter vi et objekt baseret på manifestet fra Git,
  • ved hjælp af slet — sletter, hvis objektet ikke længere er nødvendigt,
  • ved hjælp af patch — ændrer objektet, så det svarer til det beskrevne i Git.

Men for at gøre dette, er det nødvendigt at oprette den rigtige patch!

Sådan fungerer patches i Helm 2: 2-way-merge

Ved første installation af en Helm-udgivelse udfører den en operation create for chartets ressourcer.

Ved opdatering af Helm-udgivelsen:

  • beregner den patchen mellem versionen af ressourcen fra det tidligere chart og den nuværende version af chartet,
  • og anvender denne patch.

Denne patch vil vi kalde 2-way-merge patch, fordi oprettelsen involverer 2 manifests:

  • manifestet af ressourcen fra den tidligere udgivelse,
  • manifestet af ressourcen fra den nuværende ressource.

Ved sletning kaldes operationen slet i kube apiserver for ressourcer, der blev erklæret i den tidligere udgivelse, men ikke erklæret i den nuværende.

2-way merge patch-tilgangen har et problem: det fører til desynkronisering af den faktiske tilstand af ressourcen i klyngen og manifestet i Git.

Illustration af problemet med et eksempel

  • I Git, i chartet, er der et manifest, hvor feltet billede i Deployment har værdien ubuntu:18.04.
  • Brugeren ændrede værdien af dette felt til kubectl edit ændret til ubuntu:19.04.
  • Ved re-deployment af chartet genererer Helm ikke en patch,fordi feltet billede i den tidligere udgave og i det nuværende chart er ens.
  • Efter om-deployment billede forbliver ubuntu:19.04, selvom det i chartet står ubuntu:18.04.

Vi har fået desynkronisering og mistet deklarativitet.

Hvad er en synkroniseret ressource?

Generelt set, fuld overensstemmelse mellem ressourcens manifest i den kørende klynge og manifestet fra Git er umuligt at opnå. Fordi der i det faktiske manifest kan være systemanmodninger/labels, yderligere containere og andre data, der dynamisk tilføjes og fjernes fra ressourcen af bestemte controllere. Disse data ønsker vi ikke at gemme i Git. Men vi ønsker, at de felter, vi specifikt angav i Git, får de tilsvarende værdier ved implementeringen.

Det resulterer i en generel regel for synkroniseret ressource: ved implementering af ressourcen kan man kun ændre eller fjerne de felter, der tydeligt er angivet i manifestet fra Git (eller som blev angivet i den tidligere version, men nu er fjernet).

3-way-merge patch

Den grundlæggende idé 3-way-merge patch: genererer en patch mellem den senest anvendte version af manifestet fra Git og den målrettede version af manifestet fra Git med hensyn til den nuværende version af manifestet fra den kørende klynge. Den endelige patch skal overholde synkroniseringsreglen for ressourcen:

  • nye felter, der er tilføjet til den målrettede version, tilføjes ved hjælp af patchen;
  • Tidligere eksisterende felter i den senest anvendte version, som ikke findes i målet, nulstilles ved hjælp af en patch;
  • Felter i den aktuelle version af objektet, der adskiller sig fra den målversion af manifestet, opdateres via en patch.

Det er præcist efter denne metode, at patches genereres kubectl apply:

  • Den senest anvendte version af manifestet gemmes i annotationen af selve objektet,
  • målet tages fra den angivne YAML-fil,
  • den aktuelle tages fra det fungerende cluster.

Nu, når vi har forstået teorien, er det tid til at fortælle, hvad vi har gjort i werf.

Anvendelse af ændringer i werf

Tidligere brugte werf, ligesom Helm 2, 2-way-merge-patches.

Repair patch

For at skifte til den nye type patches — 3-way-merge — har vi som det første skridt indført såkaldte repair-patches.

Ved deployment bruges den standard 2-way-merge patch, men werf genererer desuden en patch, der synkroniserer den faktiske tilstand af ressourcen med det, der er skrevet i Git (denne patch oprettes ved anvendelse af de samme regler for synkroniserede ressourcer, som beskrevet ovenfor).

I tilfælde af, at der opstår en desynkronisering, modtager brugeren ved slutningen af deployment en WARNING med den relevante besked og en patch, som skal anvendes for at bringe ressourcen tilbage til en synkroniseret tilstand. Denne patch gemmes også i en speciel annotation werf.io/repair-patch. Det forventes, at brugeren selv manuelt anvender denne patch: werf vil ikke anvende den principielt.

Genereringen af repair-patches er en midlertidig foranstaltning, der gør det muligt at teste oprettelsen af patches baseret på princippet om 3-way-merge, men at anvende disse patches automatisk.

3-way-merge patch kun for nye udgivelser

Fra den 1. december 2019 begynder beta- og alpha-versioner af werf at som standard anvende fuldt ud 3-way-merge-patches til anvendelse af ændringer kun for nye Helm-udgivelser, der distribueres gennem werf. Allerede eksisterende udgivelser vil fortsætte med at anvende tilgangen med 2-way-merge + repair-patches.

Denne driftsform kan eksplicit aktiveres ved at indstille WERF_THREE_WAY_MERGE_MODE=onlyNewReleases allerede nu.

Bemærk: funktionen er kommet til werf over flere udgivelser: i alpha-kanalen blev den klar med versionen v1.0.5-alpha.19, og i beta-kanalen — med v1.0.4-beta.20.

3-way-merge patch for alle udgivelser

Fra den 15. december 2019 vil beta- og alpha-versioner af werf som standard bruge fuld 3-vejs-merge-patches til at anvende ændringer til alle udgivelser.

Denne driftsform kan eksplicit aktiveres ved at indstille WERF_THREE_WAY_MERGE_MODE=enabled allerede nu.

Hvordan håndteres autoskalering af ressourcer?

I Kubernetes findes der 2 typer af autoskalering: HPA (horisontal) og VPA (vertikal).

Horisontal vælger automatisk antallet af replikaer, mens vertikal vælger mængden af ressourcer. Både antallet af replikaer og kravene til ressourcer angives i ressourcemanifestet (se spec.replicas eller spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory og andre).

Problemet er: hvis brugeren konfigurerer ressourcen i chartet, så den indeholder bestemte værdier for ressourcer eller replikaer, og autoskalere er aktiveret for den pågældende ressource, så vil werf ved hver deployment nulstille disse værdier til dem, der er skrevet i chartmanifestet.

Der er to løsninger på problemet. For det første er det bedst at undgå at angive autoskalere værdier eksplicit i chartmanifestet. Hvis denne mulighed af en eller anden grund ikke er passende (f.eks. fordi det er praktisk at angive oprindelige ressourcebegrænsninger og antallet af replikaer i chartet), så tilbyder werf følgende annotationer:

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

Med en sådan annotation vil werf ikke nulstille de relevante værdier ved hver deployment, men kun indstille dem ved den oprindelige oprettelse af ressourcen.

For mere information – se projektets dokumentation om HPA og VPA.

Forbyd brug af 3-vejs-merge patch

Brugeren kan midlertidigt forbyde brugen af nye patches i werf ved hjælp af miljøvariablen WERF_THREE_WAY_MERGE_MODE=disabled. Men fra den 1. marts 2020 vil dette forbud ophøre med at fungere og det vil kun være muligt at bruge 3-vejs-merge-patches.

Adoption af ressourcer i werf

Implementeringen af 3-vejs-merge-patch-ændringer har gjort os i stand til straks at understøtte en funktion som adoption af eksisterende ressourcer i klyngen i Helm-udgivelser.

Helm 2 har et problem: Man kan ikke tilsætte en ressource, der allerede eksisterer i klyngen, i chartmanifestet uden at genskabe ressourcen fra bunden (se #6031, #3275). Vi har lært werf at acceptere eksisterende ressourcer i udgivelsen. For at gøre dette skal man tilføje en annotation til den nuværende version af ressourcen fra den kørende klynge (f.eks. ved hjælp af kubectl edit):

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

Nu skal ressourcen beskrives i diagrammet, og ved den næste deployment med werf af en version med det tilsvarende navn, vil den eksisterende ressourc blive optaget i denne version og forblive under dens kontrol. Desuden vil werf under optagelsen af ressourcen bringe den nuværende tilstand af ressourcen fra det fungerende cluster til den tilstand, der er beskrevet i diagrammet, ved at bruge de samme 3-way-merge-patches og reglen for synkroniserede ressourcer.

Bemærk: indstilling WERF_THREE_WAY_MERGE_MODE påvirker ikke adoption af ressourcer — ved adoption bruges der altid 3-way-merge-patch.

Detaljer findes i dokumentationen.

Konklusioner og fremtidige planer

Jeg håber, at det efter denne artikel er blevet klarere, hvad 3-way-merge-patches er, og hvorfor vi kom til dem. Set fra et praktisk perspektiv for projektudviklingen af werf er deres implementering et yderligere skridt mod forbedring af Helm-lignende deployment. Nu kan vi glemme problemerne med synkronisering af konfigurationer, som ofte opstod ved brug af Helm 2. Samtidig er der tilføjet en ny nyttig funktion til adoption af allerede downloadede Kubernetes-ressourcer i Helm-versionen.

Der er stadig nogle problemer og vanskeligheder, såsom brugen af Go-skabeloner, i Helm-lignende deployment, og vi vil fortsætte med at løse dem.

Oplysninger om metoder til opdatering af ressourcer og adoption kan også findes på denne dokumentationsside.

Helm 3

Det fortjener at blive bemærket, at den nye store version af Helm — v3, udgivet for nylig, som også bruger 3-way-merge-patches og fjerner Tiller. Den nye version af Helm kræver migration af allerede eksisterende installationer for at konvertere dem til det nye format til lagring af versioner.

Werf har på sin side allerede opgivet brugen af Tiller, skiftet til 3-way-merge og tilføjet meget mere, mens den forbliver kompatibel med allerede eksisterende installationer på Helm 2 (der skal ikke udføres migrationsscripts). Derfor, mens werf ikke er skiftet til Helm 3, mister brugerne af werf ikke de grundlæggende fordele, som Helm 3 har i forhold til Helm 2 (de er også til stede i werf).

Ikke desto mindre er skiftet af werf til Helm 3-koden uundgåeligt og vil ske i den nærmeste fremtid. Det forventes at være werf 1.1 eller werf 1.2 (for øjeblikket er hovedversionen af werf — 1.0; for mere information om versioneringsstrukturen for werf, se her). I mellemtiden vil Helm 3 få tid til at stabilisere sig.

P.S.

Læs også på vores blog:

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster