3-Wege-Fusion in Werf: Einsatz in Kubernetes mit Helm "auf Steroiden"

Es ist etwas passiert, worauf wir (und nicht nur wir) gewartet haben: werf , unser Open Source-Dienstprogramm zum Erstellen von Anwendungen und zum Bereitstellen auf Kubernetes, unterstützt jetzt das Anwenden von Änderungen mithilfe von 3-Wege-Merge-Patches! Darüber hinaus wurde es möglich, vorhandene K8-Ressourcen in Helm-Releases zu übernehmen, ohne diese Ressourcen neu zu erstellen.



Wenn es sehr kurz ist, setzen Sie WERF_THREE_WAY_MERGE=enabled - wir erhalten die Bereitstellung "wie in kubectl apply ", kompatibel mit bestehenden Installationen auf Helm 2 und sogar ein bisschen mehr.

Aber fangen wir mit der Theorie an: Was sind 3-Wege-Merge-Patches im Allgemeinen, wie sind die Menschen mit ihrer Generation dazu gekommen, und warum sind sie in CI / CD-Prozessen mit Kubernetes-basierter Infrastruktur wichtig? Und danach - mal sehen, was 3-Wege-Merge in werf ist, welche Modi standardmäßig verwendet werden und wie man es verwaltet.

Was ist ein 3-Wege-Merge-Patch?


Beginnen wir also mit der Aufgabe, die in den YAML-Manifesten in Kubernetes beschriebenen Ressourcen bereitzustellen.

Um mit Ressourcen zu arbeiten, bietet die Kubernetes-API die folgenden grundlegenden Vorgänge: Erstellen, Patchen, Ersetzen und Löschen. Es wird davon ausgegangen, dass mit ihrer Hilfe ein bequemer kontinuierlicher Rollout von Ressourcen in den Cluster erstellt werden muss. Wie?

Imperative Kubectl-Teams


Der erste Ansatz zum Verwalten von Objekten in Kubernetes besteht darin, die zwingenden Kubectl-Befehle zum Erstellen, Ändern und Löschen dieser Objekte zu verwenden. Einfach ausgedrückt:

  • kubectl run Befehl kubectl run kann Deployment oder Job ausführen:

     kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE 
  • Befehl kubectl scale - Anzahl der Replikate ändern:

     kubectl scale --replicas=3 deployment/mysql 
  • usw.

Ein solcher Ansatz mag auf den ersten Blick zweckmäßig erscheinen. Es gibt jedoch Probleme:

  1. Es ist schwer zu automatisieren .
  2. Wie spiegelt sich die Konfiguration in Git wider ? Wie überprüfe ich Änderungen an einem Cluster?
  3. Wie kann die Reproduzierbarkeit der Konfiguration beim Neustart sichergestellt werden?
  4. ...

Es ist klar, dass dieser Ansatz nicht gut zum Speichern der Anwendung und der Infrastruktur als Code (IaC oder sogar GitOps als modernere Option, die im Kubernetes-Ökosystem an Beliebtheit gewinnt) mit dem Code passt . Daher wurden diese Teams in kubectl nicht weiterentwickelt.

Vorgänge erstellen, abrufen, ersetzen und löschen


Mit der primären Erstellung ist alles einfach: Wir senden das Manifest an die create von kube api und die Ressource wird erstellt. Die YAML-Darstellung des Manifests kann in Git gespeichert werden. Zum Erstellen verwenden Sie den kubectl create -f manifest.yaml .

Das Löschen ist ebenfalls einfach: Wir ersetzen das gleiche manifest.yaml von Git durch den kubectl delete -f manifest.yaml .

Mit der replace können Sie die Ressourcenkonfiguration vollständig durch eine neue ersetzen, ohne die Ressource neu erstellen zu müssen. Dies bedeutet, dass es logisch ist, die aktuelle Version mit der Operation get anzufordern, zu ändern und mit der Operation replace aktualisieren, bevor eine Änderung an einer Ressource vorgenommen wird. Die optimistische Sperrung ist in kube apiserver integriert. Wenn sich das Objekt nach dem Abrufvorgang geändert hat, schlägt der replace fehl.

Um die Konfiguration in Git zu speichern und mit replace zu aktualisieren, müssen Sie einen get Vorgang ausführen, die Konfiguration von Git mit dem, was wir haben, halten und replace ausführen. Normalerweise können Sie in kubectl nur den Befehl kubectl replace -f manifest.yaml , wobei manifest.yaml das vollständig vorbereitete (in unserem Fall angefügte) Manifest ist, das installiert werden muss. Es stellt sich heraus, dass der Benutzer Zusammenführungsmanifeste implementieren muss, aber dies ist keine triviale Angelegenheit ...

Es ist auch erwähnenswert, dass, obwohl manifest.yaml in Git gespeichert ist, wir nicht im Voraus wissen können, ob wir ein Objekt erstellen oder aktualisieren müssen - dies sollte von der Anwendersoftware durchgeführt werden.

Fazit: Können wir ein kontinuierliches Rollout nur mit create, replace und delete erstellen, um sicherzustellen, dass die Infrastrukturkonfiguration zusammen mit dem Code und einer praktischen CI / CD in Git gespeichert wird?

Grundsätzlich können wir ... Dazu müssen wir die Zusammenführungsoperation der Manifeste und eine Art Bindung implementieren, die :

  • prüft, ob ein Objekt im Cluster vorhanden ist,
  • führt die anfängliche Erstellung der Ressource durch,
  • aktualisiert oder löscht es.

Beim Aktualisieren müssen Sie berücksichtigen, dass sich die Ressource seit dem letzten Abruf möglicherweise geändert hat get und den Fall des optimistischen Sperrens automatisch behandeln. Versuchen Sie wiederholt, eine Aktualisierung durchzuführen.

Warum jedoch das Rad neu erfinden, wenn kube-apiserver eine andere Möglichkeit zum Aktualisieren von Ressourcen bietet: den patch Vorgang, mit dem einige der vom Benutzer beschriebenen Probleme behoben werden?

Patch


Also kamen wir zu den Patches.

Patches sind die Hauptmethode, um Änderungen an vorhandenen Objekten in Kubernetes anzuwenden. Der patch Vorgang funktioniert folgendermaßen:

  • Der Benutzer von kube-apiserver muss den Patch im JSON-Format senden und das Objekt angeben.
  • und apiserver selbst wird sich mit dem aktuellen Zustand des Objekts befassen und es in die gewünschte Form bringen.

Ein optimistisches Sperren ist in diesem Fall nicht erforderlich. Diese Operation ist aussagekräftiger als das Ersetzen, obwohl es auf den ersten Blick andersherum erscheinen mag.

Auf diese Weise:

  • Mit der Operation create create wir ein Objekt aus dem Manifest von Git.
  • using delete - Löschen, wenn das Objekt nicht mehr benötigt wird,
  • using patch - Wir modifizieren das Objekt und bringen es in die in Git beschriebene Form.

Dazu müssen Sie jedoch den richtigen Patch erstellen!

Wie Patches in Helm 2 funktionieren: 2-Wege-Zusammenführung


Bei der ersten Installation einer Version führt Helm einen create für Diagrammressourcen durch.

Beim Aktualisieren der Helm-Version für jede Ressource:

  • Zählt den Patch zwischen der Version der Ressource aus dem vorherigen Diagramm und der aktuellen Version des Diagramms.
  • wendet diesen Patch an.

Wir werden einen solchen Patch als 2-Wege-Merge-Patch bezeichnen , da zwei Manifeste an seiner Erstellung beteiligt sind:

  • Ressourcenmanifest aus vorheriger Version,
  • Das Manifest der Ressource aus der aktuellen Ressource.

Beim Löschen wird der delete in kube apiserver für Ressourcen aufgerufen, die in der vorherigen Version deklariert, in der aktuellen Version jedoch nicht deklariert wurden.

Der Ansatz mit 2-Wege-Merge-Patch hat ein Problem: Er führt zu einer Desynchronisierung des tatsächlichen Zustands der Ressource im Cluster und des Manifests in Git .

Ein Beispiel für ein Problem


  • In Git wird ein Manifest in dem Diagramm gespeichert, in dem das Feld Deployment- image den ubuntu:18.04 Wert ubuntu:18.04 .
  • Der Benutzer hat durch kubectl edit den Wert dieses Feldes in ubuntu:19.04 geändert.
  • Wenn Sie das Diagramm erneut bereitstellen, generiert Helm keinen Patch , da das image in der vorherigen Version der Version und im aktuellen Diagramm identisch sind.
  • Nach der wiederholten Bereitstellung von image bleibt ubuntu:19.04 , obwohl ubuntu:18.04 in der Tabelle ubuntu:18.04 ist.

Wir haben Desync und Deklarativität verloren.

Was ist eine synchronisierte Ressource?


Im Allgemeinen ist es unmöglich, eine vollständige Übereinstimmung zwischen einem Ressourcenmanifest in einem laufenden Cluster und einem Manifest von Git zu erhalten. Da im realen Manifest möglicherweise Dienstanmerkungen / -beschriftungen, zusätzliche Container und andere Daten vorhanden sind, die von einigen Controllern dynamisch zur Ressource hinzugefügt und daraus gelöscht wurden. Wir können und wollen diese Daten nicht in Git speichern. Wir möchten jedoch, dass beim Rollout die Felder, die wir explizit in Git angegeben haben, entsprechende Werte annehmen.

Es stellt sich heraus, dass diese allgemeine Regel einer synchronisierten Ressource gilt : Wenn Sie eine Ressource bereitstellen, können Sie nur die Felder ändern oder löschen, die explizit im Manifest von Git angegeben sind (oder in der vorherigen Version registriert wurden, jetzt aber gelöscht werden).

3-Wege-Merge-Patch


Die Hauptidee des 3-Wege-Merge-Patches : Wir generieren einen Patch zwischen der zuletzt angewendeten Version des Manifests von Git und der Zielversion des Manifests von Git unter Berücksichtigung der aktuellen Version des Manifests aus dem Arbeitscluster. Der endgültige Patch muss der synchronisierten Ressourcenregel entsprechen:

  • Neue Felder, die der Zielversion hinzugefügt wurden, werden mit dem Patch hinzugefügt.
  • Zuvor vorhandene Felder in der zuletzt angewendeten Version und nicht im Zielfeld vorhandene Felder werden mit dem Patch zurückgesetzt.
  • Felder in der aktuellen Version des Objekts, die sich von der Zielversion des Manifests unterscheiden, werden mit dem Patch aktualisiert.

Nach diesem Prinzip werden kubectl apply Patches generiert:

  • Die zuletzt angewendete Version des Manifests wird in der Annotation des Objekts selbst gespeichert.
  • Ziel - aus der angegebenen YAML-Datei entnommen,
  • current - aus einem Arbeitscluster.

Nachdem wir die Theorie herausgefunden haben, ist es Zeit, Ihnen zu erzählen, was wir bei werf gemacht haben.

Änderungen auf werf anwenden


Früher verwendete werf wie Helm 2 2-Wege-Merge-Patches.

Patch reparieren


Um zu einer neuen Art von Patches zu wechseln - 3-Wege-Merge - haben wir als ersten Schritt die sogenannten Repair-Patches eingeführt .

Bei der Bereitstellung wird der standardmäßige 2-Wege-Merge-Patch verwendet, aber werf generiert zusätzlich einen Patch, der den tatsächlichen Status der Ressource mit dem in Git geschriebenen synchronisiert (ein solcher Patch wird mit derselben synchronisierten Ressourcenregel wie oben beschrieben erstellt).

Bei einer Rassynchronisierung erhält der Benutzer am Ende der Bereitstellung eine WARNUNG mit der entsprechenden Meldung und dem entsprechenden Patch, die angewendet werden muss, um die Ressource in ein synchronisiertes Formular zu bringen. Auch dieser Patch ist in einer speziellen Annotation werf.io/repair-patch . Es wird davon ausgegangen, dass der Benutzer diesen Patch selbst mit den Händen anwendet: werf wird ihn grundsätzlich nicht anwenden.

Das Generieren von Reparatur-Patches ist eine temporäre Maßnahme, mit der Sie die Erstellung von Patches nach dem Prinzip der 3-Wege-Zusammenführung testen können, diese Patches jedoch nicht automatisch anwenden können. Momentan ist diese Betriebsart standardmäßig aktiviert.

3-Wege-Merge-Patch nur für Neuerscheinungen


Ab dem 1. Dezember 2019 verwenden Beta- und Alpha-Versionen von werf standardmäßig vollwertige 3-Wege-Merge-Patches, um Änderungen nur für neue Helm-Versionen anzuwenden, die über werf eingeführt wurden. In bestehenden Releases wird weiterhin der 2-Wege-Merge + Repair-Patch-Ansatz verwendet.

Sie können diesen Betriebsmodus explizit WERF_THREE_WAY_MERGE_MODE=onlyNewReleases jetzt WERF_THREE_WAY_MERGE_MODE=onlyNewReleases .

Hinweis : Die Funktion erschien in werf über mehrere Releases: Im Alphakanal wurde sie ab Version 1.0.5-alpha.19 und im Betakanal mit Version 1.0.4-beta.20 bereitgestellt .

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


Ab dem 15. Dezember 2019 verwenden die Beta- und Alpha-Versionen von werf standardmäßig vollwertige Patches für die 3-Wege-Zusammenführung, um Änderungen für alle Releases zu übernehmen.

Diese Betriebsart kann explizit WERF_THREE_WAY_MERGE_MODE=enabled indem WERF_THREE_WAY_MERGE_MODE=enabled jetzt WERF_THREE_WAY_MERGE_MODE=enabled .

Was ist mit Ressourcen für die automatische Skalierung zu tun?


Kubernetes bietet zwei Arten der automatischen Skalierung: HPA (horizontal) und VPA (vertikal).

Horizontal wählt automatisch die Anzahl der Replikate aus, vertikal die Anzahl der Ressourcen. Sowohl die Anzahl der Replikate als auch die Ressourcenanforderungen werden im Ressourcenmanifest angegeben (siehe spec.replicas oder spec.containers[].resources.limits.cpu , spec.containers[].resources.limits.memory und andere ).

Problem: Wenn ein Benutzer eine Ressource im Diagramm so konfiguriert, dass bestimmte Werte für Ressourcen oder Replikate angezeigt werden und für diese Ressource automatische Skalierer aktiviert sind, werden diese Werte bei jeder Bereitstellung auf die Werte zurückgesetzt, die im Diagrammmanifest angegeben sind.

Es gibt zwei Lösungen für das Problem. Für den Anfang ist es am besten, die explizite Angabe von Autoscale-Werten im Diagrammmanifest zu verwerfen. Wenn diese Option aus irgendeinem Grund nicht passt (z. B. weil es bequem ist, die anfänglichen Ressourcenlimits und die Anzahl der Replikate im Diagramm festzulegen), bietet werf die folgenden Anmerkungen an:

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

Wenn eine solche Anmerkung vorhanden ist, setzt werf die entsprechenden Werte nicht bei jeder Bereitstellung zurück, sondern legt sie nur beim erstmaligen Erstellen der Ressource fest.

Weitere Informationen finden Sie in der Projektdokumentation für HPA und VPA .

Verweigern Sie die Verwendung eines 3-Wege-Patches


Der Benutzer kann weiterhin die Verwendung neuer Patches in werf unter Verwendung der Umgebungsvariablen WERF_THREE_WAY_MERGE_MODE=disabled . Ab dem 1. März 2020 funktioniert dieses Verbot jedoch nicht mehr und es können nur noch 3-Wege-Merge-Patches verwendet werden.

Übernahme von Ressourcen in werf


Durch die Beherrschung der Methode zum Anwenden von Änderungen in 3-Wege-Merge-Patches konnten wir eine Funktion wie die Übernahme von Ressourcen, die im Cluster in der Helm-Version vorhanden sind, sofort implementieren.

Helm 2 hat ein Problem: Sie können keine Ressource zu einem bereits im Cluster vorhandenen Diagrammmanifest hinzufügen, ohne diese Ressource von Grund auf neu zu erstellen (siehe # 6031 , # 3275 ). Wir haben werf gelehrt, vorhandene Ressourcen in einer Version zu akzeptieren. Dazu müssen Sie in einem laufenden Cluster eine Anmerkung für die aktuelle Version der Ressource kubectl edit (z. B. mit kubectl edit ):

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

Nun muss die Ressource in der Tabelle beschrieben werden und bei der nächsten Bereitstellung durch die werf-Version der Version mit dem entsprechenden Namen wird die vorhandene Ressource in diese Version aufgenommen und bleibt unter ihrer Kontrolle. Darüber hinaus wird werf beim Akzeptieren der Ressource zur Freigabe den aktuellen Status der Ressource aus dem Arbeitscluster mit denselben 3-Wege-Merge-Patches und der synchronisierten Ressourcenregel in den im Diagramm beschriebenen Status versetzen.

Hinweis : Die Einstellung von WERF_THREE_WAY_MERGE_MODE wirkt sich nicht auf die Übernahme von Ressourcen aus. Im Falle einer Übernahme wird immer ein 3-Wege-Merge-Patch verwendet.

Details finden Sie in der Dokumentation .

Schlussfolgerungen und Zukunftspläne


Ich hoffe, dass nach diesem Artikel klarer wurde, was 3-Wege-Merge-Patches sind und warum sie zu ihnen gekommen sind. Aus praktischer Sicht war die Implementierung des werf-Projekts ein weiterer Schritt zur Verbesserung des helmartigen Einsatzes. Jetzt können Sie die Probleme bei der Konfigurationssynchronisierung vergessen, die bei der Verwendung von Helm 2 häufig auftraten. Gleichzeitig wurde eine neue nützliche Funktion für die Übernahme von Kubernetes-Ressourcen hinzugefügt, die bereits in die Helm-Version hochgeladen wurden.

Es gibt immer noch einige Probleme und Schwierigkeiten bei der helmartigen Bereitstellung, z. B. die Verwendung von Go-Vorlagen, und wir werden sie weiterhin lösen.

Informationen zu Ressourcenaktualisierungsmethoden und zur Übernahme finden Sie auch auf dieser Dokumentationsseite .

Helm 3


Ein besonderer Hinweis verdient die kürzlich veröffentlichte neue Hauptversion von Helm - v3 -, die ebenfalls 3-Wege-Merge-Patches verwendet und Tiller beseitigt. Die neue Version von Helm erfordert die Migration vorhandener Installationen, um sie in ein neues Release-Speicherformat zu konvertieren.

Werf seinerseits hat nun die Verwendung von Tiller beseitigt, auf 3-Wege-Zusammenführung umgestellt und viel mehr hinzugefügt, wobei die Kompatibilität mit vorhandenen Installationen auf Helm 2 erhalten bleibt (es sind keine Migrationsskripte erforderlich). Bis werf auf Helm 3 umgestellt wird, verlieren werf-Benutzer daher nicht die Hauptvorteile von Helm 3 gegenüber Helm 2 (sie existieren auch in werf).

Die Umstellung auf die Helm 3-Codebasis ist jedoch unumgänglich und wird in naher Zukunft erfolgen. Vermutlich handelt es sich um werf 1.1 oder werf 1.2 (die Hauptversion von werf ist derzeit 1.0; weitere Informationen zum werf-Versionsgerät finden Sie hier ). Während dieser Zeit hat Helm 3 Zeit, sich zu stabilisieren.

PS


Lesen Sie auch in unserem Blog:

Source: https://habr.com/ru/post/de476646/


All Articles