
In unserer Praxis stehen wir häufig vor der Aufgabe, Client-Anwendungen für die Ausführung auf Kubernetes anzupassen. Bei der Ausführung dieser Arbeiten treten einige typische Probleme auf. Wir haben kürzlich eine davon im Artikel
Lokale Dateien beim Übertragen einer Anwendung auf Kubernetes behandelt , und die andere, die bereits mit CI / CD-Prozessen verknüpft ist, wird in diesem Artikel beschrieben.
Beliebige Befehle mit Helm und werf
Eine Anwendung besteht nicht nur aus Geschäftslogik und Daten, sondern auch aus einer Reihe beliebiger Befehle, die für eine erfolgreiche Aktualisierung ausgeführt werden müssen. Dies können zum Beispiel Migrationen für Datenbanken, "Kellner" für die Verfügbarkeit externer Ressourcen, einige Transcoder oder Entpacker, Registrare in der externen Dienstermittlung sein - Sie können verschiedene Aufgaben in verschiedenen Projekten erfüllen.
Was bietet Kubernetes, um solche Probleme zu lösen?
Kubernetes kann Container als Pods ausführen. Daher besteht die Standardlösung darin, einen Befehl von einem Image aus auszuführen. Zu diesem Zweck verfügt Kubernetes über ein
Job-Grundelement , mit dem Sie Pods mit Anwendungscontainern ausführen und den Abschluss dieses Pods überwachen können.
Helm geht noch einen Schritt weiter und schlägt vor, Job's in verschiedenen Phasen des Bereitstellungsprozesses zu starten. Es handelt sich um
Helm-Hooks, mit denen Sie Job vor oder nach dem Aktualisieren von Ressourcenmanifesten ausführen können. Nach unserer Erfahrung ist dies eine hervorragende Helm-Funktion, mit der Bereitstellungsaufgaben gelöst werden können.
Es ist jedoch nicht möglich, aktuelle Informationen zum Status von Objekten während des
Rollouts in Helm
abzurufen. Daher verwenden wir das Dienstprogramm
werf , mit dem Sie den Status von Ressourcen während des Rollouts direkt vom CI-System aus überwachen und, falls dies nicht erfolgreich ist, schnell einen Ausfall diagnostizieren können.
Wie sich herausstellte, schließen sich diese nützlichen Features Helm und werf manchmal gegenseitig aus, aber es gibt immer einen Ausweg. Überlegen Sie, wie Sie den Status von Ressourcen überwachen und beliebige Befehle am Beispiel von Migrationen ausführen können.
Ausführen von Migrationen vor der Veröffentlichung
Ein wesentlicher Bestandteil der Version einer Datenbankanwendung ist die Aktualisierung des Datenschemas. Die Standardbereitstellung für Anwendungen, die Migrationen durch Ausführen eines separaten Befehls anwenden, umfasst die folgenden Schritte:
- Aktualisierung der Codebasis;
- Beginn der Migration;
- Umschalten des Datenverkehrs auf die neue Version der Anwendung.
Innerhalb von Kubernetes sollte der Prozess derselbe sein, aber angepasst an das, was wir brauchen:
- Starten Sie einen Container mit einem neuen Code, der möglicherweise eine neue Gruppe von Migrationen enthält.
- Starten Sie den Migrationsprozess, bevor Sie die Version der Anwendung aktualisieren.
Ziehen Sie die Option in Betracht, wenn
die Datenbank für die Anwendung bereits ausgeführt wird und wir sie nicht als Teil der Version bereitstellen müssen, mit der die Anwendung bereitgestellt wird. Zwei Haken eignen sich zum Anwenden von Migrationen:
pre-install
- Funktioniert beim ersten Helm-Release der Anwendung, nachdem alle Vorlagen verarbeitet wurden, jedoch bevor Ressourcen in Kubernetes erstellt werden.pre-upgrade
- Funktioniert bei der Aktualisierung der Helm-Version und wird wie bei der pre-install
, nachdem die Vorlagen verarbeitet wurden, jedoch bevor Ressourcen in Kubernetes erstellt werden.
Jobbeispiel mit Helm und den beiden genannten Haken:
--- apiVersion: batch/v1 kind: Job metadata: name: {{ .Chart.Name }}-apply-migrations annotations: "helm.sh/hook": pre-install,pre-upgrade spec: activeDeadlineSeconds: 60 backoffLimit: 0 template: metadata: name: {{ .Chart.Name }}-apply-migrations spec: imagePullSecrets: - name: {{ required ".Values.registry.secret_name required" .Values.registry.secret_name }} containers: - name: job command: ["/usr/bin/php7.2", "artisan", "migrate", "--force"] {{ tuple "backend" . | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" . | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never
Hinweis : Die obige YAML-Vorlage wurde unter Berücksichtigung der Besonderheiten von werf erstellt. Um es an einen "sauberen" Helm anzupassen, reicht es aus:- Ersetze
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
in das {{ tuple "backend" . | include "werf_container_image" | indent 8 }}
Container-Image ein. - lösche die Zeile
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
, der im env
Schlüssel angegeben ist.
Daher muss diese Helm-Vorlage zum Verzeichnis
.helm/templates
hinzugefügt werden, das bereits die restlichen Release-Ressourcen enthält. Wenn
werf deploy --stages-storage :local
aufgerufen wird, werden zuerst alle Vorlagen verarbeitet und dann in den Kubernetes-Cluster geladen.
Starten von Migrationen während des Freigabeprozesses
Die obige Option impliziert die Verwendung von Migrationen für den Fall, dass die Datenbank bereits ausgeführt wird. Aber was ist, wenn wir die Zweigüberprüfung für die Anwendung einführen müssen und die
Datenbank mit der Anwendung in einer Version eingeführt wird?
NB : Beim Rollout in die Produktionsumgebung kann ein ähnliches Problem auftreten, wenn Sie Service mit einem Endpunkt verwenden, der die IP-Adresse der Datenbank enthält, um eine Verbindung zur Datenbank herzustellen.In diesem Fall sind die Hooks
pre-install
und
pre-upgrade
für uns nicht geeignet, da die Anwendung versucht, Migrationen auf die Datenbank anzuwenden, die
noch nicht vorhanden ist . Daher müssen
nach der Veröffentlichung Migrationen durchgeführt werden.
Mit Helm ist eine solche Aufgabe möglich, da
der Status von Anwendungen
nicht überwacht wird . Nach dem Laden von Ressourcen in Kubernetes werden Post-Hooks
immer ausgelöst:
post-install
- nach dem Laden aller Ressourcen in K8s beim ersten Release;post-upgrade
- nach dem Aktualisieren aller Ressourcen in K8s beim Aktualisieren der Version.
Wie bereits erwähnt,
verfügt werf während der Veröffentlichung
über ein Ressourcenverfolgungssystem . Ich werde darauf noch etwas näher eingehen:
- Für das Tracking nutzt werf die Funktionen der kubedog- Bibliothek, über die wir bereits im Blog gesprochen haben.
- Mit dieser Funktion in werf können wir den Status der Freigabe eindeutig bestimmen und Informationen zum erfolgreichen oder nicht erfolgreichen Abschluss der Bereitstellung in der Schnittstelle des CI / CD-Systems anzeigen.
- Ohne diese Informationen kann nicht über eine Automatisierung des Freigabeprozesses gesprochen werden, da die erfolgreiche Erstellung von Ressourcen im Kubernetes-Cluster nur eine der Phasen ist. Beispielsweise kann es sein, dass die Anwendung aufgrund einer falschen Konfiguration oder eines Netzwerkproblems nicht gestartet wird. Um dies jedoch nach dem
helm upgrade
, müssen Sie zusätzliche Schritte ausführen.
Nun zurück zur Anwendung von Migrationen auf Helm-Post-Hooks. Die Probleme, auf die wir gestoßen sind:
- Viele Anwendungen prüfen den Status der Schaltung in der Datenbank, bevor sie auf die eine oder andere Weise gestartet werden. Ohne neue Migrationen kann die Anwendung daher möglicherweise nicht gestartet werden.
- Da werf standardmäßig sicherstellt, dass sich alle Objekte im Status
Ready
, funktionieren Post-Hooks nicht und Migrationen schlagen fehl. - Das Verfolgen von Objekten kann durch zusätzliche Anmerkungen deaktiviert werden. In diesem Fall können jedoch keine zuverlässigen Informationen über die Ergebnisse der Bereitstellung abgerufen werden.
Als Ergebnis kamen wir zu folgendem:
- Jobs werden vor den Hauptressourcen erstellt, sodass für Migrationen keine Helm-Hooks verwendet werden müssen .
- Ein Job mit Migrationen muss jedoch bei jeder Bereitstellung ausgeführt werden. Zu diesem Zweck muss Job einen eindeutigen Namen (zufällig) haben. In diesem Fall ist dies für Helm jedes Mal ein neues Objekt in der Version, das in Kubernetes erstellt wird.
- Bei einem solchen Start macht es keinen Sinn, sich Sorgen zu machen, dass sich Job mit Migrationen ansammelt, da alle von ihnen eindeutige Namen haben und der vorherige Job mit einer neuen Version gelöscht wird.
- Ein Job mit Migrationen muss über einen Init-Container verfügen , der die Verfügbarkeit der Datenbank überprüft. Andernfalls wird die Bereitstellung gelöscht (Job fällt auf den Init-Container).
Die resultierende Konfiguration sieht ungefähr so aus:
--- apiVersion: batch/v1 kind: Job metadata: name: {{ printf "%s-apply-migrations-%s" .Chart.Name (now | date "2006-01-02-15-04-05") }} spec: activeDeadlineSeconds: 60 backoffLimit: 0 template: metadata: name: {{ printf "%s-apply-migrations-%s" .Chart.Name (now | date "2006-01-02-15-04-05") }} spec: imagePullSecrets: - name: {{ required ".Values.registry.secret_name required" .Values.registry.secret_name }} initContainers: - name: wait-db image: alpine:3.6 ommand: ["/bin/sh", "-c", "while ! nc -z postgres 5432; do sleep 1; done;"] containers: - name: job command: ["/usr/bin/php7.2", "artisan", "migrate", "--force"] {{ tuple "backend" . | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" . | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never
NB : Genau genommen werden Init-Container zur Überprüfung der Datenbankverfügbarkeit sowieso am besten verwendet.Ein Beispiel für eine universelle Vorlage für alle Bereitstellungsvorgänge
Die Vorgänge, die während des Releases ausgeführt werden müssen, können jedoch länger dauern als der Start der bereits erwähnten Migrationen. Sie können die Ausführungsreihenfolge von Job nicht nur über die Arten von Hooks steuern, sondern auch,
indem Sie jedem von ihnen eine Gewichtung zuweisen - über die Annotation
helm.sh/hook-weight
. Hooks werden nach Gewicht in aufsteigender Reihenfolge und bei gleichem Gewicht nach Ressourcennamen sortiert.
Bei einer großen Anzahl von Jobs ist es praktisch, eine universelle Vorlage für Jobs zu
values.yaml
und die Konfiguration in die
values.yaml
. Letzteres könnte so aussehen:
deploy_jobs: - name: migrate command: '["/usr/bin/php7.2", "artisan", "migrate", "--force"]' activeDeadlineSeconds: 120 when: production: 'pre-install,pre-upgrade' staging: 'pre-install,pre-upgrade' _default: '' - name: cache-clear command: '["/usr/bin/php7.2", "artisan", "responsecache:clear"]' activeDeadlineSeconds: 60 when: _default: 'post-install,post-upgrade'
... und die Vorlage selbst sieht so aus:
{{- range $index, $job := .Values.deploy_jobs }} --- apiVersion: batch/v1 kind: Job metadata: name: {{ $.Chart.Name }}-{{ $job.name }} annotations: "helm.sh/hook": {{ pluck $.Values.global.env $job.when | first | default $job.when._default }} "helm.sh/hook-weight": "1{{ $index }}" spec: activeDeadlineSeconds: {{ $job.activeDeadlineSeconds }} backoffLimit: 0 template: metadata: name: {{ $.Chart.Name }}-{{ $job.name }} spec: imagePullSecrets: - name: {{ required "$.Values.registry.secret_name required" $.Values.registry.secret_name }} initContainers: - name: wait-db image: alpine:3.6 ommand: ["/bin/sh", "-c", "while ! nc -z postgres 5432; do sleep 1; done;"] containers: - name: job command: {{ $job.command }} {{ tuple "backend" $ | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" $ | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never {{- end }}
Mit diesem Ansatz können Sie dem Freigabeprozess schnell neue Befehle hinzufügen und die Liste der ausgeführten Befehle übersichtlicher gestalten.
Fazit
Der Artikel enthält Beispiele für Vorlagen, mit denen Sie allgemeine Vorgänge beschreiben können, die Sie beim Freigeben einer neuen Version der Anwendung ausführen müssen. Obwohl sie das Ergebnis von Erfahrungen bei der Implementierung von CI / CD-Prozessen in Dutzenden von Projekten waren, bestehen wir nicht darauf, dass es für alle Aufgaben nur eine richtige Lösung gibt. Wenn die im Artikel beschriebenen Beispiele nicht die Anforderungen Ihres Projekts abdecken, freuen wir uns über Situationen in den Kommentaren, die zur Ergänzung dieses Materials beitragen könnten.
Kommentar von Werf-Entwicklern:
In Zukunft plant werf die Einführung benutzerkonfigurierbarer Bereitstellungsphasen von Ressourcen. Mit Hilfe solcher Stadien können nicht nur beide Fälle beschrieben werden.
PS
Lesen Sie auch in unserem Blog: