Blog Engineering Deployment mit GitLab CI an verschiedene Umgebungen: So funktioniert es
Aktualisiert am: May 16, 2025
14 Minuten Lesezeit

Deployment mit GitLab CI an verschiedene Umgebungen: So funktioniert es

Erfahre, wie du GitLab CI für automatische Deployments in mehrere Umgebungen einrichtest, inklusive AWS S3-Integration und sicherer Variablenverwaltung.

intro.jpg

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

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"

Manual deployment 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: Failed command

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"

Automated deployment 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

Picture of Variables page

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:

Successful build

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.

Automated deployment of main branch 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).

Deployment to two places 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:

List of environments

GitLab bietet eine vollständige Aufzeichnung deiner Bereitstellungen für alle deine aktuellen CI-Umgebungen:

List of deployments to staging environment

Environments

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 …

image11

… und sie in Settings > Integrations > Slack notifications zusammen mit deinem Slack-Benutzernamen einzutragen:

image12

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:

image13

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.

Queue of branches for review on Staging

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

Staging branch 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.

List of environments 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:

Skipped job is available for manual launch

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]How to use GitLab CI - update - 19 - updated

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

  1. 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.

  2. 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.

  3. Mit GitLab CI kannst du flexibel spezifizieren, an welche Branches du deployen willst.

  4. 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.

  5. Für kritische Aspekte deiner Infrastruktur kannst du statt der automatischen Bereitstellung auf eine manuelle umstellen.

Wir möchten gern von dir hören

Hat dir dieser Blogbeitrag gefallen oder hast du Fragen oder Feedback? Erstelle ein neues Diskussionsthema im GitLab Community-Forum und tausche deine Eindrücke aus. Teile dein Feedback

Bist du bereit?

Sieh dir an, was dein Team mit einer einheitlichen DevSecOps-Plattform erreichen könnte.

Kostenlose Testversion anfordern

Finde heraus, welcher Tarif für dein Team am besten geeignet ist

Erfahre mehr über die Preise

Erfahre mehr darüber, was GitLab für dein Team tun kann

Sprich mit einem Experten/einer Expertin