In diesem ausführlichen Artikel wollen wir dir zeigen, wie du GitLab CI für eine automatisierte Kompilierung und Bereitstellung nutzen kannst. Als Ausgangspunkt haben wir das folgende Szenario gewählt: Du bist der (die) glückliche Besitzer(in), Redakteur(in) und alleinige Entwickler(in) eines imaginären Nachrichtenportals.
Da du deinen Projekt-Code bereits auf GitLab.com hostest, ist dir bewusst, dass du mit GitLab CI/CD-Tests durchführen kannst. Jetzt aber möchtest du wissen, ob das Tool auch für deine Bereitstellung verwendet werden kann - und was für Optionen dir dafür zur Verfügung stehen.
Um nicht von den Details spezifischer Tech-Stacks abgelenkt zu werden, gehen wir in unserem Beispiel davon aus, dass die App nur aus HTML-Dateien besteht. Es gibt keinen serverseitigen Code, keine komplizierte Kompilierung der JavaScript-Assets.
Als Zielplattform wählen wir Amazon S3 - ebenfalls eine einfache Lösung.
Achtung: Ziel des Artikels ist es nicht, dir möglichst viele kleinteilige Bausteine zu bieten, die du dann mit Kopieren und Einfügen in deinen Code integrierst. Vielmehr möchten wir dir die Prinzipien und Funktionalitäten von GitLab CI vermitteln, so dass du sie einfacher auf deinen Tech-Stack anwenden kannst.
Inhaltsverzeichnis
- Der Anfang der Geschichte
- Die erste automatisierte Bereitstellung
- Wie Teams GitLab CI für die Bereitstellung nutzen können
- Einführung: Umgebungen (environments)
- Fehlerbehebung bei der Bereitstellung
- Slack-Benachrichtigungen für Bereitstellungen
- Skalierbarkeit von Teamarbeit
- Fünf Kernpunkte
Lass uns ganz am Anfang beginnen. Da, wo es noch keine kontinuierliche Integration (continuous integration, CI) gibt.
Der Anfang der Geschichte
Deployment: Was verstehen wir unter dem Begriff „Bereitstellung"? In unserem Fall möchten wir, dass eine große Zahl an HTML-Dateien in deinem S3-Bucket - der bereits für statisches Webseiten-Hosting konfiguriert wurde - erscheint.
Hier führen unzählige Wege nach Rom. In unserem Beispiel werden wir die awscli-Bibliothek von Amazon selbst verwenden.
So sieht der vollständige Befehl aus:
aws s3 cp ./ s3://yourbucket/ --recursive --exclude "*" --include "*.html"
Die Übertragung des Code mittels Push-Befehl in ein Repository und die eigentliche Bereitstellung sind zwei voneinander unabhängige Prozesse.
Wichtiges Detail: Der Befehl erwartet von dir, dass du AWS_ACCESS_KEY_ID
und AWS_SECRET_ACCESS_KEY
Umgebungs-Variablen bereitstellst. Das bedeutet, dass du gegebenenfalls die AWS_DEFAULT_REGION
festlegen musst.
Lass uns nun versuchen, diesen Prozess mit GitLab CI zu automatisieren.
Die erste automatisierte Bereitstellung
Mit GitLab macht es keinen Unterschied, welche Befehle du verwendest. Du kannst GitLab so einrichten, dass es genau auf deine persönlichen Bedürfnisse zugeschnitten ist und wie ein lokales Terminal auf deinem Rechner funktioniert.
Solange du von dort aus die Befehle ausführst, kannst du CI damit beauftragen, dasselbe für dich in GitLab zu tun. Platziere dein Script einfach in .gitlab-ci.yml,pushe deinen Code –und siehe da: CI erzeugt einen Job und führt deine Befehle aus.
Um unser Ausgangsszenario ein wenig auszuschmücken, fügen wir ihm nun ein wenig Kontext hinzu: Unsere Webseite ist klein, sie hat täglich 20-30 Besucher und das Code-Repository besitzt nur einen einzigen Standard-Branch: main
.
Unser Ziel: Das Einrichten einer automatisierten Bereitstellung.
Lass uns damit anfangen, dass wir den oben erwähnten Befehl verwenden, um in der .gitlab-ci.yml-Datei einen Job zu spezifizieren:
deploy:
script: aws s3 cp ./ s3://yourbucket/ --recursive --exclude "*" --include "*.html"
Hat leider nicht geklappt:
Es ist unsere Aufgabe dafür zu sorgen, dass eine ausführbare aws
-Datei vorliegt. Um awscli
installieren zu können, benötigen wir pip
, ein Tool zur Installation von Python-Paketen. Unser Vorschlag: Spezifiziere dafür ein Docker-Image mit vorinstalliertem Python. Das nämlich sollte pip
beinhalten.
deploy:
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://yourbucket/ --recursive --exclude "*" --include "*.html"
You push your code to GitLab, and it is automatically deployed by CI.
Du pushst deinen Code zu GitLab und dadurch wird dieser automatisch von CI bereitgestellt. Damit hast du dein erstes Ziel einer automatisierten Kompilierung und Bereitstellung erreicht.
Die Installation von awscli
verlängert die benötigte Zeit, den Job auszuführen. Das aber soll uns im Augenblick nicht stören. Wenn du den Prozess beschleunigen musst, kannst du jederzeit nach einem Docker-Image mit vorinstalliertem awscli
suchen oder selbst ein solches Image erstellen.
Wir sollten außerdem die folgenden Gitlab-CI-Environment-Variablen nicht vergessen, die du dir gerade aus der AWS-Konsole gezogen hast:
variables:
AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
deploy:
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://yourbucket/ --recursive --exclude "*" --include "*.html"
Das sollte zwar an sich funktionieren,aber es ist dennoch eine gute Idee, geheime Schlüssel zu schützen - sogar in einem privaten Repository. Suchen wir also mal nach einer Lösung.
Wie man Geheimes geheim hält
Es gibt in GitLab einen eigenen Ort für geheime Variablen: Settings > CI/CD > Variables
Alles, was du dort ablegst, wird in GitLab-CI-Umgebungs-Variablen verwandelt.
Wenn du nun das Kontrollkästchen für Maskenvariablen (mask variables) markierst, obfuskierst du die Variablen im Job-Log. Das bedeutet, dass du den Zugriff Dritter auf diese Daten erheblich erschwerst. Als Nächstes setzt du ein Häkchen im Kontrollkästchen „Variable schützen” (Protect variable). Dadurch wird die entsprechende Variable nur noch über Pipelines exportiert, die auf geschützten Branches und Tags laufen. Nur Nutzer(innen) mit „Owner”- oder „Maintainer”-Status haben Zugriff auf diesen Bereich.
Wir könnten Umgebungs-Variablen aus unserer GitLab-CI-Konfiguration entfernen. Stattdessen aber wollen wir sie zu einem anderen Zweck verwenden.
Wie du nicht geheime Variablen spezifizierst und nutzt
Wenn deine Konfiguration wächst, kann es nützlich sein, einige der Parameter zu Beginn der Konfiguration als Variablen zu belassen. Das gilt umso mehr, wenn du sie an mehr als einer Stelle verwendest. Obwohl das in unserer Situation nicht der Fall ist, wollen wir den S3-Bucket-Namen als Variable verwenden, um das Prinzip zu verdeutlichen:
variables:
S3_BUCKET_NAME: "yourbucket"
deploy:
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude "*" --include "*.html"
Soweit so gut:
In unserem hypothetischen Szenario ist es dir gelungen, mehr Besucher auf deine Seite zu bekommen. Daher unterstützt dich jetzt ein(e) Entwickler(in).
Werfen wir deswegen einen Blick darauf, wie Teamarbeit den GitLab-CI-Workflow verändert.
Wie Teams GitLab CI für die Bereitstellung nutzen können
Da nun zwei Mitarbeiter(innen) am gleichen Repository arbeiten, ist es nicht mehr sinnvoll, die main
-Branch für die Bereitstellung zu nutzen. Deswegen entscheidest du dich dafür, zwei separate Branches zu erzeugen: Eines für neue Features und das andere für neue Artikel. Am Endes willst du beide dann in main
zusammenführen.
Dabei gibt es aber leider ein Problem – deine aktuelle CI-Konfiguration interessiert sich nicht für Branches. Sobald du etwas zu GitLab pushst, wird es auch für S3 bereitgestellt.
Zum Glück lässt sich dieses Problem recht einfach beheben. Füge lediglich only: main
zu deinem deploy
-Job hinzu.
Du willst deiner Produktions-Website zwar nicht jede Branch hinzufügen, aber es wäre durchaus gut, wenn du dir deine Änderungen an Feature-Branches per Vorschau ansehen könntest.
Wie du einen separaten Ort für das Testen von Code einrichtest
Dein Entwickler, nennen wir ihn Patrick, erinnert dich daran, dass es ein Feature namens GitLab Pages gibt. Es scheint ideal dafür zu sein, dir eine Vorschau dessen zu bieten, woran du gerade arbeitest.
Um Websites auf GitLab Pages zu hosten, sollte deine CI-Konfiguration drei einfache Voraussetzungen erfüllen:
- Der Job sollte als
pages
angelegt werden - Es sollte einen
artifacts
-Bereich mit einem öffentlichen Ordner geben - Du solltest alles, was du hosten willst, in den
public-
Ordner legen
Die Inhalte des öffentlichen Ordners werden an folgendem Ort gehostet: http://<username>.gitlab.io/<projectname>/
Nach der Anwendung der Beispielkonfiguration für plain-html-Websites, sieht die vollständige CI-Konfiguration so aus:
variables:
S3_BUCKET_NAME: "yourbucket"
deploy:
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude "*" --include "*.html"
only:
- main
pages:
image: alpine:latest
script:
- mkdir -p ./public
- cp ./*.html ./public/
artifacts:
paths:
- public
except:
- main
Wir haben zwei Jobs spezifiziert. Ein Job stellt die Website für deine Kunden auf S3 bereit (deploy
). Die andere (pages
) stellt die Website auf GitLab Pages bereit. Aus diesem Grund nennen wir sie jeweils „Produktionsumgebung” und „Prüfungsumgebung” (Staging Environment).
Alle Branches, mit Ausnahme von main, werden auf GitLab Pages bereitgestellt.
Einführung: Umgebungen (environments)
GitLab bietet Support für Umgebungen (einschließlich dynamischer und statischer Umgebungen). Dazu musst du lediglich die zutreffende Umgebung für den jeweiligen Deployment-Job festlegen:
variables:
S3_BUCKET_NAME: "yourbucket"
deploy to production:
environment: production
image: python:latest
script:
- pip install awscli
- aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude "*" --include "*.html"
only:
- main
pages:
image: alpine:latest
environment: staging
script:
- mkdir -p ./public
- cp ./*.html ./public/
artifacts:
paths:
- public
except:
- main
GitLab trackt deine CI-Bereitstellungen. So weißt du jederzeit, was aktuell auf deinen Servern bereitgestellt wird:
GitLab bietet eine vollständige Aufzeichnung deiner Bereitstellungen für alle deine aktuellen CI-Umgebungen:
Nun, da wir alles automatisiert und eingerichtet haben, können wir uns den neuen Herausforderungen stellen, die uns erwarten.
Fehlerbehebung bei der Bereitstellung
Und da ist es schon wieder passiert: Du hast deine Feature-Branch gepusht, um sie in der „Staging-Umgebung” in der Vorschau zu sehen und nur eine Minute später hat Patrick seine Branch gepusht. Die Folge: Die Staging-Umgebung wurde mit seinem Beitrag überschrieben. Wie ärgerlich!! Das passiert heute schon zum dritten Mal!
Vorschlag: Warum verwenden wir nicht Slack, um uns über CI-Bereitstellungen auf dem Laufenden zu halten? So können wir verhindern, dass wir uns bei der Bereitstellung gegenseitig in die Quere kommen.
Lerne, wie man GitLab in Slack integriert.
Slack-Benachrichtigungen für Bereitstellungen
Das Einrichten von Slack-Benachrichtigungen ist ein recht unkomplizierter Vorgang.
Der Gedanke dahinter ist, die eintreffende WebHook-URL von Slack zu nehmen …
… und sie in Settings > Integrations > Slack notifications zusammen mit deinem Slack-Benutzernamen einzutragen:
Das einzige, worüber du auf dem Laufenden gehalten werden möchtest, sind Bereitstellungen. Deswegen kannst du die Häkchen aus allen Kontrollkästchen außer dem für „Deployment” in den obengenannten Einstellungen entfernen. Das war’s auch schon. Ab jetzt wirst du über jede erfolgte Bereitstellung informiert:
Skalierbarkeit von Teamarbeit
Einige Zeit später ist deine Website wirklich beliebt geworden und dein Team ist von zwei auf acht Mitarbeiter angewachsen. Diese arbeiten parallel an Entwicklungs-Jobs. So kommt es recht häufig vor, dass mehrere von ihnen aufeinander warten müssen, weil jemand gerade eine Vorschau in der Staging-Umgebung durchführt. Damit ist die Idee, jede Branch in Staging bereitzustellen, obsolet geworden.
Es ist an der Zeit, den Prozess ein letztes Mal zu modifizieren. Du und dein Team sind zu dem Entschluss gekommen, dass alle, die ihre Änderungen auf dem Staging-Server ansehen möchten, diese zuerst mit der Staging-Branch zusammenführen sollen.
Dazu bedarf es nur einer minimalen Änderung von .gitlab-ci.yml
:
except:
- main
Es wird zu:
only:
- staging
Die Mitarbeiter müssen nun ihre Feature-Branches zusammenführen, bevor sie diese auf dem Staging-Server als Vorschau betrachten können.
Natürlich erfordert dies zusätzliche Zeit und einen Mehraufwand für das Mergen. Aber alle stimmen überein, dass dies besser ist, als jedes Mal zu warten.
Wie du mit Notfällen umgehst
Du kannst nicht alles kontrollieren. Manchmal geht einfach etwas schief. Nehmen wir ein Beispiel: Ein(e) Mitarbeiter(in) hat die Branches nicht korrekt zusammengeführt und das Ergebnis direkt in die Produktions-Umgebung gepusht - genau zu einem Zeitpunkt, als deine Website bei HackerNews ganz oben stand! Tausende Besucher haben so dein komplett zerschossenes Layout gesehen, statt deiner eigentlich so schönen Main-Page.
Zum Glück hat ein Team-Mitglied den Rollback-Button entdeckt. Damit konntest du die Website bereits eine Minute, nachdem das Problem entdeckt wurde, auf den alten Stand zurücksetzen.
Der Rollback-Button erlaubt es, eine Webseite schnell auf den Ursprungszustand zurückzusetzen.
Rollback führt alles zu dem alten Job mit dem vorigen commit zurück.
Trotzdem stellt dich diese Lösung des Problems noch nicht zufrieden. Du entschließt dich, die automatische Bereitstellung in die Produktions-Umgebung auszuschalten und stattdessen zum manuellen CI-Deployment zurückzukehren. Dazu fügst du deinem Job when: manual
hinzu.
Wie du bereits erwartet hast, erfolgen ab jetzt keine automatischen Bereitstellungen in die Produktion mehr. Um eine manuelle Bereitstellung durchzuführen, gehe zu CI/CD > Pipelines, und klicke auf den Button:
Springen wir nun in die Zukunft. Endlich ist dein Unternehmen zu einer Aktiengesellschaft herangewachsen. Inzwischen arbeiten hunderte Mitarbeiter(innen) an der Website. Das bedeutet, dass die Kompromisse aus der Vergangenheit nicht mehr praxistauglich sind.
Es wird Zeit, Review Apps zu verwenden
Der nächste logische Schritt besteht darin, ein temporäres Objekt der Applikation über die Feature-Branch zum Prüfen zu booten.
In unserem Fall richten wir dazu einen weiteren S3-Bucket ein. Der einzige Unterschied besteht darin, dass wir die Inhalte unserer Website in einen „Ordner” mit dem Namen der Entwicklungs-Branch kopieren. Nun sieht die URL so aus:
http://<REVIEW_S3_BUCKET_NAME>.s3-website-us-east-1.amazonaws.com/<branchname>/
Hier ist der Ersatz für den pages
-Job, den wir zuvor benutzt haben:
review apps:
variables:
S3_BUCKET_NAME: "reviewbucket"
image: python:latest
environment: review
script:
- pip install awscli
- mkdir -p ./$CI_BUILD_REF_NAME
- cp ./*.html ./$CI_BUILD_REF_NAME/
- aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude "*" --include "*.html"
Es ist interessant zu hinterfragen, woher wir diese $CI_BUILD_REF_NAME
-Variable bekommen haben. GitLab CI definiert viele Umgebungs-Variablen vor, so dass du sie direkt in deinen Jobs verwenden kannst.
Beachte, dass wir die S3_BUCKET_NAME
-Variable im Job definiert haben. Damit kannst du Definitionen auf höchstem Level umschreiben.
Hier ist eine visuelle Darstellung dieser Konfiguration:
![Review apps]
Wie genau die Review Apps implementiert werden, hängt von einer Vielzahl Faktoren ab, darunter dein Tech-Stack und dein Bereitstellungs-Prozess. Das ist sehr komplex und deswegen wollen wir in diesem Blogpost nicht näher darauf eingehen.
Immerhin können wir mit Gewissheit behaupten, dass der Prozess sich nicht mehr ganz so einfach darstellen wird wie noch bei unserer statischen html-Website. An dieser Stelle sei nur ein Beispiel genannt: Du musst diese Objekte temporär anlegen. Wenn du sie nun mit der gesamten benötigten Software und allen Diensten automatisch hochfahren möchtest, ist das keine triviale Angelegenheit mehr. Dennoch ist es machbar, vor allem, wenn du Docker-Container verwendest - oder zumindest Chef oder Ansible.
Wir werden uns mit Docker-Bereitstellungen in einem zukünftigen Blogpost beschäftigen. Ich fühle mich ehrlich gesagt ein wenig schuldig, dass ich mich bei der Diskussion des Bereitstellungs-Prozesses nur auf das einfache Kopieren von html-Dateien beschränke und kein einziges wirklich anspruchsvolles Szenario durchgehe. Wenn du dringend mehr Informationen in diese Richtung benötigst, empfehle ich dir den englischsprachigen Artikel „Building an Elixir Release into a Docker image using GitLab CI."
In diesem Artikel aber möchte ich nur noch einen letzten Punkt behandeln.
Bereitstellung auf verschiedenen Plattformen
Im echten Leben müssen wir uns nicht auf S3 und GitLab Pages beschränken. Wir hosten unsere Apps und Pakete auf verschiedenen Diensten - gleiches gilt somit auch für unsere CI-Bereitstellungen.
Darüber hinaus kann auch der Fall eintreten, dass du dich dazu entschließt, auf eine neue Plattform zu migrieren. In dem Fall müsstest du alle deine Bereitstellungs-Skripte neu schreiben. Um den Aufwand zu minimieren, kannst du ein ungemein wertvolles Tool namens dpl
benutzen.
In den Beispielen oben haben wir awscli
verwendet, um den Code an einen Beispiel-Dienst zu liefern (in unserem Fall Amazon S3). Unabhängig davon aber, welches Tool und welches Zielsystem du verwendest, bleibt das Prinzip dasselbe: Du führst einen Befehl mit bestimmten Parametern aus und identifizierst dich mit einem geheimen Schlüssel.
Das dpl
-Bereitstellungs-Tool nutzt dieses Prinzip und bietet ein einheitliches Interface für diese Liste von Providern an.
So sähe ein Produktions-Bereitstellungs-Job aus, wenn wir dpl
nutzen:
variables:
S3_BUCKET_NAME: "yourbucket"
deploy to production:
environment: production
image: ruby:latest
script:
- gem install dpl
- dpl --provider=s3 --bucket=$S3_BUCKET_NAME
only:
- main
Wenn du an verschiedene Systeme bereitstellen möchtest oder öfter die Ziel-Plattform änderst, ergibt es Sinn, über die Nutzung von dpl
nachzudenken, um deine Bereitstellungs-Skripte einheitlich zu halten.
Fünf Kernpunkte
-
Eine Bereitstellung ist nichts weiter als ein Befehl (oder eine Kombination von Befehlen), die regelmäßig ausgeführt werden. Deswegen kannst du Bereitstellungen über GitLab CI laufen lassen.
-
In den meisten Fällen wirst du einen oder mehrere geheime Schlüssel verwenden müssen, um die Befehle ausführen zu können. Speichere diese geheimen Schlüssel in Settings > CI/CD > Variables.
-
Mit GitLab CI kannst du flexibel spezifizieren, an welche Branches du deployen willst.
-
Wenn du Bereitstellungen an verschiedene CI-Umgebungen durchführen möchtest, speichert GitLab die Bereitstellungen. Das erlaubt es dir, einen Rollback zu einer früheren Version durchzuführen.
-
Für kritische Aspekte deiner Infrastruktur kannst du statt der automatischen Bereitstellung auf eine manuelle umstellen.