3-way merge in werf: deploy in Kubernetes met Helm "op steroïden"

Het is gebeurd waar wij (en niet alleen wij) al lang op hebben gewacht: werf, onze Open Source-tool voor het bouwen van applicaties en hun levering aan Kubernetes ondersteunt nu het toepassen van wijzigingen met behulp van 3-way-merge-patches! Daarnaast is het nu mogelijk om bestaande K8s-resources in Helm-releases te adopteren zonder deze resources opnieuw te creëren.

3-way merge in werf: deploy in Kubernetes met Helm "op steroïden"

Een korte samenvatting: we zetten WERF_THREE_WAY_MERGE=enabled — we krijgen een uitrol "zoals in kubectl apply", compatibel met bestaande installaties op Helm 2 en zelfs iets meer.

Maar laten we beginnen met de theorie: wat zijn 3-way-merge-patches, hoe zijn mensen tot deze benadering gekomen en waarom zijn ze belangrijk in CI/CD-processen met een infrastructuur gebaseerd op Kubernetes? En daarna bekijken we wat 3-way-merge in werf inhoudt, welke modi standaard worden gebruikt en hoe je deze kunt beheren.

Wat is een 3-way-merge-patch?

Laten we beginnen met de taak om resources, beschreven in YAML-manifesten, in Kubernetes uit te rollen.

Voor het werken met resources biedt de Kubernetes API de volgende basisbewerkingen: create, patch, replace en delete. Het idee is dat je hiermee een handige continue uitrol van resources naar het cluster kunt construeren. Hoe?

Imperatieve kubectl-commando's

De eerste benadering voor het beheren van objecten in Kubernetes is het gebruik van imperatieve kubectl-commando's voor het creëren, wijzigen en verwijderen van deze objecten. In eenvoudige bewoordingen:

  • met het commando kubectl run je kunt een Deployment of Job starten:
    kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE
  • met het commando kubectl scale — wijzig het aantal replicas:
    kubectl scale --replicas=3 deployment/mysql
  • enzovoorts.

Deze aanpak lijkt misschien handig op het eerst gezicht. Maar er zijn problemen:

  1. Het is moeilijk te automatiseren.
  2. Zoals de configuratie in Git te reflecteren? Hoe maak je een review van de wijzigingen die plaatsvinden in het cluster?
  3. Hoe zorg je voor reproduceerbaarheid van de configuratie bij herstart?
  4. …

Het is duidelijk dat deze aanpak slecht past bij het opslaan met de code van de applicatie en infrastructuur als code (IaC; of zelfs GitOps als een modernere variant die steeds populairder wordt in de Kubernetes-ecosysteem). Daarom heeft deze commando's in kubectl niet verder ontwikkeld.

De operaties create, get, replace en delete

Met de primaire creatie is alles eenvoudig: we sturen het manifest naar de operatie create bij de kube api en de resource is aangemaakt. De YAML-representatie van het manifest kan in Git worden opgeslagen, en om te creëren — gebruik het commando kubectl create -f manifest.yaml.

met verwijderen is ook eenvoudig: we gebruiken hetzelfde manifest.yaml uit Git in het commando kubectl delete -f manifest.yaml.

Operatie replace stelt in staat om de configuratie van een resource volledig te vervangen door een nieuwe, zonder de resource opnieuw aan te maken. Dit betekent dat het logisch is om de huidige versie op te vragen voordat je een wijziging aan de resource aanbrengt met de operatie krijgen, deze te wijzigen en de operatie bij te werken replace. In kube apiserver is ingebouwd optimistische locking en, als het object na de operatie krijgen is veranderd, dan zal de operatie replace niet doorgaan.

Om de configuratie in Git op te slaan en deze bij te werken met behulp van replace, moet je de operatie krijgen, de configuratie uit Git samenvoegen met wat we hebben ontvangen, en uitvoeren replace. Standaard laat kubectl alleen de commandoregel gebruiken kubectl replace -f manifest.yaml, waar manifest.yaml — al een volledig voorbereide (in ons geval — samengevoegde) manifest die moet worden geïnstalleerd. Dit betekent dat de gebruiker de samenvoeging van manifesten moet implementeren, wat geen triviale taak is...

Het moet ook worden opgemerkt dat hoewel manifest.yaml het in Git is opgeslagen, we niet vooraf kunnen weten of een object moet worden gemaakt of bijgewerkt — dit moet door de gebruikerssoftware worden gedaan.

Kortom: kunnen we een continue uitrol bouwen alleen met behulp van create, replace en delete, waarbij we de infrastructuurconfiguratie in Git opslaan samen met de code en een handige CI/CD bieden?

In principe kunnen we dat… Hiervoor is het nodig om de operatie samenvoegen van manifesten en een soort omhulsel te implementeren dat:

  • de aanwezigheid van het object in de cluster controleert,
  • de initiële creatie van de resource uitvoert,
  • deze bijwerkt of verwijdert.

Bij een update moet rekening worden gehouden met het feit dat de resource kan zijn veranderd sinds de laatste krijgen en automatisch de situatie van optimistische locking moet verwerken — herhaaldelijke pogingen tot een update uitvoeren.

Maar waarom het wiel opnieuw uitvinden, als kube-apiserver een andere manier biedt om resources bij te werken: de operatie patch, die enkele van de hierboven beschreven problemen voor de gebruiker oplost?

Patch

Hier zijn we dan aangekomen bij patches.

Patches zijn de belangrijkste manier om wijzigingen aan te brengen in bestaande objecten in Kubernetes. De operatie patch werkt zodanig dat:

  • de gebruiker van kube-apiserver een patch in JSON-vorm moet versturen en het object moet opgeven,
  • en de apiserver zelf de huidige staat van het object zal analyseren en het naar de vereiste toestand zal brengen.

Optimistische locking is in dit geval niet vereist. Deze operatie is meer declaratief in vergelijking met replace, hoewel het in eerste instantie misschien anders lijkt.

Dus:

  • met de operatie create maken we een object aan volgens het manifest uit Git,
  • met behulp van Verwijder afbeeldingen — verwijderen we het, als het object niet meer nodig is,
  • met behulp van patch — wijzigen we het object en brengen we het in de staat zoals beschreven in Git.

Echter, om dit te doen, moet je de juiste patch maken!

Hoe patches werken in Helm 2: 2-way-merge

Bij de eerste installatie van de Helm-release voert het create een operatie uit voor de resources van de chart.

Bij het updaten van de Helm-release:

  • wordt de patch berekend tussen de versie van de resource uit de vorige chart en de huidige versie van de chart,
  • deze patch wordt toegepast.

Deze patch noemen we 2-way-merge patch, omdat bij het maken ervan 2 manifesten betrokken zijn:

  • het manifest van de resource uit de vorige release,
  • het manifest van de resource uit de huidige versie.

Bij verwijdering wordt de operatie Verwijder afbeeldingen in kube apiserver aangeroepen voor resources die zijn verklaard in de vorige release, maar niet in de huidige.

De aanpak van de 2 way merge patch heeft een probleem: het leidt tot desynchronisatie van de werkelijke toestand van de resource in de cluster en het manifest in Git.

Een illustratie van het probleem aan de hand van

  • In Git wordt in de chart een manifest opgeslagen waarin het veld afbeelding bij de Deployment is ingesteld op ubuntu:18.04.
  • De gebruiker via kubectl edit veranderde deze waarde naar ubuntu:19.04.
  • Bij het opnieuw deployen van de Helm-chart wordt er geen patch gegenereerd, omdat het veld afbeelding in de vorige versie van de release en in de huidige chart hetzelfde is.
  • Na de herdeploy blijft er afbeelding , hoewel in de chart staat ubuntu:19.04We hebben desynchronisatie gekregen en de declarativiteit verloren. ubuntu:18.04.

Wat is een gesynchroniseerde resource?

In het algemeen,

het volledige overeenkomst van het manifest van de resource in de actieve cluster en het manifest uit Git is onmogelijk te verkrijgen. Omdat in het echte manifest mogelijk operationele annotaties/labels, extra containers en andere gegevens aanwezig zijn die dynamisch door sommige controllers aan de resource worden toegevoegd of verwijderd. Deze gegevens willen en kunnen we niet in Git houden. Echter willen we dat de velden die we expliciet in Git hebben opgegeven, de overeenkomstige waarden aannemen bij de uitrol. Het resultaat is een algemene

regel voor gesynchroniseerde resources: bij de uitrol van de resource kunnen alleen de velden worden gewijzigd of verwijderd die expliciet in het manifest uit Git zijn vermeld (of die in de vorige versie zijn vermeld maar nu zijn verwijderd).3-way-merge patch

: genereert een patch tussen de laatste toegepaste versie van het manifest uit Git en de doelversie van het manifest uit Git, rekening houdend met de huidige versie van het manifest uit de actieve cluster. De uiteindelijke patch moet voldoen aan de regel voor gesynchroniseerde resources:

Het belangrijkste idee : genereert een patch tussen de laatste toegepaste versie van het manifest uit Git en de doelversie van het manifest uit Git, rekening houdend met de huidige versie van het manifest uit de actieve cluster. De uiteindelijke patch moet voldoen aan de regel voor gesynchroniseerde resources:nieuwe velden die aan de doelversie zijn toegevoegd, worden met behulp van de patch toegevoegd;

  • nieuwe velden die aan de doelversie zijn toegevoegd, worden toegevoegd via een patch;
  • Veld die eerder bestonden in de laatst toegepaste versie en die niet bestaan in de doelversie worden op nul gezet met behulp van een patch;
  • Velden in de huidige versie van het object die verschillen van de doelversie van het manifest worden bijgewerkt met behulp van een patch.

Op deze manier genereert de patch kubectl apply:

  • de laatst toegepaste versie van het manifest wordt opgeslagen in de annotatie van het object zelf,
  • de doelversie wordt uit het opgegeven YAML-bestand gehaald,
  • de huidige versie komt uit de actieve cluster.

Nu we de theorie hebben begrepen, is het tijd om te vertellen wat we in werf hebben gedaan.

Toepassing van wijzigingen in werf

Eerder gebruikte werf, net als Helm 2, 2-way-merge patches.

Repair patch

Om over te schakelen naar het nieuwe type patches - 3-way-merge - hebben we als eerste stap de zogenaamde repair-patches geïntroduceerd.

Bij het deployen wordt de standaard 2-way-merge patch gebruikt, maar werf genereert daarnaast een patch die de werkelijke staat van de bron in sync brengt met wat in Git is geschreven (deze patch wordt gemaakt met gebruik van dezelfde regels voor gesynchroniseerde bronnen die hierboven zijn beschreven).

In het geval van desynchronisatie ontvangt de gebruiker aan het einde van de deployment een WAARSCHUWING met het bijbehorende bericht en een patch die moet worden toegepast om de bron in een gesynchroniseerde staat te brengen. Deze patch wordt ook geregistreerd in een speciale annotatie werf.io/repair-patch. Er wordt aangenomen dat de gebruiker handmatig zelf deze patch toepast: werf zal dit principieel niet doen.

Het genereren van repair-patches is een tijdelijke maatregel die het mogelijk maakt om het maken van patches volgens het 3-way-merge principe uit te testen, maar deze patches automatisch niet toe te passen. Momenteel is deze werkwijze standaard ingeschakeld.

3-way-merge patch alleen voor nieuwe releases

Vanaf 1 december 2019 beginnen beta- en alpha-versies van werf standaard volwaardige 3-way-merge patches te gebruiken voor het aanbrengen van wijzigingen alleen voor nieuwe Helm-releases die via werf worden uitgerold. Bestaande releases blijven de aanpak met 2-way-merge + repair-patches gebruiken.

Deze werkwijze kan expliciet worden ingeschakeld door de instelling WERF_THREE_WAY_MERGE_MODE=onlyNewReleases al nu beschikbaar.

Opmerking: de functie is door verschillende releases in werf beschikbaar gesteld: in de alpha-channel is het beschikbaar sinds versie v1.0.5-alpha.19, en in de beta-channel sinds v1.0.4-beta.20.

3-way-merge patch voor alle releases

Vanaf 15 december 2019 beginnen beta- en alpha-versies van werf standaard volledige 3-way-merge patches te gebruiken om wijzigingen aan te brengen voor alle releases.

Deze werkwijze kan expliciet worden ingeschakeld door de instelling WERF_THREE_WAY_MERGE_MODE=enabled al nu beschikbaar.

Hoe om te gaan met de automatische schaling van middelen?

In Kubernetes zijn er 2 typen automatische schaling: HPA (horizontaal) en VPA (verticaal).

Horizontaal kiest automatisch het aantal replica's, verticaal de hoeveelheid middelen. Zowel het aantal replica's als de eisen voor middelen worden gespecificeerd in de manifest van de bron (zie spec.replicas of spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory en andere).

Probleem: als een gebruiker de bron in de chart zo configureert dat er specifieke waarden voor middelen of replica's worden opgegeven en autoscalers zijn ingeschakeld voor deze bron, dan zal werf bij elke deployment deze waarden resetten naar wat er in de manifest van de chart is vastgelegd.

Er zijn twee oplossingen voor het probleem. Het is het beste om expliciet vermelden van autoschaalwaarden in de manifest van de chart te vermijden. Als deze optie om een of andere reden niet geschikt is (bijvoorbeeld omdat het handig is om de initiële beperkingen voor middelen en het aantal replica's in de chart op te geven), dan biedt werf de volgende annotaties aan:

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

Bij het hebben van een dergelijke annotatie zal werf de bijbehorende waarden niet bij elke deployment resetten, maar deze alleen instellen bij de eerste creatie van de bron.

Zie voor meer informatie de projectdocumentatie over HPA en VPA.

Gebruik van 3-way-merge patch verbieden

De gebruiker kan voorlopig het gebruik van nieuwe patches in werf verbieden met behulp van de omgevingsvariabele WERF_THREE_WAY_MERGE_MODE=disabled. Echter, vanaf 1 maart 2020 zal dit verbod niet meer van kracht zijn en zal alleen het gebruik van 3-way-merge patches mogelijk zijn.

Adoptie van middelen in werf

De implementatie van de methode voor het aanbrengen van wijzigingen via 3-way-merge patches heeft het ons mogelijk gemaakt om direct een functie te implementeren zoals de adoptie van bestaande middelen in de cluster naar de Helm-release.

Helm 2 heeft een probleem: het is niet mogelijk om een bron die al in de cluster bestaat, aan de manifeste van de chart toe te voegen zonder deze bron vanaf nul opnieuw te creëren (zie #6031, #3275). We hebben werf geleerd om bestaande bronnen in de release te accepteren. Hiervoor moet aan de huidige versie van de bron uit de werkende cluster een annotatie worden toegevoegd (bijvoorbeeld met behulp van kubectl edit):

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

Nu moet de bron in de chart worden beschreven en bij de volgende uitrol van de werf met de bijbehorende naam zal de bestaande bron in deze release worden opgenomen en onder zijn beheer blijven. Bovendien zal werf, tijdens het opnemen van de bron in de release, de huidige staat van de bron uit het werkende cluster naar de staat, beschreven in de chart, brengen, gebruikmakend van dezelfde 3-way-merge-patches en de regel van de gesynchroniseerde bron.

Opmerking: configuratie WERF_THREE_WAY_MERGE_MODE heeft geen invloed op de adoptie van bronnen — bij adoptie wordt altijd een 3-way-merge-patch gebruikt.

Details zijn te vinden in de documentatie.

Conclusies en toekomstige plannen

Ik hoop dat na dit artikel duidelijker is geworden wat 3-way-merge-patches zijn en waarom we hiervoor hebben gekozen. Vanuit praktisch oogpunt is de implementatie hiervan in het werf-project een stap voorwaarts voor het verbeteren van de Helm-achtige uitrol. We kunnen nu de synchronisatieproblemen met configuratie vergeten, die vaak voorkwamen bij het gebruik van Helm 2. Tegelijkertijd is er een nieuwe handige functie toegevoegd voor de adoptie van al gedownloade Kubernetes-bronnen in de Helm-release.

Bij de Helm-achtige uitrol blijven er echter nog steeds enkele problemen en moeilijkheden bestaan, zoals het gebruik van Go-templates, en we zullen deze blijven oplossen.

Informatie over de methoden voor het bijwerken van bronnen en adoptie is ook te vinden op deze documentatiepagina.

Helm 3

Een aparte opmerking waard is de recentelijk uitgebrachte letterlijk enkele dagen geleden nieuwe majeure versie van Helm — v3, — die ook 3-way-merge-patches gebruikt en zich ontdoet van Tiller. De nieuwe versie van Helm vereist migratie van bestaande installaties om ze om te zetten naar het nieuwe opslagformaat voor releases.

Werf heeft op dit moment al geen gebruik meer van Tiller en is overgestapt op 3-way-merge en heeft veel meer toegevoegd, terwijl het compatibel is gebleven met de bestaande installaties op Helm 2 (er hoeven geen migratiescripts te worden uitgevoerd). Daarom, zolang werf niet is overgestapt op Helm 3, verliezen werf-gebruikers geen van de belangrijke voordelen van Helm 3 ten opzichte van Helm 2 (deze zijn er ook in werf).

Desondanks is de overstap van werf naar de codebasis van Helm 3 onvermijdelijk en zal deze in de nabije toekomst plaatsvinden. Naar verwachting zal dit werf 1.1 of werf 1.2 zijn (op dit moment is de hoofdversie van werf — 1.0; voor meer informatie over de versie-structuur van werf zie hier). In de tussentijd zal Helm 3 stabieler worden.

P.S.

Lees ook op onze blog:

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster