Automatische Deployments: Einsatz und Vorteile bei Liechtenecker
In diesem Beitrag möchte ich euch unsere automatischen Deployments näher bringen. Um euch einen guten Überblick zu verschaffen, werde ich versuchen, alle sich bei uns im Einsatz befindlichen Komponenten grob zu umreißen.
Lange ist’s her, als ich euch für Docker zu begeistern versuchte. In meinem Beitrag Let’s Dockerize everything! habe ich bereits jede Menge Vorteile von Docker zusammengefasst. In diesem Beitrag möchte ich euch – darauf aufbauend – unsere automatischen Deployments näher bringen. Um euch einen guten Überblick zu verschaffen, werde ich versuchen, alle sich bei uns im Einsatz befindlichen Komponenten grob zu umreißen. Daraus resultiert natürlich, dass ich in diesem Beitrag auf keine davon im Detail eingehen werde.
Wir verwenden Docker für unsere lokalen Entwicklungsumgebungen: die Projekte liegen also bereits als Container-Image vor. Da wäre es natürlich schade, wenn wir die Images nicht auch für das Hosting der Applikationen nutzen würden.
Kubernetes
Auf der Suche nach Container-Orchestrierungs-Software stößt man schnell auf Kubernetes, eine der populärsten ihrer Art. Auf der offiziellen Dokumentations-Webseite beschreibt es sich wie folgt:
<em>“Kubernetes ist eine portable, erweiterbare Open-Source-Plattform zur Verwaltung von containerisierten Arbeitslasten und Services, die sowohl die deklarative Konfiguration als auch die Automatisierung erleichtert.”</em> Kubernetes Website
Kubernetes kümmert sich um die “Orchestrierung” von Docker-Containern innerhalb eines Kubernetes Clusters. Gesteuert wird ein Cluster mithilfe der Kubernetes API, über die API Objekte in Kubernetes (PODs, Namespaces, Services, etc.) abgefragt und manipuliert werden können. Des Weiteren gibt es ein offizielles Command Line Tool, kubectl, welches die Steuerung des Clusters im Terminal, bzw. durch Skripte ermöglicht.
Ich muss gestehen, dass das Handling von Kubernetes etwas gewöhnungsbedürftig ist. Speziell wenn man von der klassischen Schiene kommt: auf den Server verbinden, Änderungen vom VCS pullen und Datenbank-Updates ausführen.
Im Gegensatz dazu verbindet man sich nämlich nirgends hin, man pullt keine Änderungen und auch die Updates werden nicht mehr manuell ausgeführt. Stattdessen erstellt/updatet man Kubernetes Objekte über die Kubernetes API. Man informiert Kubernetes sozusagen darüber, welches Docker-Image mit welchem Tag deployed werden soll. Den Rest erledigt dann Kubernetes.
Unter dem Rest kann man sich unter anderem folgendes vorstellen:
- Lastverteilung innerhalb des Clusters
- Load-Balancing von Traffic zwischen mehreren Replikaten einer App
- Healthchecks
- das automatische Neustarten von Apps bei Fehlern
- uvm.
Geil, oder?!
Aufmerksame Leser haben sicherlich bemerkt, dass Datenbank-Updates nicht mehr manuell durchgeführt werden. Demzufolge muss die Applikation soweit automatisiert sein, dass solche Tasks automatisch ausgeführt werden können.
Wir haben für unsere Systeme Update Skripts geschrieben, welche sich um die korrekte Ausführung der Updates kümmern.
Klar: Initial bedeutet das Aufwand. Aber glaubt mir, sobald die Automatisierungs-Skripts geschrieben sind, erleichtern sie einem das Leben ungemein. Es bietet auch mehr Sicherheit, da man im Skript die korrekte Ausführung der Updates sicherstellen kann. Man kann und sollte natürlich auch gleich Backup- und Restore Strategien einbauen.
Soweit so gut.
Ich denke, ihr habt nun einen guten Überblick über Kubernetes.
Falls ihr noch mehr darüber lesen wollt, kann ich euch die offizielle Docs Seite ans Herz legen.
Helm
Also, statt pullen Konfigurationen anwenden. Das klingt erstmal nach einer großen Menge Konfigurationsfiles. Ohne weitere Hilfsmittel landet man schnell in einer Konfigurations-Dateien-Hölle. Speziell wenn man bei komplexeren Projekten mehrere Kubernetes Objekte mit ähnlichen oder gleichen Konfigurationen hat. Als Beispiel kann man sich den Cronjob Container und den PHP-Apache Container vorstellen, die beide dieselben Environment Variablen für die Datenbankverbindung benötigen. In klassischen Kubernetes Konfigurationsfiles müssten diese Werte in 2 Konfigurationsfiles (deployment.yaml und cronjob.yaml) angepasst werden! Und um dieses Problem zu lösen, gibt es Helm.
Helm ist ein Open Source Package Manager für Kubernetes. Anstatt fix-fertige Kubernetes Konfigurationsfiles anzulegen, kann man Helm Templates erstellen. Die Templates können wiederum Platzhalter enthalten, welche dann von Helm mit Werten aus den sogenannten “Values-Files” ersetzt werden.
Man kann also Environment-spezifische Konfigurationen anlegen, indem man pro Environment ein Values-File erstellt.
Wir haben pro System ein Helm Chart mit Templates für alle nötigen Kubernetes-Konfigurationen erstellt, in dem diverse Werte mittels Values-File übergeben werden können. Jedes der Charts lässt sich auf das System optimiert konfigurieren.
Jenkins
Ihr wisst nun, dass wir pro Projekt ein Docker Image haben, welches wir mittels Helm auf einen Kubernetes Cluster deployen. Was noch fehlt, sind automatischen Deployments. Dafür verwenden wir Jenkins, eine der bekanntesten Open Source Automatisierungs-Lösungen.
Falls ihr’s noch nicht bemerkt habt: Wir sind Kubernetes Fans! 😉 Daher haben wir einen Kubernetes Cluster bei DigitalOcean angelegt, auf dem all unsere Develop-, Staging- und QA-Umgebungen gehostet werden. Das hat den Vorteil, dass wir für sehr viele unterschiedliche Projekte und Environments nur sehr wenige Server benötigen.
Und warum erzähle ich das? Weil auf diesem Cluster auch Jenkins mithilfe des offiziellen Helm Charts installiert ist.
Wir haben unsere Jenkins Projekte als Multibranch-Pipeline konfiguriert, welche durch Pushes in ausgewählte Branches eines ausgewählten Git Repositories gestartet wird.
Unsere hausinterne Standard-Pipeline besteht aus folgenden Schritten:
- Docker Tag generieren: wir verwenden dafür den Branch-Namen und den Build-Timestamp → somit haben wir einen eindeutigen Tag-Namen pro Build
- Docker Images builden: für unsere Zwecke bauen wir 2 Docker Images parallel:
- ein Test-Image inklusive DEV-Dependencies (also dem Test-Framework, etc.)
- das Haupt-Image ohne DEV-Dependencies, welches dann unter dem Tag in unsere Docker Registry gepusht wird
- Automatisierte Tests im Test-Image ausführen
- Haupt-Image mit dem Docker-Image-Tag in ein privates Repository auf Dockerhub pushen
- in allen nicht-produktiv-Umgebungen: Upgrade vom Helm Release mit einem branch-spezifischen Values-File
Der letzte Punkt wird natürlich nur in Test-Environments automatisch durchgeführt. Für Releases in Produktivumgebungen muss dieser Schritt zur Sicherheit manuell gestartet werden.
Wenn einer der Build-Steps fehlschlägt, bricht die komplette Pipeline ab. Somit wird sichergestellt, dass keine Updates deployed werden, wenn beispielsweise die Tests fehlschlagen.
Für uns Entwickler bedeutet das, dass wir nur noch Features in Environments mergen müssen. Das Bauen der Docker-Images und das Update der Helm Releases passiert dann automatisch via Jenkins.
Durch die automatischen Deployments ergeben sich unter anderem folgende Vorteile:
- einheitliche Deployment Strategie über alle Projekte
- erhöhte Systemstabilität durch automatisierte Tests
- erhöhte Sicherheit durch automatisierte Backups vor Deployments
- Verhinderung menschlicher Fehler bei Deployments
Sobald die Helm-Charts und Pipelines initial eingerichtet wurden, lassen sich diese sehr einfach auf neue Projekte übertragen.
Ich hoffe, ich konnte einige von euch überzeugen, sich mit den Systemen auseinanderzusetzen. Wir arbeiten nun seit ungefähr eineinhalb Jahren damit und sind sehr zufrieden. Wir verbessern natürlich auch regelmäßig die einzelnen Komponenten unserer Automatisierungssysteme. Also, je früher man damit startet, desto früher kann man mit den Erweiterungen beginnen, die einem das Leben noch leichter machen! 🙂
Du willst mit jemanden über das Thema plaudern?
Einen kostenlosen Termin mit CEO Susanne vereinbaren!AI-Driven UX - Möglichkeiten, Design-Prinzipien und Pflichten für UX-Designer - 2024 Update
UPDATE 2024: Ausgegraben aus 2019 dieses schmucke Fundstück über AI und UX. Irgendwie drehen sich die Trend-Themen doch alle Jahre im Kreis und man könnte glauben man findet sich diesbezüglich als Bill Murray in "Täglich grüßt das Murmeltier [...]
Jetzt lesenFolge #62 mit Susanne Liechtenecker
In Folge 62 besinnt sich Susanne auf die Anfänge dieses Podcasts und begrüßt keinen Gast, sondern erzählt über das Buch "Jäger, Hirten, Kritiker" von Richard David Precht und warum es sie inspiriert hat.
Jetzt anhören