CI/CD mit Bitbucket Pipelines. Eine Einführung

Vektorillustration einer DevOps-Unendlichkeits­schleife mit zwei Personen, die an CI/CD-Prozessen arbeiten, umgeben von technischen Elementen.

Continuous Integration und Continuous Deployment sind in der Softwareentwicklung heute nahezu allgegenwärtig. Auch bei WATA Factory setzen wir in fast allen Projekten zumindest irgendeine Form automatisierter Integration und Deployment ein.
Da sich diese Praktiken ständig weiterentwickeln, wird es immer wichtiger zu verstehen, wie man Workflows effektiv strukturiert und automatisiert.

In diesem Beitrag gehen wir durch alles, was man wissen sollte, bevor man CI/CD mit Bitbucket Pipelines einrichtet:
Wir klären die grundlegenden Begriffe, erklären den Aufbau von Pipelines und zeigen sowohl einfache als auch fortgeschrittenere Konfigurationsbeispiele, die du in deinen Projekten anwenden kannst.
Kurz gesagt: Dieser Artikel bietet einen praxisnahen Überblick darüber, wie Bitbucket Pipelines funktioniert und was sich damit erreichen lässt.

Doch bevor wir einsteigen, klären wir erst einmal einige wichtige Begriffe.

Was ist CI?

CI (Continuous Integration) bezeichnet die Praxis, Code automatisch zu bauen, Tests und statische Codeanalyse durchzuführen und den Code ggf. zu mergen.
Das entscheidende Wort hier lautet „automatisch“.

Das manuelle Ausführen dieser Schritte kostet Zeit und ist nicht nur ineffizient, sondern erhöht auch das Risiko menschlicher Fehler — denn wir Menschen sind schlicht nicht gut in langwierigen, sich wiederholenden Tätigkeiten.

Stattdessen richtet man einen Prozess ein (meist „Pipeline“ genannt), der automatisch ausgeführt wird, sobald man Code in das Repository pusht.
Derdie Entwicklerin wird benachrichtigt, wenn ein Schritt fehlschlägt, und kann das Problem dann frühzeitig beheben.

Man kann auch unterschiedliche Pipelines für verschiedene Branches einrichten.
Beispiel: Für einen Feature-Branch möchte man eventuell nicht bei jedem Push zeitaufwendige Integrations- oder End-to-End-Tests ausführen, da diese in größeren Projekten Stunden dauern können. Das System lässt sich also so konfigurieren, dass schwere Tests nur bei einem Merge in den Master- oder Release-Branch laufen (je nach Branching-Modell des Projekts).

Typischerweise ist das Ergebnis der CI-Phase ein Artefakt, das im nächsten Schritt deployt werden kann — etwa eine .jar-/ .war-Datei oder ein Docker-Image.

Was ist CD?

CD steht für Continuous Delivery oder Continuous Deployment und kann als Erweiterung von CI betrachtet werden.
Im Allgemeinen bezeichnet es alles, was nach einem erfolgreich abgeschlossenen CI-Durchlauf passiert — auch wenn sich die Grenze zwischen beiden Bereichen nicht immer klar ziehen lässt (vor allem, da beide oft am selben Ort definiert werden).

Die CD-Pipeline nimmt normalerweise das in der CI-Phase erzeugte Artefakt und deployed es in eine Test- oder Staging-Umgebung, damit Tester darauf zugreifen können.
Dazu gehören das Kopieren von Dateien, Setzen von Konfigurationen oder das Neustarten von Servern. Ebenso können Skripte eingebunden werden, die Versionsnummern automatisch erhöhen oder Artefakte in Repositories hochladen.
Optional kann man sogar das automatische Deployment in Produktion einrichten.

Wie eine CI/CD-Pipeline konkret aussieht, hängt immer vom jeweiligen Projekt ab — und kein Projekt gleicht dem anderen.

Kurz: Auch wenn es anfangs etwas Aufwand bedeutet, lohnt sich das Einrichten einer CI/CD-Pipeline langfristig, weil repetitive Schritte automatisiert werden und Fehler früher erkannt werden — da wichtige Tests bei jedem Push laufen können.

Alternative CI/CD-Plattformen

Bei WATA Factory nutzen wir Bitbucket Pipelines vor allem, weil es sich nahtlos in andere Atlassian-Produkte integriert, die wir täglich verwenden — z. B. Jira und Confluence.

Es gibt jedoch zahlreiche Alternativen. Hier eine kurze Übersicht über einige der bekanntesten Plattformen:

  • GitHub Actions
    • Native Integration in GitHub-RepositoriesGroße Community, einfache Nutzung
    • Sehr gut für Open-Source-Projekte
  • GitLab CI/CD
    • Voll integriert in GitLab
    • Integrierte Container Registry und Security-Scanning
    • Häufig in Enterprise- und DevOps-Teams eingesetzt
  • Jenkins
    • Komplett Open Source
    • Wohl eines der ältesten Tools in dieser Liste
    • Self-Hosted
    • Extrem anpassbar, aber komplexer
    • Oft genutzt für anspruchsvolle, selbst gehostete Enterprise-Setups
  • CircleCI
    • Nutzt Docker-Container oder virtuelle Maschinen
    • Sehr schnelle Parallelisierung und Caching
    • Gute Unterstützung für Docker und Kubernetes
  • Azure DevOps Pipelines
    • Starke Integration in Microsoft- und Azure-Ökosystem
    • Unterstützt YAML-Pipelines, bietet aber auch eine GUI-Variante

Ein einfaches Bitbucket-Pipeline-Setup

Um Bitbucket Pipelines zu verwenden, braucht man ein Bitbucket-Konto und ein Repository, in das der Code hochgeladen ist.

Pipelines aktivieren

Bevor man Pipelines nutzen kann, müssen sie im Bitbucket-Interface aktiviert werden:
Repository → Repository SettingsPipelinesSettingsEnable Pipelines einschalten.
Vorher wirkt sich keine Konfiguration aus.

Die bitbucket-pipelines.yml

Alle Konfigurationen für eine Bitbucket-Pipeline befinden sich in einer einzigen YAML-Datei, bitbucket-pipelines.yml, die sich im Root-Verzeichnis deines Repositories befinden muss.
Das bedeutet natürlich, dass du — wenn dein Projekt auf mehrere Repositories verteilt ist (zum Beispiel Frontend und Backend) — für jedes deiner Repositories eine eigene Pipeline und eine eigene bitbucket-pipelines.yml benötigst.

Alle bitbucket-pipelines.yml-Dateien haben eine bestimmte Struktur, die die Schritte, Umgebungen und Bedingungen des CI/CD-Prozesses definiert.
Die wichtigsten Elemente sind die folgenden:

  • image – das Docker-Image, in dem die Steps ausgeführt werden (kann ein Standard-Image für die Runtime-Umgebung oder ein benutzerdefiniertes Image sein, das die Dev- oder Prod-Umgebung nachbildet)
  • pipelines – der Hauptblock und das oberste Element, das definiert, wann und wie Steps ausgeführt werden. Einige der nützlichen Eigenschaften sind:
    • branches – wird bei jedem Commit ausgeführt für Branches, die einen bestimmten Namen haben oder einem bestimmten Namensmuster folgen (z. B. „master“ oder „/feature“)
    • pullrequests – wird ausgeführt, wenn ein Pull Request für einen bestimmten Branch erstellt wird. Ermöglicht ebenfalls Einschränkungen auf bestimmte Namensmuster, ähnlich wie „branches“
    • tags – wird für bestimmte Tags im Git-Repository ausgeführt
    • default – wie der Name schon sagt, eine Fallback-Option. Wird für jeden Commit auf jedem Branch ausgeführt, der von keiner der oben genannten Eigenschaften abgedeckt ist
    • custom – für manuelle oder geplante Pipelines
  • step – eine Ausführungseinheit (zum Beispiel „build and test“), definiert durch Shell-Befehle. Hier passiert das Wesentliche. Ein Step kann Folgendes enthalten (unter anderem):
    • script – die tatsächlichen Befehle, die in einem Step ausgeführt werden
    • caches – Verzeichnisse (zum Beispiel „node_modules“), die zwischen den Runs gecached werden, was Ausführungen erheblich beschleunigen kann, da nicht alle Abhängigkeiten jedes Mal neu heruntergeladen werden müssen
    • artifacts – in einem Step erzeugte Dateien (zum Beispiel eine .jar-Datei), die in einem anderen Step weiterverwendet werden können
    • deployment – markiert einen Step als Deployment (erscheint in der „Deployments“-UI von Bitbucket)

pipe – ein benutzerdefiniertes Docker-Image für einen Container, der ein Skript zur Ausführung einer bestimmten Aufgabe enthält. Man kann sie sich wie vorgefertigte Bausteine vorstellen, besonders nützlich für Integrationen mit Tools von Drittanbietern.

Ein einfaches Beispiel

ie Bitbucket-Weboberfläche stellt tatsächlich einige Vorlagen für typische Konfigurationen bereit, aber für diesen Artikel erstellen wir die bitbucket-pipelines.yml manuell. Da ich Java-Entwickler bin, wird das folgende Beispiel für ein Java-Maven-Projekt sein. Dies ist eines der einfachsten Beispiele überhaupt:

image: maven:3.9.6-eclipse-temurin-17
pipelines:
  default:
    - step:
        name: Build and Test
        caches:
          - maven
        script:
          - mvn -B clean install

Schauen wir uns an, was das macht:

  • Die erste Zeile weist die Pipeline an, JDK 17 mit Maven 3.9 zu verwenden.
  • Das Schlüsselwort default sagt uns, dass diese Pipeline bei jedem Commit und für jeden Branch ausgeführt wird.
  • Wir cachen den Ordner maven, damit zukünftige Builds deutlich schneller sind.
  • Schließlich führen wir den Maven-Befehl aus, der das Projekt bereinigt, Abhängigkeiten herunterlädt (falls erforderlich), kompiliert und Unit-Tests ausführt.

So haben wir selbst mit diesem kleinen Stück Code bereits eine Pipeline, die den Code bei jedem Commit automatisch baut und die Tests ausführt.
Außerdem erhältst du eine E-Mail, wenn einer der Schritte fehlschlägt.

Ein komplexeres Beispiel

Das folgende Beispiel zeigt eine komplexere Pipeline, die getrennte und unabhängige Schritte für das Bauen, das Ausführen von Unit-Tests, das Ausführen von Integrationstests, das Paketieren und das Deployen des Codes enthält.
Während Build und Unit-Tests bei jedem Commit ausgeführt werden, werden Integrationstests, Paketierung und Deployment nur für Release-Branches ausgeführt.

image: maven:3.9.6-eclipse-temurin-17

pipelines:
  default:
    - step:
        name: Build (Compile Only)
        caches:
          - maven
        script:
          - mvn -B -DskipTests compile
        artifacts:
          - target/**
    - step:
        name: Unit Tests
        caches:
          - maven
        script:
          - mvn -B test
        artifacts:
          - target/**
  
  branches:
    'release/*':
      - step:
          name: Build (Compile Only)
          caches:
            - maven
          script:
            - mvn -B -DskipTests compile
          artifacts:
            - target/**
      - step:
          name: Unit Tests
          caches:
            - maven
          script:
            - mvn -B test
          artifacts:
            - target/**
      - step:
          name: Integration Tests
          caches:
            - maven
          script:
            - mvn -B verify -DskipUnitTests
          artifacts:
            - target/**
      - step:
          name: Package as JAR
          caches:
            - maven
          script:
            - mvn -B package -DskipTests
          artifacts:
            - target/*.jar
      - step:
          name: Deploy
          deployment: Development
          script:
            - pipe: atlassian/scp-deploy:1.6.0
              variables:
                USER: $DEPLOY_USER
                SERVER: $DEPLOY_HOST
                REMOTE_PATH: "/var/deploy"
                LOCAL_PATH: "target/*.jar"

Zunächst fällt auf, dass wir unter pipelines zwei Blöcke haben:
einen default-Block (der bei jedem Commit ausgeführt wird) und einen branches-Block, der nur für Release-Branches ausgeführt wird (die wir als alle Branches unter release/ definiert haben).

Beachte außerdem, dass die Schritte Build (Compile Only) und Unit Tests wiederholt werden — da wir diese ebenfalls für die Release-Branches ausführen möchten.

Zusätzlich zu Build- und Unit-Test-Schritten führt der branches-Abschnitt auch die Integrationstests aus und paketiert die Anwendung als JAR-Datei.
Diese Schritte werden ausschließlich für Release-Branches ausgeführt.

Am interessantesten ist der letzte Schritt, der die scp-deploy pipe verwendet.
Diese Pipe nimmt — wie der Name schon vermuten lässt — die im vorherigen Schritt erzeugten Artefakte und kopiert sie per SCP auf einen Remote-Server.
Damit das funktioniert, benötigt sie die Informationen aus dem Bereich variables.
Alle Werte, die mit einem $ beginnen, sind Variablen, die in der Bitbucket-Weboberfläche als Repository-Variablen definiert werden (denn sensible Daten sollten natürlich nicht im Repository liegen).

Fazit

CI/CD ist ein unverzichtbarer Bestandteil moderner Softwareentwicklung.
Bei WATA Factory setzen wir dafür auf Bitbucket Pipelines. Wie dieser Artikel zeigt, sind einfache Pipelines schnell eingerichtet — und auch komplexere Setups lassen sich klar strukturieren.

Neben den vielen Vorteilen der Automatisierung integriert sich Bitbucket Pipelines hervorragend in das Atlassian-Ökosystem — und ist deshalb unser bevorzugtes Tool.

Related Posts