Bei True Engineering haben wir den Prozess der kontinuierlichen Bereitstellung von Updates auf die Server des Kunden eingerichtet und möchten diese Erfahrung teilen.
Zunächst haben wir ein Online-System für den Kunden entwickelt und es in einem eigenen Kubernetes-Cluster bereitgestellt. Nun ist unsere hochverfügbare Lösung auf die Plattform des Kunden umgezogen, für die wir den vollständig automatisierten Prozess des Continuous Deployment eingerichtet haben. Dadurch haben wir die Time-to-Market – die Bereitstellung von Änderungen in der Produktumgebung – beschleunigt.
In diesem Artikel werden wir alle Phasen des Prozesses des Continuous Deployment (CD) oder der Bereitstellung von Updates auf der Plattform des Kunden beschreiben:
- wie dieser Prozess startet,
- Synchronisation mit dem Git-Repository des Kunden,
- Build des Backends und Frontends,
- automatisierte Bereitstellung der Anwendung in der Testumgebung,
- automatisierte Bereitstellung in der Prod.
Während des Prozesses werden wir Details zur Konfiguration teilen.

1. Start des CD
Continuous Deployment beginnt damit, dass der Entwickler Änderungen in den Release-Branch unseres Git-Repositories hochlädt.
Unsere Anwendung basiert auf einer Mikroservice-Architektur, wobei alle Komponenten in einem einzigen Repository gespeichert sind. Dadurch werden alle Mikroservices gesammelt und installiert, selbst wenn sich einer von ihnen ändert.
Wir haben die Arbeit über ein einziges Repository aus mehreren Gründen organisiert:
- Entwicklungsfreundlichkeit – die Anwendung entwickelt sich aktiv weiter, daher kann man sofort mit dem gesamten Code arbeiten.
- Eine einheitliche CI/CD-Pipeline, die garantiert, dass die Anwendung als integriertes System alle Tests durchläuft und im Produktionsumfeld des Kunden bereitgestellt wird.
- Verwirrung bei den Versionen ausschließen – wir müssen keine Versionskarte der Mikroservices aufbewahren und für jeden Mikroservice eine eigene Konfiguration in Helm-Skripten beschreiben.
2. Synchronisierung mit dem Git-Repository des Kunden
Die vorgenommenen Änderungen werden automatisch mit dem Git-Repository des Kunden synchronisiert. Dort ist der Build-Prozess der Anwendung eingestellt, der nach der Aktualisierung des Branches gestartet wird, und die Bereitstellung in der Produktion. Beide Prozesse finden in deren Umgebung aus dem Git-Repository statt.
Wir können nicht direkt mit dem Repository des Kunden arbeiten, da wir eigene Entwicklungs- und Testumgebungen benötigen. Daher verwenden wir unser eigenes Git-Repository, das mit ihrem Git-Repository synchronisiert ist. Sobald der Entwickler Änderungen in den entsprechenden Branch unseres Repositories hochlädt, werden diese Änderungen sofort an den Kunden gesendet.

Danach muss ein Build erstellt werden. Dieser besteht aus mehreren Schritten: dem Build des Backends und des Frontends, dem Testen und der Bereitstellung in der Produktion.
3. Build von Backend und Frontend
Der Build von Backend und Frontend sind zwei parallele Aufgaben, die im GitLab Runner ausgeführt werden. Die Konfiguration des ursprünglichen Builds befindet sich im selben Repository.
.
Der GitLab Runner nimmt den Code aus dem benötigten Repository, erstellt mit dem Build-Befehl der Java-Anwendung und sendet ihn an das Docker-Registry. Hier bauen wir das Backend und das Frontend zusammen, erstellen Docker-Images, die wir im Repository des Kunden ablegen. Zur Verwaltung der Docker-Images verwenden wir den .
Wir synchronisieren die Versionen unserer Images mit der Release-Version, die im Docker bereitgestellt wird. Für einen reibungslosen Betrieb haben wir einige Einstellungen vorgenommen:
1. Die Container zwischen der Testumgebung und der Produktionsumgebung werden nicht neu gebaut. Wir haben Parameter eingerichtet, damit ein und derselbe Container ohne Neuaufbau mit allen Einstellungen, Umgebungsvariablen und Services sowohl in der Testumgebung als auch in der Produktion arbeiten kann.
2. Um die Anwendung über Helm zu aktualisieren, muss die Version angegeben werden. Bei uns sind der Build des Backends, das Frontend und die Aktualisierung der Anwendung drei verschiedene Aufgaben, daher ist es wichtig, überall dieselbe Version der Anwendung zu verwenden. Für diese Aufgabe verwenden wir Daten aus der Git-Historie, da sich die Konfiguration des K8S-Clusters und der Anwendung im selben Git-Repository befindet.
Die Version der Anwendung erhalten wir aus den Ergebnissen des Befehls
git describe --tags --abbrev=7.
4. Automatische Bereitstellung aller Änderungen in der Testumgebung (UAT)
Der nächste Schritt in diesem Build-Skript besteht darin, das K8S-Cluster automatisch zu aktualisieren. Dies erfolgt, sofern die gesamte Anwendung erfolgreich gebaut wurde und alle Artefakte im Docker-Registry veröffentlicht sind. Anschließend wird das Testumfeld aktualisiert.
Das Update des Clusters wird über gestart. Tritt dabei ein Problem auf, so rollt Helm automatisch alle Änderungen zurück. Es ist keine Überwachung erforderlich.
Wir liefern die Konfiguration des K8S-Clusters mit dem Build aus. Daher wird in diesem Schritt die Konfiguration aktualisiert: configMaps, deployments, services, secrets und alle anderen K8S-Konfigurationen, die wir geändert haben.
Danach initiiert Helm das RollOut-Update der Anwendung im Testumfeld, bevor die Anwendung in die Produktionsumgebung gebracht wird. Dies ermöglicht es den Benutzern, die geschäftlichen Features, die wir im Testumfeld bereitgestellt haben, manuell zu überprüfen.
5. Automatische Bereitstellung aller Änderungen im Prod
Um das Update in die Produktionsumgebung zu übertragen, muss nur ein Knopf in GitLab gedrückt werden – und die Container werden sofort in die Produktionsumgebung bereitgestellt.
Ein und dasselbe Anwendung kann ohne Neukompilierung in verschiedenen Umgebungen – Test und Produktion – betrieben werden. Wir verwenden dieselben Artefakte, ohne etwas an der Anwendung zu ändern, während die Parameter von außen bereitgestellt werden.
Die flexible Parametrisierung der Anwendungseinstellungen hängt von der Umgebung ab, in der die Anwendung ausgeführt wird. Wir haben alle Umgebungsparameter nach außen verlagert: alles wird über die K8S-Konfiguration und Helm-Parameter parametriert. Wenn Helm eine Version in der Testumgebung bereitstellt, werden die Testparameter angewendet, in der Produktionsumgebung die Produktionsparameter.
Die größte Herausforderung war es, alle verwendeten Dienste und Variablen, die von der Umgebung abhängen, zu parametrisieren und als Umgebungsvariablen sowie in der Beschreibungs- und Konfigurationsparameter für Helm zu übertragen.
In den Anwendungseinstellungen werden Umgebungsvariablen verwendet. Ihre Werte werden in Containern mithilfe von K8S ConfigMap festgelegt, die mit Go-Templates automatisiert wird. Zum Beispiel kann das Festlegen einer Umgebungsvariable für den Domainnamen so erfolgen:
APP_EXTERNAL_DOMAIN: {{ (pluck .Values.global.env .Values.app.properties.app_external_domain | first) }}
.Values.global.env – In dieser Variablen wird der Name der Umgebung (prod, stage, UAT) gespeichert.
.Values.app.properties.app_external_domain – In dieser Variablen definieren wir im .Values.yaml die benötigte Domain.
Bei der Aktualisierung der Anwendung erstellt Helm anhand der Vorlagen im configmap.yaml und weist das APP_EXTERNAL_DOMAIN den entsprechenden Wert je nach Umgebung zu, in der das Update der Anwendung gestartet wird. Diese Variable wird bereits im Container festgelegt. Der Zugriff erfolgt über die Anwendung, sodass in jeder Umgebung der Anwendung ein anderer Wert für diese Variable vorhanden ist.
Relativ neu in Spring Cloud ist die Unterstützung von K8S, einschließlich der Arbeit mit configMaps: . Da das Projekt aktiv entwickelt wird und sich grundlegend ändert, können wir es nicht in der Produktionsumgebung verwenden. Aber wir überwachen seinen Zustand aktiv und nutzen es in DEV-Konfigurationen. Sobald es stabil läuft, werden wir von der Verwendung von Umgebungsvariablen auf dieses umschalten.
Gesamt
So, Continuous Deployment ist eingerichtet und funktioniert. Alle Updates erfolgen mit einem einzigen Tastendruck. Die Lieferung von Änderungen in die Produktionsumgebung ist automatisiert. Und was wichtig ist, die Aktualisierungen unterbrechen nicht den Betrieb des Systems.

Zukunftspläne: automatische Datenbankmigration
Wir haben über ein Upgrade der Datenbank und die Möglichkeit nachzudenken, diese Änderungen rückgängig zu machen. Immerhin laufen zwei verschiedene Versionen der Anwendung gleichzeitig: die alte Version funktioniert, während die neue hochgefahren wird. Die alte werden wir erst abschalten, wenn wir sicher sind, dass die neue Version funktionsfähig ist. Die Migration der Datenbank sollte es ermöglichen, mit beiden Versionen der Anwendung zu arbeiten.
Deshalb können wir nicht einfach den Namen einer Spalte oder andere Daten ändern. Aber wir können eine neue Spalte erstellen, die Daten aus der alten Spalte kopieren und Trigger schreiben, die beim Aktualisieren der Daten gleichzeitig in der anderen Spalte kopieren und aktualisieren. Nach dem erfolgreichen Deployment der neuen Version der Anwendung können wir nach Ablauf der Phase des post-launch Supports die alte Spalte und den nicht mehr benötigten Trigger löschen.
Wenn die neue Version der Anwendung fehlerhaft arbeitet, können wir auf die vorherige Version zurückspringen, einschließlich der vorherigen Version der Datenbank. Kurz gesagt, unsere Änderungen ermöglichen es, gleichzeitig mit mehreren Versionen der Anwendung zu arbeiten.
Wir planen, die Automatisierung der Datenbankmigration über einen K8S-Job zu realisieren und in den CD-Prozess zu integrieren. Natürlich werden wir diese Erfahrungen auf Habr teilen.
Quelle: habr.com
