3-Wege-Zusammenführung in werf: Deployment in Kubernetes mit Helm «auf Steroiden»

Es ist passiert, worauf wir (und nicht nur wir) lange gewartet haben: werf, unser Open Source-Tool zum Erstellen und Bereitstellen von Anwendungen in Kubernetes unterstützt jetzt die Anwendung von Änderungen mit 3-Wege-Zusammenführungs-Patches! Darüber hinaus gibt es jetzt die Möglichkeit, bestehende K8s-Ressourcen in Helm-Releases zu übernehmen, ohne diese Ressourcen neu erstellen zu müssen.

3-Wege-Zusammenführung in werf: Deployment in Kubernetes mit Helm «auf Steroiden»

Kurz gesagt, wir setzen WERF_THREE_WAY_MERGE=enabled — und erhalten ein Deployment «wie in kubectl apply», das mit bestehenden Installationen auf Helm 2 und sogar etwas mehr kompatibel ist.

Aber lass uns mit der Theorie beginnen: Was genau sind 3-Wege-Zusammenführungs-Patches, wie sind die Leute zu diesem Ansatz gekommen und warum sind sie wichtig in CI/CD-Prozessen mit Kubernetes-basierter Infrastruktur? Danach schauen wir uns an, was die 3-Wege-Zusammenführung in werf ist, welche Modi standardmäßig verwendet werden und wie man damit umgeht.

Was ist ein 3-Wege-Zusammenführungs-Patch?

Beginnen wir mit der Aufgabe, Ressourcen, die in YAML-Manifests beschrieben sind, in Kubernetes bereitzustellen.

Um mit Ressourcen zu arbeiten, bietet die Kubernetes-API die grundlegenden Operationen: create, patch, replace und delete. Diese sollen dazu dienen, einen kontinuierlichen Rollout von Ressourcen in den Cluster bequem zu gestalten. Wie?

Imperative Befehle von kubectl

Der erste Ansatz zur Verwaltung von Objekten in Kubernetes ist die Verwendung von imperativen kubectl-Befehlen zum Erstellen, Ändern und Löschen dieser Objekte. Einfacher ausgedrückt:

  • mit dem Befehl kubectl run hiermit können Sie ein Deployment oder Job starten:
    kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE
  • mit dem Befehl kubectl scale – ändern Sie die Anzahl der Replikate:
    kubectl scale --replicas=3 deployment/mysql
  • usw.

Dieser Ansatz scheint auf den ersten Blick praktisch zu sein. Es gibt jedoch Probleme:

  1. Es ist schwierig, diesen Prozess zu automatisieren. Er überwacht die Historie der CPU- und Speicherauslastung und passt neue Requests und Limits basierend auf diesen Informationen an..
  2. Wie die Konfiguration in Git zu reflektieren? Wie führen Sie eine Überprüfung der Änderungen durch, die am Cluster vorgenommen werden?
  3. Wie stellen Sie sicher, dass die Konfiguration bei einem Neustart reproduzierbar ist?

Es ist klar, dass dieser Ansatz schlecht mit der Speicherung von Anwendungscode und Infrastructure as Code (IaC; oder sogar GitOps als die modernere Variante, die in der Kubernetes-Ökosystem an Popularität gewinnt) kombiniert wird. Daher haben diese Befehle in kubectl keine weitere Entwicklung erfahren.

Operationen create, get, replace und delete

Mit der ersten Erstellung ist alles einfach: wir senden das Manifest an die Operation create an die kube API und die Ressource wird erstellt. Die YAML-Darstellung des Manifests kann in Git gespeichert werden, und zur Erstellung verwenden wir den Befehl kubectl create -f manifest.yaml.

E Löschung ist ebenfalls einfach: wir setzen dasselbe manifest.yaml ein aus Git in das Team kubectl delete -f manifest.yaml.

Operation ersetzen ermöglicht es, die Konfiguration einer Ressource vollständig durch eine neue zu ersetzen, ohne die Ressource neu zu erstellen. Das bedeutet, dass es sinnvoll ist, vor einer Änderung an der Ressource die aktuelle Version mit einem get, sie zu ändern und mit einem ersetzenzu aktualisieren. Im Kube API-Server ist optimistische Sperrung integriert, und wenn sich das Objekt nach der Operation get geändert hat, schlägt die Operation ersetzen fehl.

Um die Konfiguration in Git zu speichern und mit replace zu aktualisieren, muss man die Operation get, die Konfiguration aus Git mit dem, was wir erhalten haben, mergen und ersetzenausführen. Standardmäßig erlaubt kubectl nur die Verwendung des Befehls kubectl replace -f manifest.yaml, wobei manifest.yaml ein — bereits vollständig vorbereiteter (in unserem Fall — gemergter) Manifest, der installiert werden muss. Das bedeutet, dass der Benutzer das Merging der Manifeste durchführen muss, was keine triviale Aufgabe ist…

Es ist auch erwähnenswert, dass, obwohl manifest.yaml ein in Git gespeichert wird, wir im Voraus nicht wissen können, ob wir ein Objekt erstellen oder aktualisieren müssen — das muss die Benutzeranwendung übernehmen.

Insgesamt: können wir eine kontinuierliche Ausrollung nur mit create, replace und delete erreichen, indem wir die Infrastrukturkonfiguration zusammen mit dem Code und einem benutzerfreundlichen CI/CD in Git speichern?

Im Prinzip können wir... Dafür muss eine Merge-Operation durchgeführt werden für die Manifeste und eine Art Wrapper, die:

  • die Existenz des Objekts im Cluster überprüft,
  • die Erstcreation der Ressource durchführt,
  • es aktualisiert oder löscht.

Bei der Aktualisierung muss man berücksichtigen, dass sich die Ressource seit dem letzten Änderungsvorgang verändert haben könnte get und automatisch den Fall des optimistischen Lockings behandeln — es müssen Wiederholungsversuche zur Aktualisierung gemacht werden.

Aber warum das Rad neu erfinden, wenn der kube-apiserver einen anderen Weg anbietet, um Ressourcen zu aktualisieren: die Operation patch, die dem Benutzer einige der beschriebenen Probleme abnimmt?

Patch

Hier sind wir also bei den Patches angekommen.

Patches sind der Hauptweg, um Änderungen an bestehenden Objekten in Kubernetes anzuwenden. Die Operation patch funktioniert so, dass:

  • der Benutzer des kube-apiserver ein Patch im JSON-Format senden und das Objekt angeben muss,
  • während sich der apiserver selbst um den aktuellen Zustand des Objekts kümmert und es in den gewünschten Zustand bringt.

Optimistisches Locking ist in diesem Fall nicht erforderlich. Diese Operation ist deklarativer als replace, auch wenn es zunächst anders erscheinen mag.

Somit:

  • mit Hilfe der Operation create erstellen wir ein Objekt aus dem Manifest von Git,
  • mit Hilfe von löschen — entfernt, wenn das Objekt nicht mehr benötigt wird,
  • mit Hilfe von patch — ändern Sie das Objekt, um es in den im Git beschriebenen Zustand zu bringen.

Um dies zu tun, ist es jedoch notwendig, einen korrekten Patch zu erstellen!

So funktionieren Patches in Helm 2: 2-Wege-Merge

Bei der ersten Installation des Releases führt Helm die Operation aus, create für die Ressourcen des Charts.

Beim Aktualisieren des Releases: Helm betrachtet für jede Ressource,

  • den Patch zwischen der Version der Ressource aus dem vorherigen Chart und der aktuellen Version des Charts,
  • und wendet diesen Patch an.

So einen Patch nennen wir 2-Wege-Merge-Patch, weil bei seiner Erstellung zwei Manifeste beteiligt sind:

  • das Manifest der Ressource aus dem vorherigen Release,
  • das Manifest der Ressource aus der aktuellen Ressource.

Bei der Löschung wird die Operation löschen im kube-apiserver für Ressourcen aufgerufen, die im vorherigen Release deklariert wurden, jedoch im aktuellen nicht deklariert sind.

Der Ansatz mit dem 2-Wege-Merge-Patch hat ein Problem: er führt zu eine Asynchronität zwischen dem tatsächlichen Zustand der Ressource im Cluster und dem Manifest im Git..

Veranschaulichung des Problems am Beispiel

  • Im Git befindet sich im Chart ein Manifest, in dem das Feld image des Deployments den Wert hat ubuntu:18.04..
  • Der Benutzer hat über kubectl edit den Wert dieses Feldes auf ubuntu:19.04 geändert..
  • Bei der Neu-Deployment des Helm-Charts wird kein Patch generiert,weil das Feld image In der vorherigen Version des Releases und im aktuellen Chart sind sie identisch.
  • Nach dem erneuten Deployment image bleibt ubuntu:19.04 geändert., obwohl im Chart steht ubuntu:18.04..

Wir haben eine Desynchronisation erhalten und die Deklarativität verloren.

Was ist eine synchronisierte Ressource?

Im Allgemeinen vollständige Übereinstimmung des Ressourcenmanifests im laufenden Cluster mit dem Manifest aus Git ist unmöglich. Denn im echten Manifest können servicebezogene Annotations/Labels, zusätzliche Container und andere Daten enthalten sein, die dynamisch von bestimmten Controllern zu Ressourcen hinzugefügt oder entfernt werden. Diese Daten möchten wir nicht im Git führen. Wir wollen jedoch, dass bei der Bereitstellung die Felder, die wir ausdrücklich in Git angegeben haben, die entsprechenden Werte annehmen.

Es ergibt sich also eine allgemeine Regel für die synchronisierte Ressource: Bei der Bereitstellung der Ressource dürfen nur die Felder geändert oder gelöscht werden, die ausdrücklich im Manifest aus Git angegeben sind (oder die in der vorherigen Version angegeben waren und nun entfernt wurden).

3-Wege-Merge-Patch

Die Grundidee 3-Wege-Merge-Patch: Wir generieren einen Patch zwischen der zuletzt angewendeten Manifestversion aus Git und der Zielversion des Manifests aus Git, unter Berücksichtigung der aktuellen Manifestversion aus dem laufenden Cluster. Der endgültige Patch muss der Regel für synchronisierte Ressourcen entsprechen:

  • neue Felder, die in die Zielversion aufgenommen wurden, werden durch den Patch hinzugefügt;
  • bereits vorhandene Felder in der letzten angewendeten Version, die in der Zielversion nicht existieren, werden durch den Patch auf null gesetzt;
  • Felder in der aktuellen Objektversion, die von der Zielversion des Manifests abweichen, werden durch den Patch aktualisiert.

Nach diesem Prinzip werden die Patches erstellt. kubectl apply:

  • Die letzte angewendete Version des Manifests wird in der Annotation des Objekts gespeichert,
  • die Zielversion wird aus der angegebenen YAML-Datei entnommen,
  • die aktuelle Version stammt aus dem laufenden Cluster.

Nachdem wir nun die Theorie geklärt haben, ist es an der Zeit, zu erzählen, was wir in werf gemacht haben.

Anwendung von Änderungen in werf

Früher verwendete werf, ähnlich wie Helm 2, 2-way-merge-Patches.

Repair-Patch

Um auf die neue Art von Patches — 3-way-merge — umzusteigen, haben wir als ersten Schritt die sogenannten Repair-Patches eingeführt..

Beim Deployment wird ein Standard-2-Wege-Merge-Patch verwendet, aber werf generiert zusätzlich einen Patch, der den aktuellen Status der Ressource mit dem synchronisiert, was in Git geschrieben steht (dieser Patch wird unter Verwendung der gleichen Synchronisationsregel erstellt, die oben beschrieben wurde).

Im Falle eines Desynchronisierungsproblems erhält der Benutzer am Ende des Deployments eine WARNUNG mit einer entsprechenden Nachricht und einem Patch, der angewendet werden muss, um die Ressource in einen synchronisierten Zustand zu bringen. Außerdem wird dieser Patch in einer speziellen Annotation aufgezeichnet. werf.io/repair-patch. Es wird davon ausgegangen, dass der Benutzer diesen Patch manuell selbst anwenden wird: werf wird dies grundsätzlich nicht automatisch tun.

Die Generierung von Reparatur-Patches ist eine vorübergehende Maßnahme, die es ermöglicht, die Erstellung von Patches nach dem 3-Wege-Merge-Prinzip praktisch zu testen, aber diese Patches nicht automatisch anzuwenden. Derzeit ist dieser Arbeitsmodus standardmäßig aktiviert.

3-Wege-Merge-Patch nur für neue Releases

Ab dem 1. Dezember 2019 beginnen die Beta- und Alpha-Versionen von werf. standardmäßig verwendet Nutzen Sie vollwertige 3-Wege-Merge-Patches, um Änderungen nur auf neue Helm-Releases anzuwenden, die über werf ausgerollt werden. Bereits bestehende Releases verwenden weiterhin den Ansatz mit 2-Wege-Merge + Repair-Patches.

Dieser Betriebsmodus kann durch die folgende Einstellung explizit aktiviert werden: WERF_THREE_WAY_MERGE_MODE=onlyNewReleases bereits jetzt.

Hinweis: Diese Funktion war in werf über mehrere Releases hinweg verfügbar: Im Alpha-Kanal war sie ab Version v1.0.5-alpha.19, im Beta-Kanal jedoch ab v1.0.4-beta.20.

3-Wege-Merge-Patch für alle Releases

Seit dem 15. Dezember 2019 verwenden Beta- und Alpha-Versionen von werf standardmäßig vollwertige 3-Wege-Merge-Patches zur Anwendung von Änderungen für alle Releases.

Dieser Betriebsmodus kann durch die folgende Einstellung explizit aktiviert werden: WERF_THREE_WAY_MERGE_MODE=enabled bereits jetzt.

Wie steht es um die automatisierte Skalierung von Ressourcen?

In Kubernetes gibt es 2 Arten der automatischen Skalierung: HPA (horizontal) und VPA (vertikal).

Das horizontale Skalieren wählt automatisch die Anzahl der Replikate aus, das vertikale die Menge der Ressourcen. Sowohl die Anzahl der Replikate als auch die Ressourcenspezifikationen werden im Ressourcenmanifest angegeben (siehe: spec.replicas oder spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory und andere).

Problem: Wenn ein Benutzer die Ressource im Chart so konfiguriert, dass bestimmte Werte für Ressourcen oder Replikate angegeben werden und für diese Ressource Autoscaler aktiviert sind, werden bei jedem Deployment werf diese Werte auf die im Manifest des Charts angegebenen zurückgesetzt.

Es gibt zwei Lösungen für das Problem. Zunächst sollte man am besten auf die explizite Angabe autoskalierbarer Werte im Manifest des Charts verzichten. Sollte diese Option aus irgendeinem Grund nicht geeignet sein (zum Beispiel, weil es im Chart praktisch ist, anfängliche Ressourcengrenzen und die Anzahl der Replikate festzulegen), bietet werf die folgenden Annotationsmöglichkeiten an:

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

Mit einer solchen Annotation wird werf die entsprechenden Werte bei jedem Deployment nicht zurücksetzen, sondern nur bei der erstmaligen Erstellung der Ressource festlegen.

Für weitere Informationen – siehe die Projektdokumentation zu HPA und VPA.

Vermeiden Sie die Verwendung von 3-Way-Merge-Patches

Der Benutzer kann vorerst die Verwendung neuer Patches in werf über die Umgebungsvariable unterbinden WERF_THREE_WAY_MERGE_MODE=disabled. Ab dem 1. März 2020 wird dieses Verbot jedoch nicht mehr wirksam sein. Und es wird möglicherweise nur die Verwendung von 3-Wege-Merge-Patches möglich sein.

Ressourcenenadaption in werf

Die Beherrschung der Methode zur Anwendung von 3-Wege-Merge-Patches hat es uns ermöglicht, sofort eine Funktion wie die Adoption bestehender Ressourcen im Cluster in Helm-Releases zu implementieren.

Helm 2 hat ein Problem: Es ist nicht möglich, eine Ressource, die bereits im Cluster existiert, ohne deren vollständige Neuerschaffung zu den Chart-Manifests hinzuzufügen (siehe #6031, #3275). Wir haben werf beigebracht, vorhandene Ressourcen in einen Release zu integrieren. Dazu muss eine Anmerkung zur aktuellen Version der Ressource aus dem laufenden Cluster hinzugefügt werden (zum Beispiel mithilfe von kubectl edit):

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

Jetzt muss die Ressource im Chart beschrieben werden, und beim nächsten Deployment des werf-Releases mit dem entsprechenden Namen wird die bestehende Ressource in diesen Release aufgenommen und unter dessen Verwaltung bleiben. Darüber hinaus wird werf beim Prozess der Ressourcenauswahl den aktuellen Zustand der Ressource aus dem laufenden Cluster an den im Chart beschriebenen Zustand anpassen, indem die gleichen 3-Wege-Merge-Patches verwendet werden und die Regel für synchronisierte Ressourcen angewendet wird.

Hinweis: Konfiguration WERF_THREE_WAY_MERGE_MODE beeinflusst nicht die Adoption von Ressourcen – im Falle der Adoption wird immer ein 3-Wege-Merge-Patch verwendet.

Details – in Dokumentation..

Schlussfolgerungen und zukünftige Pläne

Ich hoffe, dass nach diesem Artikel klarer geworden ist, was 3-way-merge-Patches sind und warum wir sie eingeführt haben. Aus praktischer Sicht hinsichtlich der Weiterentwicklung des Projekts werf stellt ihre Implementierung einen weiteren Schritt zur Verbesserung der Helm-ähnlichen Bereitstellung dar. Jetzt können die Probleme mit der Synchronisierung der Konfiguration, die häufig bei der Verwendung von Helm 2 auftraten, vergessen werden. Zudem wurde eine neue nützliche Funktion für die Übernahme bereits heruntergeladener Kubernetes-Ressourcen in das Helm-Release hinzugefügt.

Im Helm-ähnlichen Bereitstellungsprozess bestehen weiterhin einige Probleme und Herausforderungen, wie die Verwendung von Go-Templates, und wir werden weiterhin daran arbeiten, diese zu lösen.

Informationen zu den Methoden zur Aktualisierung von Ressourcen und zur Übernahme finden Sie auch auf dieser Dokumentationsseite.

Helm 3

Besonders erwähnenswert ist die vor Kurzem veröffentlichte neue Hauptversion von Helm – v3 –, die ebenfalls 3-way-merge-Patches verwendet und Tiller entfernt. Die neue Helm-Version erfordert eine Migration der bereits bestehenden Installationen, um sie in das neue Format der Release-Speicherung zu konvertieren.

Werf hat seinerseits bereits die Verwendung von Tiller eingestellt, ist auf 3-way-merge umgestiegen und hat hinzugefügt und vieles mehr, während es mit bestehenden Installationen von Helm 2 kompatibel bleibt (es sind keine Migrationsskripte erforderlich). Daher verlieren Benutzer von werf, solange es nicht auf Helm 3 umgestellt wird, nicht die wesentlichen Vorteile von Helm 3 gegenüber Helm 2 (diese sind auch in werf vorhanden).

Dennoch ist der Wechsel von werf zur Codebasis von Helm 3 unvermeidlich und wird in naher Zukunft stattfinden. Voraussichtlich wird dies in werf 1.1 oder werf 1.2 geschehen (derzeit ist die Hauptversion von werf 1.0; weitere Informationen zur Versionsstruktur von werf finden Sie in hier). In der Zwischenzeit wird Helm 3 stabiler werden.

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster