This blog post is also available in English

Einrichtung der Pipeline

Wenn wir eine GitLab CI/CD-Pipeline als Service oder Basis für mehrere Entwicklungsteams bereitstellen, befindet sich der Pipeline-Code oft in einem separaten Repository. Die Entwicklungsteams binden dann die Pipeline in ihrer .gitlab-ci.yml ein und nutzen diese. Das Beispiel geht davon aus, dass die Pipeline im Ganzen verwendet wird, aber die Teststrategie kann auch verwendet werden, um einzelne Jobs eher im Stil von Unit-Tests zu testen.

Unser Beispiel besteht aus einem Pipeline-Repository und einem Anwendungs-Repository, wie unten dargestellt.

devops/ # Gruppe, die DevOps Projekte beinhaltet
  pipeline # Das Pipeline Projekt
apps/
  some-application # Eine Anwendung, welche die Pipeline als Konsument via include nutzt
GitLab Gruppen / Projekt Struktur
pipeline/
  jobs/
    build.yml
    test.yml
    quality.yml
  pipeline.yml
Verzeichnisstruktur des devops/pipeline Repositories
include:
  - local: 'pipeline/jobs/build.yml'
  - local: 'pipeline/jobs/test.yml'
  - local: 'pipeline/jobs/quality.yml'

stages:
  - build
  - test
  - quality
Vereinfachter Inhalt von pipeline.yml
include:
  - project: 'devops/pipeline'
    file: 'pipeline/pipeline.yml'
    ref: '1.0.0'
Inhalt der .gitlab-ci.yml der konsumierenden Anwenung im apps/some-application Repository

Testen der Pipeline

Wie stellen wir jetzt bei Weiterentwicklung der Pipeline sicher, dass diese weiterhin funktioniert, bevor wir sie an unsere Nutzer:innen ausliefern und diese feststellen müssen, dass die Pipeline nicht mehr nutzbar ist?

Eine einfache Lösung besteht darin, ein Testprojekt zu erstellen, das unsere Pipeline verwendet und so die Funktionsfähigkeit unserer Pipeline prüft. Dies würde in etwa wie folgt aussehen.

devops/
  pipeline
  spring-boot-test-app # unsere Testanwendung
apps/
  some-application
Aktualiserte GitLab Gruppen / Projekt Struktur
include:
  - project: 'devops/pipeline'
    file: 'pipeline/pipeline.yml'
    ref: 'main'
Inhalt der .gitlab-ci.yml unserer Testanwendung im devops/spring-boot-test-app Repository

Wie vielleicht aufgefallen ist, haben wir den ref Wert von 1.0.0, was auf einen Git Tag verweist, auf main geändert, was den Default Branch im Pipeline-Repository referenziert.

Nun wollen wir die Ausführung der Pipeline des Testprojekts bei jeder Änderung auf dem main Branch im Pipeline-Repository automatisieren. Das erreichen wir einfach, indem wir eine Pipeline zum Pipeline-Repository hinzufügen, welche GitLabs Downstream-Pipelines Feature und das trigger-Schlüsselwort nutzt. Das führt zu folgendem Code:

pipeline/
  jobs/
    build.yml
    test.yml
    quality.yml
  pipeline.yml
.gitlab-ci.yml # <- Die Pipeline im Pipeline-Repository
Verzeichnisstruktur des devops/pipeline Repositories
stages:
  - test-pipeline

test-spring-boot-app:
  stage: test-pipeline
  trigger:
    project: 'devops/spring-boot-test-app'
    strategy: depend
  # der Job läuft nur bei Änderungen auf dem main Branch
  rules:
    - if: $CI_COMMIT_BRANCH == 'main'
Inhalt der .gitlab-ci.yml unserer Pipeline im devops/pipeline Repository

Der Ablauf sieht nun so aus

GitLab pipeline test flow

Jedes Mal, wenn ein Commit auf dem main Branch im Pipeline-Repository erfolgt, wird eine CI/CD-Pipeline in devops/spring-boot-test-app gestartet, die wiederum den Pipeline-Code aus devops/pipeline vom main Branch verwendet. Die Verwendung der depend Strategie im Trigger führt sogar dazu, dass die Upstream-Pipeline fehlschlägt, wenn die Downstream-Pipeline fehlschlägt.

Verbesserung der Idee

Wenn wir nur den main Branch der Pipeline testen, könnten wir immer noch auf Fehler stoßen, nachdem wir bereits ein neues Feature zur Pipeline hinzugefügt haben. Was wir eigentlich möchten, ist einen Feature Branch testen, bevor wir ihn in den main Branch mergen. Das ist überraschend einfach, da wir die Branch-Information in vordefinierten GitLab-Variablen wie CI_COMMIT_BRANCH und CI_COMMIT_REF_NAME zur Verfügung haben und wir ebenfalls eine Variable im ref des include verwenden können. Es gibt zwar einige Einschränkungen hinsichtlich der Variablen, die im include verwendet werden können, aber wir können mit einer Projekt- oder Gruppen-Variable arbeiten, und - das ist wichtig - einer Trigger-Variablen, die wir PIPELINE_REF_NAME nennen werden.

Wir müssen PIPELINE_REF_NAME als Projekt- oder Gruppen-Variable für die Testanwendung in devops/spring-boot-test-app deklarieren und deren Wert auf main setzen, damit standardmäßig der main Branch von devops/pipeline inkludiert wird. Anschließend übergeben wir den Namen des aktuellen Pipeline Branches im Trigger an die Downstream-Pipeline.

stages:
  - test-pipeline

test-spring-boot-app:
  stage: test-pipeline
  variables:
    PIPELINE_REF_NAME: $CI_COMMIT_REF_NAME # <- wir übergeben den aktuellen Branch Namen an die Downstream-Pipeline
  trigger:
    project: 'devops/spring-boot-test-app'
    strategy: depend
Aktualisierter Inhalt der .gitlab-ci.yml unserer Pipeline im devops/pipeline Repository
include:
  - project: 'devops/pipeline'
    file: 'pipeline/pipeline.yml'
    ref: $PIPELINE_REF_NAME # <- nutzt den Wert, den wir im Trigger setzen, oder den Standard aus der Projekt-Variable
Aktualiserter Inhalt der .gitlab-ci.yml unserer Testanwendung im devops/spring-boot-test-app Repository

Nun wird das include in der Downstream-Pipeline des Testprojekts dynamisch aufgelöst, abhängig vom Namen des Branches in der Upstream-Pipeline, was es uns ermöglicht, unseren geänderten Pipeline-Code vor dem Merge zu testen.

Fazit

Dieser Ansatz bietet natürlich keinen absoluten Schutz vor Fehlern und ist immer noch zeitaufwändig und ressourcenintensiv, aber er ermöglicht uns ein paar Testprojekte für häufig vorkommende Fälle zu erstellen und einige Fehler frühzeitig zu erkennen. Die Automatisierung erleichtert die Einbindung von Tests in den Pipeline-Entwicklungsprozess, genauso wie wir es mit anderen Softwarekomponenten auch tun würden, und verbessert sehr wahrscheinlich die Qualität unseres Produkts.