Blog

Sieben Tipps zum Schreiben besserer GitLab-Pipelines

Jeffrey Zaayman

Aktualisiert Oktober 15, 2025
10 Minuten

Im Bereich DevOps ist eine reibungslose CI/CD-Pipeline mehr als nur eine Annehmlichkeit - sie ist das Rückgrat einer effizienten Softwarebereitstellung. Aber nutzen Sie die Pipeline-Funktionen von GitLab auch optimal? Egal, ob Sie ein Neuling oder ein erfahrener Experte sind, in diesem Blog möchten wir Ihnen einige Tipps vorstellen, die Ihre Pipelines auf Touren bringen können. Lassen Sie uns Ihre GitLab-Reise optimieren, erneuern und verbessern.

Sie können alle Beispiele in einem Repository finden hier .

Tipp 1: Schließen Sie vererbtes Verhalten mit null

Durch das Erweitern einer Vorlage erhalten Sie alle Funktionen dieser Vorlage, aber was ist, wenn Sie an einigen Teilen nicht interessiert sind?

.template:
  before_script: echo "Setting up"
  script:
  - echo "Template behaviour"
  after_script: echo "Cleaning up"

job1:
  extends: .template
  after_script: null  # <-- Excludes after_script

In diesem Beispiel möchte ich, dass mein Job die Elemente after und script der Vorlage implementiert, aber ich möchte nicht after_script. Um das Verhalten abzubrechen, verweise ich explizit auf das Element, das ich nicht in meinem Job haben möchte, setze aber seinen Wert auf null.

Ausgabe eines GitLab-Auftrags

Das Ergebnis: "Aufräumen" wird nicht gedruckt.

Tipp 2: Erweitern Sie mehrere Vorlagen für detaillierteres Verhalten

Natürlich halten Sie Ihre .gitlab-ci.yml Datei so schlank wie möglich und nehmen Vorlagen auf und erweitern sie, damit Sie sich nicht wiederholen. Aber wussten Sie, dass Sie mehrere Vorlagen erweitern können?

Nehmen wir an, Sie haben eine oder mehrere Vorlagen, die etwas zu viel tun und deren Verhalten Sie oft nur zum Teil benötigen. Anstatt Ihren Code mit mehreren zu spicken (wie im vorherigen Beispiel), um das Verhalten, das Sie nicht wünschen, zu deaktivieren, teilen Sie diese Vorlagen in kleinere, gezieltere Vorlagen auf. Sie können dann mehrere Vorlagen erweitern, um das von Ihnen gewünschte Verhalten zu mischen und anzupassen.

.template-setup:
before_script:
- echo "Setting up"

.template-cleanup:
after_script:
- echo "Cleaning up"

.template-script:
script:
- echo "Template behaviour"

job1:
extends:
- .template-setup # <-- Explicitly
- .template-script # <-- inheriting
- .template-cleanup # <-- behaviour

In diesem Beispiel stammen die Verhaltensweisen before_script, after_script und script aus separaten Vorlagen, was den zusätzlichen Vorteil hat, dass die Funktion des Jobs deutlicher wird.

Ausgabe eines GitLab-Auftrags

Hier lief der Code aller 3 Vorlagen, ohne dass Sie etwas explizit für den Auftrag schreiben mussten.

Beachten Sie, dass, wenn Sie zwei oder mehr Vorlagen erweitern, die dieselben Elemente enthalten (z.B. script), nur das Element der letzten Vorlage ausgeführt wird (aber es gibt eine Lösung für dieses Problem im nächsten Tipp).

.template-setup:
before_script:
- echo "Setting up"

.template-cleanup:
after_script:
- echo "Cleaning up"

.template-script1:
script:
- echo "Template behaviour 1"

.template-script2:
script:
- echo "Template behaviour 2"

job1:
extends:
- .template-setup
- .template-script1 # <-- This gets overridden
- .template-script2 # <-- by this
- .template-cleanup
Ausgabe eines GitLab-Auftrags

Hier laufen before_script und after_script, aber nur .template-script2's script läuft.

Tipp 3: Verwenden Sie !reference, um das Verhalten von erweiterten Vorlagen oder Aufträgen wieder einzuführen

Hier ist ein Szenario: Sie möchten zwei oder mehr Vorlagen erweitern, weil sie das von Ihnen gewünschte Verhalten aufweisen (z.B. image oder allow_failure). Außerdem möchten Sie z.B., dass die beiden script Elemente ausgeführt werden. Wie wir im vorigen Tipp gesehen haben, wird in diesem Fall nur das script Element der letzten Vorlage ausgeführt. Wie können wir alle script Elemente ausführen oder sogar unser eigenes Verhalten hinzufügen? Die Antwort lautet !reference.

.template-setup:
before_script:
- echo "Setting up"

.template-cleanup:
after_script:
- echo "Cleaning up"

.template-script1:
image: alpine:latest
script:
- echo "Template behaviour 1"

.template-script2:
allow_failure: true
script:
- echo "Template behaviour 2"
- echo "More template behaviour 2"

job1:
extends:
- .template-setup
- .template-script1
- .template-script2
- .template-cleanup
script:
- !reference [.template-script1, script] # <-- Explicitly reference the script
- !reference [.template-script2, script] # <-- Explicitly reference the script
- echo "Custom behaviour"

In diesem Beispiel möchte ich image von .template-script1 und allow_failure von .template-script2 (Ich weiß, ich könnte einfach schreiben allow_failure in meinem Job, aber diese Beispiele dienen nur der Veranschaulichung), aber ich möchte auch, dass beide ihre script Elemente. Mit !reference kann ich diese script Elemente wieder einfügen und alles laufen lassen, einschließlich einiger benutzerdefinierter Codes.

Ausgabe eines GitLab-Auftrags

Hier sehen wir, dass das Verhalten aller Vorlagen ausgeführt wurde, einschließlich des benutzerdefinierten Codes des Auftrags.

Hinweis: !reference führt das gesamte Verhalten des Zielelements wieder ein. Wenn das Skriptelement der Vorlage ein Array mit mehreren Zeilen ist (wie in .template-script2), erhalten Sie alle - es gibt keine Möglichkeit, bestimmte Zeilen herauszupicken. Wenn Sie jedoch bereits mehrere Elemente erweitern (wie in Tipp 2 gezeigt), können Sie die Liste der script Elemente in eigene Vorlagen aufteilen und diese mischen, um die gewünschten Ergebnisse zu erzielen.

Tipp 4: Verwenden Sie !reference, um eine Vorlage zu erweitern, ohne sie tatsächlich zu erweitern.

!reference kann zwar nützlich sein, um ein Element aus einer Vorlage, die Sie erweitern, wieder einzuführen, aber manchmal wollen Sie nur ein bestimmtes Verhalten aus dieser Vorlage. Anstatt die Vorlage zu erweitern und viele nullzu verwenden, um das Verhalten, das Sie nicht wollen, zu negieren, erweitern Sie sie einfach nicht und verwenden stattdessen !reference.

.template:
image: python:3.7
allow_failure: true
before_script: echo "Setting up"
script:
- echo "Template behaviour"
- echo "More template behaviour"
after_script: echo "Cleaning up"

job1:
before_script: !reference [.template, before_script] # <-- No need to extend
script:
- echo "Custom behaviour"

In diesem Beispiel macht .template eine Menge, was wir nicht wollen. Indem wir die Vorlage jedoch nicht erweitern und einfach auf ein bestimmtes Element verweisen, erhalten wir nur das gewünschte Verhalten.

Ausgabe eines GitLab-Auftrags

Die Ausgabe ist das von uns gewünschte Verhalten und ein benutzerdefiniertes Verhalten.

Hinweis: Sie können auch !reference Elemente aus anderen Aufträgen, nicht nur aus Vorlagen, verwenden.

Tipp 5: Verwenden Sie needs, um einen Auftrag in der nächsten Phase zu starten, bevor die aktuelle Phase abgeschlossen ist.

Standardmäßig laufen die Aufträge parallel und die Phasen werden nacheinander ausgeführt. Das Schlüsselwort sorgt dafür, dass Aufträge in einer bestimmten Reihenfolge innerhalb einer Phase ausgeführt werden. Aber needs hat auch die Möglichkeit, Aufträge in anderen Stages in eine Warteschlange zu stellen.

stages:
- build
- test

# Build stage
compile:
stage: build
script:
- echo "Compiling"

lint:
stage: build
needs: [compile]
script:
- echo "Linting"

# Test stage
prepare:
stage: test
needs: [compile] # <-- This job will run after compile
script:
- echo "Preparing"

unittest:
stage: test
script:
- echo "Unit testing"

Wenn Sie einen Auftrag (prepare) in der nächsten Phase (test) so konfigurieren, dass er einen Auftrag (compile) in der aktuellen Phase (build) benötigt, dann wird dieser Auftrag in der späteren Phase gestartet, obwohl die aktuelle Phase noch nicht beendet ist.

Etappen eines GitLab-Auftrags

Wie Sie sehen, wird der Auftrag prepare in der Phase test gestartet, nachdem compile in der Phase build beendet wurde, und läuft zur gleichen Zeit wie der Auftrag lint.

Der Auftrag unittest in der Phase test verhält sich wie ein normaler Auftrag und startet erst, wenn die Phase build beendet ist.

Wenn Sie also Aufträge in späteren Phasen haben, die nicht von früheren Aufträgen abhängen und parallel laufen können, wird dies Ihre Pipeline beschleunigen.

Eine andere Möglichkeit besteht darin, die Etappen ganz abzuschaffen.

Tipp 6: Verwenden Sie YAML-Anker und Aliasnamen

Während die Verwendung von Vorlagen, extends und !reference gute Optionen sind, um Ihren Code DRY zu halten, sollten Sie nicht vergessen, dass YAML selbst über ein Template-Verhalten verfügt.

Sie können ein einzelnes Element oder einen ganzen Block referenzieren und sogar Teile davon mit Hilfe von YAML-Ankern und -Aliasen außer Kraft setzen.

.script-1: &script-1 # <-- Sets up an anchor
echo "Template behaviour 1"

.script-2: &script-2 # <-- Sets up an anchor
- echo "Template behaviour 2.1"
- echo "Template behaviour 2.2"

.template-of-templates: &main-template # <- Sets up an anchor
before_script: echo "Setting up"
script: &merged-script # <-- Sets up an anchor
- *script-1 # <-- Implements &script-1
- *script-2 # <-- Implements &script-2
after_script: echo "Cleaning up"

.template1:
script:
- *script-1 # <-- Implements &script-1
- echo "Custom behaviour"

.template2:
before_script: echo "Custom setup"
script: *merged-script # <-- Implements &merged-script

.template3:
<<: *main-template # <-- Implements the whole template
before_script: echo "Setting up in a different way"

Das obige Beispiel zeigt verschiedene Möglichkeiten zur Verwendung von Ankern und Aliasen.

  • In .template1 sind wir nur an der Implementierung einer Skriptzeile aus .script-1
  • In .template2 wollen wir das Verhalten des zusammengeführten Skripts von template-of-templates, aber unser eigenes benutzerdefiniertes Verhalten before_script
  • In .template3 möchten wir das gesamte template-of-templates duplizieren, aber before_script mit einem benutzerdefinierten Verhalten überschreiben.

Die Verwendung von Ankern und Aliasen reduziert die Menge an Boilerplate-Code, die Sie in Ihren Vorlagen benötigen.

Hinweis: Ein Alias in einer Datei kann nicht auf einen Anker in einer anderen Datei verweisen. Die Anker und Aliase müssen sich alle in der gleichen Datei befinden.

Tipp 7: Wie man mit Monorepos umgeht

Wussten Sie, dass Google ein Monorepo verwendet? Der gesamte Code für Google Mail, Dokumente, alles befindet sich in einem einzigen großen Repository.

Dieser Artikel wird sich zwar nicht mit den Vor- und Nachteilen der Verwendung von Monorepos befassen, aber vielleicht arbeiten Sie ja gerade an einem solchen.

Eines der größten Probleme bei einem Monorepo ist die Isolierung von Build-Auslösern. Sie wollen nicht, dass ein Push von jemandem aus Team X einen Build Ihres Codes in einem anderen Verzeichnis auslöst. Wie können Sie also alle Vorteile von CI/CD nutzen, ohne bei jedem Commit Dutzende von unverbundenen Builds auszulösen?

Die Antwort lautet: rules.

Um mit einer Monorepo zu arbeiten, müssen Sie in jedem Unterverzeichnis des Projekts eine YAML-Datei ablegen (es ist hilfreich, der Datei ein Präfix zu geben, damit Sie sie auseinanderhalten können, wenn Sie mehrere Dateien bearbeiten).

Ihre .gitlab-ci.yml Datei wird unglaublich schlank sein und möglicherweise nur aus einer Reihe von Include-Anweisungen bestehen.

include:
- local: /docker/gitlab-ci-docker.yml
- local: /python/gitlab-ci-python.yml

Im folgenden Beispiel wird in der Datei docker-gitlab-ci.yml eine Vorlage mit einer Regel deklariert, die das Auslösen des Auftrags auf Änderungen innerhalb des Docker-Verzeichnisses beschränkt. Allerdings müssen Sie diese Regelvorlage dann in jedem Job in der Pipeline-Datei erweitern.

.rules-docker:
rules:
- changes: [docker/**]

job-docker:
extends: [.rules-docker]
script:
- echo "Docker behaviour"

Vielleicht fragen Sie sich: "Warum legen Sie die Regeln nicht einfach in der Datei .gitlab-ci.yml fest, um die Includes zu steuern?". Die Antwort ist, dass GitLab zwar die Verwendung von rules unter include unterstützt, nicht aber das Schlüsselwort changes.

Sie werden sich vielleicht auch fragen: "Warum die Regelvorlage in jedem Auftrag erweitern und nicht einfach als Standard festlegen?" Das Problem dabei ist, dass beim Zusammenstellen der Pipeline alle Dateien in einer großen Datei zusammengefasst werden. Da beide Vorlagen enthalten sind, würden beide Standardwerte herangezogen werden und miteinander kollidieren. Sie müssen alle Aufträge in ihre jeweiligen Unterverzeichnisse aufteilen.

Hinweis: Da der gesamte Code in einer großen Datei zusammengefasst ist, müssen alle Vorlagen eindeutige Namen haben. Wenn Sie alle Ihre Regelvorlagen .rules nennen würden, würde nur die letzte erscheinen und alle vorherigen außer Kraft setzen.

Jetzt werden nur noch Änderungen im Docker-Verzeichnis die Datei gitlab-ci-docker.yml auslösen.

Fazit

In der sich ständig weiterentwickelnden DevOps-Landschaft kann die Beherrschung der Nuancen von GitLab-Pipelines nicht nur die Geschwindigkeit und Effizienz Ihrer Pipelines verbessern, sondern auch deren Lesbarkeit. Es geht nicht nur um die Ausführung von Befehlen, sondern auch um die Feinabstimmung, das Verständnis dieser Nuancen und die Vorhersage möglicher Herausforderungen. Wie wir heute herausgefunden haben, können ein paar gut platzierte Tipps einen großen Unterschied in Bezug auf Effizienz, Zuverlässigkeit und Geschwindigkeit ausmachen.

Verfasst von

Jeffrey Zaayman

Contact

Let’s discuss how we can support your journey.