Es ist geschehen, worauf wir (und nicht nur wir) lange gewartet haben: , unser Open Source-Tool zur Erstellung von Anwendungen und deren Bereitstellung in Kubernetes unterstützt jetzt die Anwendung von Änderungen mit 3-Way-Merge-Patches! Darüber hinaus gibt es die Möglichkeit, vorhandene K8s-Ressourcen in Helm-Releases zu übernehmen, ohne diese Ressourcen neu zu erstellen.

Kurz gesagt, wir setzen WERF_THREE_WAY_MERGE=enabled — und erhalten ein Deployment „wie in kubectl apply“, das mit bestehenden Installationen auf Helm 2 kompatibel ist und sogar noch etwas mehr.
Doch lass uns mit der Theorie beginnen: Was sind 3-Way-Merge-Patches, wie kamen die Menschen zu dem Ansatz ihrer Generierung und warum sind sie wichtig in CI/CD-Prozessen mit Infrastruktur basierend auf Kubernetes? Danach sehen wir uns an, was 3-Way-Merge in werf ist, welche Modi standardmäßig verwendet werden und wie man damit umgeht.
Was ist ein 3-Way-Merge-Patch?
Beginnen wir mit der Aufgabe, Ressourcen, die in YAML-Manifests beschrieben sind, in Kubernetes bereitzustellen.
Für die Arbeit mit Ressourcen bietet das Kubernetes-API die grundlegenden Operationen: create, patch, replace und delete. Es wird angenommen, dass man mit ihnen einen kontinuierlichen Rollout von Ressourcen in den Cluster konstruieren kann. Wie?
Imperative kubectl-Befehle
Der erste Ansatz zur Verwaltung von Objekten in Kubernetes ist die Verwendung imperativer kubectl-Befehle zum Erstellen, Ändern und Löschen dieser Objekte. Einfach gesagt:
- Wir setzen ein eigenes Passwort für den Benutzer
kubectl runkann ein Deployment oder Job starten:kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE - Wir setzen ein eigenes Passwort für den Benutzer
kubectl scale— ändert die Anzahl der Replikate:kubectl scale --replicas=3 deployment/mysql - usw.
Dieser Ansatz mag auf den ersten Blick bequem erscheinen. Es gibt jedoch Probleme:
- Es ist schwierig, automatisieren.
- Wie die Konfiguration in Git widerzuspiegeln? Wie macht man die Überprüfung der Änderungen, die im Cluster stattfinden?
- Wie stellt man sicher, Reproduzierbarkeit dass die Konfiguration beim Neustart erhalten bleibt?
- …
Es wird klar, dass dieser Ansatz schlecht mit der Speicherung des Codes der Anwendung und Infrastruktur als Code (IaC; oder sogar als die modernere Variante, die in der Kubernetes-Ökosystem an Popularität gewinnt) kombiniert werden kann. Daher erhielt dieser Befehlsansatz in kubectl keine weitere Entwicklung.
Die Operationen create, get, replace und delete
Bei der anfänglichen Erstellung ist alles einfach: Wir senden das Manifest an die create kube API und die Ressource wird erstellt. Die YAML-Darstellung des Manifests kann in Git gespeichert werden, und zur Erstellung kann der Befehl kubectl create -f manifest.yaml.
C und das Löschen ist ebenfalls einfach: Wir setzen dasselbe manifest.yaml aus Git in den Befehl ein: kubectl delete -f manifest.yaml.
Operation replace ermöglicht es, die Ressourcenkonfiguration 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 abzufragen, get, diese zu ändern und sie dann zu aktualisieren. replaceIm kube-apiserver ist implementiert und, wenn sich das Objekt nach der Operation get geändert hat, schlägt die Operation replace 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 replaceausführen. Standardmäßig erlaubt kubectl nur die Verwendung des Befehls kubectl replace -f manifest.yamlmanifest.yaml — ein bereits vollständig vorbereiteter (in unserem Fall gemergter) Manifest, der installiert werden muss. Das bedeutet, der Benutzer muss die Manifestdateien mergen, was keine triviale Angelegenheit ist…
Es sei auch erwähnt, dass obwohl manifest.yaml in Git gespeichert wird, wir nicht im Voraus wissen können, ob ein Objekt erstellt oder aktualisiert werden muss — das muss die benutzerspezifische Software erledigen.
Insgesamt: können wir ein kontinuierliches Deployment nur mit create, replace und delete sicherstellen und die Konfiguration der Infrastruktur zusammen mit dem Code in Git speichern und ein bequemes CI/CD bereitstellen?
Im Prinzip können wir… Dafür muss die Merge-Operation der Manifeste und eine Art Wrapper implementiert werden, der:
- die Existenz des Objekts im Cluster überprüft,
- die Erstcreation der Ressource durchführt,
- sie aktualisiert oder entfernt.
Bei einer Aktualisierung muss berücksichtigt werden, dass die Ressource sich geändert haben könnte seit dem letzten get und den Fall des optimistischen Lockings automatisch bearbeiten — Wiederholungsversuche zur Aktualisierung durchführen.
Aber warum das Rad neu erfinden, wenn kube-apiserver einen anderen Weg bietet, Ressourcen zu aktualisieren: die Operation patch, die dem Benutzer einen Teil der beschriebenen Probleme abnimmt?
Patch
Jetzt sind wir bei den Patches angekommen.
Patches sind der Hauptweg, um Änderungen an vorhandenen Objekten in Kubernetes anzuwenden. Die Operation patch funktioniert so, dass der Benutzer des kube-apiserver einen Patch im JSON-Format senden und das Objekt angeben muss,
- und der apiserver selbst den aktuellen Zustand des Objekts analysiert und es in die gewünschte Form bringt.
- Optimistisches Locking ist in diesem Fall nicht erforderlich. Diese Operation ist deklarativer als replace, obwohl es zunächst anders erscheinen mag.
Somit:
mit der Operation
- erstellen wir ein Objekt aus dem Manifest in Git,
create— löschen wir, wenn das Objekt nicht mehr benötigt wird, - mit
delete— ändern wir das Objekt und bringen es in die Form, die in Git beschrieben wird. - mit
patch— Wir ändern das Objekt und bringen es in die im Git beschriebene Form.
Um dies zu tun, muss jedoch der richtige Patch erstellt werden!
Wie Patches in Helm 2 funktionieren: 2-Wege-Merge
Bei der ersten Installation des Helm-Releases führt Helm einen create für die Chart-Ressourcen durch.
Beim Aktualisieren des Helm-Releases:
- berechnet es den Patch zwischen der Ressourcenversion aus dem vorherigen Chart und der aktuellen Version des Charts,
- und wendet diesen Patch an.
Diesen Patch nennen wir 2-Wege-Merge-Patch, weil bei dessen Erstellung 2 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 delete im Kube-API-Server für Ressourcen aufgerufen, die im vorherigen Release deklariert wurden, aber nicht im aktuellen.
Der Ansatz mit dem 2-Wege-Merge-Patch hat ein Problem: Er führt zu einer Dissoziation des tatsächlichen Zustands der Ressource im Cluster und des Manifests in Git..
Eine Illustration des Problems anhand von
- In Git wird im Chart ein Manifest gespeichert, in dem das Feld
Imagein der Deployment den Wertubuntu:18.04. - Der Benutzer hat über
kubectl editden Wert dieses Feldes aufubuntu:19.04. - geändert. Beim erneuten Deployment des Helm-Chartswird kein Patch generiert,
Imageda das Feld - in der vorherigen Version des Releases und im aktuellen Chart gleich sind.
ImageNach dem erneuten Deploymentubuntu:19.04bleibtubuntu:18.04.
, obwohl im Chart steht,
Wir haben eine Desynchronisierung erfahren und die Deklarativität verloren.
Im Allgemeinen gilt, Was ist eine synchronisierte Ressource? Eine vollständige
Übereinstimmung des Ressourcenmanifests im laufenden Cluster und des Manifests aus Git ist unmöglich. Denn im tatsächlichen Manifest können Verwaltungsannotation/Labels, zusätzliche Container und andere Daten enthalten sein, die dynamisch von bestimmten Controllern hinzugefügt und entfernt werden. Diese Daten möchten wir nicht in Git speichern. Allerdings möchten wir, dass beim Deployment die Felder, die wir ausdrücklich in Git angegeben haben, die entsprechenden Werte annehmen. Es ergibt sich also die allgemeineRegel für synchronisierte Ressourcen:
Beim Deployment einer Ressource dürfen nur die Felder geändert oder entfernt werden, die eindeutig im Manifest aus Git aufgeführt sind (oder in der vorherigen Version aufgeführt waren und jetzt entfernt wurden).
Die Grundidee Der 3-Wege-Merge-Patch:
- Ein Patch wird zwischen der zuletzt angewendeten Version des Manifests aus Git und der Zielversion des Manifests aus Git unter Berücksichtigung der aktuellen Version des Manifests im laufenden Cluster generiert. Der endgültige Patch muss der Regel für synchronisierte Ressourcen entsprechen:
- Frühere vorhandene Felder in der letzten angewendeten Version, die nicht in der Zielversion vorhanden sind, werden mittels eines Patches auf null gesetzt;
- Felder in der aktuellen Version des Objekts, die sich von der Zielversion des Manifests unterscheiden, werden mit einem Patch aktualisiert.
So generiert das System Patches. 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 aus dem laufenden Cluster.
Jetzt, da wir die Theorie geklärt haben, wollen wir erzählen, was wir bei 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 der Patches, die 3-Way-Merge-Patches, umzusteigen, haben wir als ersten Schritt die sogenannten Repair-Patches.
verwendet. Bei der Bereitstellung wird ein standardmäßiger 2-Way-Merge-Patch verwendet, aber werf generiert zusätzlich einen Patch, der den aktuellen Zustand der Ressource mit dem synchronisiert, was in Git geschrieben steht (dieser Patch wird unter Verwendung derselben Regel der synchronisierten Ressource erstellt, die oben beschrieben wurde).
Im Falle einer Desynchronisierung erhält der Benutzer am Ende der Bereitstellung eine WARNUNG mit einer entsprechenden Nachricht und dem Patch, der angewendet werden muss, um die Ressource in den synchronisierten Zustand zu versetzen. Dieser Patch wird auch in einer speziellen Annotation aufgezeichnet werf.io/repair-patch. Es wird davon ausgegangen, dass der Benutzer manuell selbst diesen Patch anwendet: werf wird ihn aus Prinzip nicht anwenden.
Die Generierung von Repair-Patches ist eine vorübergehende Maßnahme, die es ermöglicht, das Erstellen von Patches nach dem Prinzip des 3-Way-Merge praktisch zu testen, jedoch werden diese Patches nicht automatisch angewendet. Derzeit ist dieser Betriebsmodus standardmäßig aktiviert.
3-Way-Merge-Patch nur für neue Releases
Ab dem 1. Dezember 2019 beginnen die Beta- und Alpha-Versionen von werf, vollständige 3-Way-Merge-Patches zur Anwendung von Änderungen nur für neue Helm-Releases, die über werf ausgerollt werden, zu verwenden. Bereits bestehende Releases werden weiterhin den Ansatz mit 2-Way-Merge + Repair-Patches verwenden. standardmäßig Dieser Betriebsmodus kann ausdrücklich durch die Einstellung aktiviert werden
WERF_THREE_WAY_MERGE_MODE=onlyNewReleases : Die Funktion wurde in werf über mehrere Releases hinweg eingeführt: im Alpha-Kanal wurde sie ab Version bereits jetzt heruntergeladen werden.
Hinweisv1.0.5-alpha.19 3-Way-Merge-Patch für alle Releases .
3-Wege-Merge-Patch für alle Versionen
Ab dem 15. Dezember 2019 verwenden die Beta- und Alpha-Versionen von werf standardmäßig vollständige 3-way-merge-Patches, um Änderungen für alle Releases anzuwenden.
WERF_THREE_WAY_MERGE_MODE=onlyNewReleases WERF_THREE_WAY_MERGE_MODE=enabled bereits jetzt heruntergeladen werden.
Wie gehen wir mit der automatischen Skalierung von Ressourcen um?
In Kubernetes gibt es 2 Arten der automatischen Skalierung: HPA (horizontal) und VPA (vertikal).
Die horizontale Skalierung wählt automatisch die Anzahl der Replikate, die vertikale Skalierung die Menge der Ressourcen. Sowohl die Anzahl der Replikate als auch die Ressourcenerfordernisse werden im Ressourcenmanifest angegeben (siehe spec.replicas oder spec.containers[].resources.limits.cpu, spec.containers[].resources.limits.memory und ).
Problem: Wenn der Benutzer die Ressource im Chart so konfiguriert, dass bestimmte Werte für Ressourcen oder Replikate festgelegt sind und für diese Ressource Auto-Scaler aktiviert sind, wird werf bei jedem Deployment diese Werte auf das zurücksetzen, was im Manifest des Charts angegeben ist.
Es gibt zwei Lösungen für dieses Problem. Zunächst ist es am besten, auf die explizite Angabe von automatisch skalierbaren Werten im Manifest des Charts zu verzichten. Falls diese Option aus bestimmten Gründen nicht geeignet ist (zum Beispiel, weil es im Chart praktisch ist, Anfangsbeschränkungen für Ressourcen und die Anzahl der Replikate festzulegen), bietet werf die folgenden Annotationen an:
-
werf.io/set-replicas-only-on-creation=true -
werf.io/set-resources-only-on-creation=true
Bei Vorliegen einer solchen Annotation wird werf die entsprechenden Werte bei jedem Deployment nicht zurücksetzen, sondern sie lediglich bei der initialen Erstellung der Ressource festlegen.
Weitere Informationen finden Sie in der Projektdokumentation unter und .
Verwendung des 3-way-merge-Patches verbieten
Der Benutzer kann derzeit die Verwendung neuer Patches in werf mit der Umgebungsvariable WERF_THREE_WAY_MERGE_MODE=disabledverhindern. Ab dem 1. März 2020 wird dieses Verbot jedoch nicht mehr wirksam sein und es wird nur die Verwendung von 3-way-merge-Patches möglich sein.
Adoption von Ressourcen in werf
Die Einführung der Methode zur Anwendung von Änderungen mit 3-way-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 sie von Grund auf neu zu erstellen, zu den Manifests des Charts hinzuzufügen (siehe , ). Wir haben gelernt, werf dazu zu bringen, bestehende Ressourcen im Release zu akzeptieren. Dazu muss eine Annotation auf die aktuelle Version der Ressource aus dem laufenden Cluster angewendet werden (zum Beispiel mit kubectl edit):
"werf.io/allow-adoption-by-release": RELEASE_NAMEJetzt muss die Ressource im Chart beschrieben werden, und beim nächsten Deployment mit werf des Releases unter dem entsprechenden Namen wird die bestehende Ressource in dieses Release übernommen und bleibt unter seiner Verwaltung. Darüber hinaus wird werf beim Übernehmen der Ressource den aktuellen Zustand der Ressource aus dem laufenden Cluster in den im Chart beschriebenen Zustand überführen, indem die gleichen 3-way-merge-Patches und die Regel des synchronisierten Ressourcen verwendet werden.
Hinweis: Konfiguration WERF_THREE_WAY_MERGE_MODE beeinflusst nicht die Annahme von Ressourcen – im Falle einer Annahme wird immer ein 3-way-merge-Patch verwendet.
Details finden Sie in .
Ergebnisse und weitere Pläne
Ich hoffe, nach diesem Artikel ist klarer geworden, was 3-way-merge-Patches sind und warum sie verwendet werden. Aus praktischer Sicht der Entwicklung des werf-Projekts stellt ihre Implementierung einen weiteren Schritt zur Verbesserung des Helm-ähnlichen Deployments dar. Man kann nun die Probleme mit der Synchronisierung der Konfiguration, die häufig bei der Verwendung von Helm 2 auftraten, vergessen. Zudem wurde eine neue nützliche Funktion zur Annahme bereits heruntergeladener Kubernetes-Ressourcen in das Helm-Release hinzugefügt.
Im Helm-ähnlichen Deployment bestehen weiterhin einige Probleme und Schwierigkeiten, wie die Verwendung von Go-Templates, und wir werden diese weiterhin angehen.
Informationen zu den Methoden zur Aktualisierung von Ressourcen und zur Annahme finden Sie ebenfalls auf .
Helm 3
Eine besondere Erwähnung verdient die neue Hauptversion von Helm – v3 – die ebenfalls 3-way-merge-Patches verwendet und Tiller eliminiert. Die neue Helm-Version erfordert bereits bestehende 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 , dabei jedoch mit bestehenden Installationen auf Helm 2 kompatibel geblieben (es sind keine Migrationsskripte erforderlich). Daher verlieren werf-Benutzer, solange werf nicht auf Helm 3 umgeschaltet ist, nicht die wesentlichen Vorteile von Helm 3 gegenüber Helm 2 (die sind auch in werf vorhanden).
Dennoch ist der Wechsel von werf zur Codebasis von Helm 3 unvermeidlich und wird in naher Zukunft stattfinden. Vermutlich wird dies werf 1.1 oder werf 1.2 sein (derzeit ist die Hauptversion von werf 1.0; weitere Informationen zur Versionierung von werf finden Sie ). Bis dahin wird Helm 3 stabiler sein.
P.S.
Lesen Sie auch in unserem Blog:
- Eine Serie von Notizen zu neuen Funktionen in werf:
- «»;
- «»;
- «».
- «»;
- «»;
- «».
Quelle: habr.com
