Die Transformation der Softwareentwicklung von monolithischen Wasserfallmodellen hin zu agilen, iterativen Prozessen hat die Notwendigkeit einer automatisierten Bereitstellungsinfrastruktur unumgänglich gemacht. Im Zentrum dieser technologischen Revolution steht die CI/CD-Pipeline (Continuous Integration / Continuous Delivery & Deployment). Sie fungiert als das Herzstück der modernen “Softwarefabrik”, in der Quellcode nicht mehr manuell kompiliert und verschoben wird, sondern einen hochgradig automatisierten Veredelungsprozess durchläuft.
Dieses Dokument dient als technisches Referenzwerk für die Planung, Konstruktion und Wartung einer proprietären CI/CD-Pipeline. Es analysiert die architektonischen Entscheidungen, die bei der Auswahl von Werkzeugen wie Jenkins, GitHub Actions und GitLab CI/CD getroffen werden müssen, und bietet detaillierte Implementierungsstrategien für kritische Komponenten wie Build-Automatisierung, Test-Orchestrierung, Artefakt-Management und Sicherheitsintegration (DevSecOps).
Das Ziel ist nicht nur die bloße Aneinanderreihung von Skripten, sondern die Schaffung eines robusten, idempotenten und auditierbaren Systems, das die “Time-to-Market” reduziert und gleichzeitig die Stabilität von Produktionsumgebungen gewährleistet. Wir betrachten die Pipeline dabei als Produkt, das denselben Qualitätsstandards unterliegt wie die Software, die sie ausliefert.
Bevor wir in die Implementierungsdetails einsteigen, ist es essenziell, die anatomische Struktur und die theoretischen Prinzipien einer Pipeline zu verstehen. Eine Pipeline ist im Wesentlichen eine Zustandsmaschine, die Code von einem unsicheren Zustand (frisch geschrieben) in einen vertrauenswürdigen Zustand (produktionsbereit) überführt.
Eine vollständige CI/CD-Pipeline gliedert sich in vier fundamentale Phasen, die als Qualitätstore (Quality Gates) fungieren. Ein Fehler in einer dieser Phasen führt zum sofortigen Abbruch des Prozesses (Fail-Fast-Prinzip), um zu verhindern, dass fehlerhafte Artefakte die nachgelagerten Systeme kontaminieren.
Der Ursprung jeder Pipeline ist das Versionskontrollsystem (VCS). In modernen Architekturen ist die Pipeline ereignisgesteuert. Sie reagiert auf spezifische Stimuli innerhalb des VCS, wie etwa einen git push, das Öffnen eines Pull Requests (PR) oder das Erstellen eines Tags.
Hier findet die Transformation von abstraktem Code in konkrete Artefakte statt.
npm install, pip install) ist oft der zeitintensivste Schritt. Hier entscheiden Caching-Strategien über die Performance der Pipeline.Automatisierte Tests sind der Garant für Vertrauen in die Pipeline.
Der finale Schritt befördert das validierte Artefakt in eine Zielumgebung.
Die Wahl der Orchestrierungsplattform ist eine langfristige Architekturentscheidung. Während der Markt unzählige Nischenprodukte bietet, haben sich drei dominante Ökosysteme herauskristallisiert: Jenkins, GitHub Actions und GitLab CI/CD. Jedes System verfolgt eine unterschiedliche Philosophie.
Jenkins ist der Industriestandard für Szenarien, die maximale Anpassbarkeit erfordern. Als selbstgehostete Java-Applikation bietet es vollständige Kontrolle über die Infrastruktur.
GitHub Actions hat sich durch die nahtlose Integration in die weltweit größte Code-Hosting-Plattform schnell etabliert.
GitLab verfolgt einen holistischen Ansatz, bei dem Source Code Management, CI/CD, Container Registry und Security Scanning in einer einzigen Applikation gebündelt sind.
| Merkmal | Jenkins | GitHub Actions | GitLab CI/CD |
|---|---|---|---|
| Pipeline-Definition | Groovy (Jenkinsfile) | YAML | YAML (.gitlab-ci.yml) |
| Hosting-Modell | Primär Self-Hosted | SaaS & Self-Hosted | SaaS & Self-Hosted |
| Erweiterbarkeit | Plugins (Java) | Actions (JS/Docker) | Native Features |
| Container-Support | Via Docker Pipeline Plugin | Nativ (Service Container) | Nativ (Services) |
| Wartungsaufwand | Hoch | Niedrig (SaaS) | Mittel |
| Einstiegshürde | Hoch | Niedrig | Mittel |
Bevor die erste Zeile Code für die Pipeline geschrieben wird, muss die physikalische oder virtuelle Infrastruktur bereitgestellt werden. Die Qualität der Runner-Infrastruktur bestimmt maßgeblich die Performance und Zuverlässigkeit der Pipeline.
Pipelines benötigen Rechenleistung. Hier gibt es zwei fundamentale Modelle:
Hardware-Anforderungen: Ein typischer Linux-Runner benötigt mindestens 2 vCPUs und 4-8 GB RAM, um Docker-Container und Kompilierungsprozesse parallel zu handhaben.
Installation: Der Runner-Agent (GitHub Actions Runner oder GitLab Runner) muss als Systemdienst installiert werden, um Neustarts zu überleben.
Da moderne CI/CD-Prozesse fast ausschließlich auf Containern basieren (um saubere Build-Umgebungen zu garantieren), muss der Runner in der Lage sein, Docker-Befehle auszuführen.
/var/run/docker.sock) wird in den Container gemountet (“Docker-outside-of-Docker”). Der Container steuert also den Docker-Daemon des Hosts. Dies ist performanter (kein Layer-Overhead), kann aber zu Konflikten bei Dateinamen führen.Für das Deployment auf virtuelle Maschinen (z.B. AWS EC2) benötigt die Pipeline SSH-Zugriff.
authorized_keys), der private Schlüssel wird als Secret in der CI-Plattform hinterlegt.GitHub Actions bietet durch seine Event-basierte Architektur eine extrem flexible Basis für CI/CD.
Ein Workflow wird in .github/workflows/main.yml definiert.
push auf main für Deployments und pull_request für reine Tests.runs-on: ubuntu-latest). Jobs bestehen aus Steps, die sequenziell ausgeführt werden.Ein Hauptgrund für langsame Pipelines ist das wiederholte Herunterladen von Abhängigkeiten (node_modules).
Implementierung: Die actions/cache-Action speichert Verzeichnisse basierend auf einem Hash-Schlüssel.
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
Key-Strategie: key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}. Dieser Schlüssel ändert sich nur, wenn sich das Betriebssystem oder die package-lock.json ändert. Bei einem “Cache Hit” wird das Verzeichnis ~/.npm wiederhergestellt, was npm ci massiv beschleunigt.
Das Erstellen und Veröffentlichen von Docker-Images ist ein Standard-Use-Case.
docker/login-action mit Secrets (DOCKERHUB_USERNAME, DOCKERHUB_TOKEN).docker/metadata-action extrahiert automatisch Tags aus Git-Refs (z.B. wird ein Git-Tag v1.0.0 automatisch zum Docker-Tag v1.0.0 und latest).docker/setup-buildx-action ermöglicht Multi-Architektur-Builds (z.B. ARM64 für Raspberry Pi und AMD64 für Cloud-Server) in einem einzigen Schritt.Netzwerkfehler können Pipelines sporadisch fehlschlagen lassen. GitHub Actions bietet keine native retry-Option für einzelne Steps, aber Workarounds existieren.
nick-fields/retry, die einen Befehl mehrfach ausführen, wenn er fehlschlägt.fail-fast: true) oder weiterlaufen.GitLab CI zeichnet sich durch seine starke Integration von Artefakten und Umgebungen aus.
GitLab-Pipelines sind strikt in Stages gegliedert (Build -> Test -> Deploy). Jobs in derselben Stage laufen parallel. Erst wenn alle Jobs einer Stage erfolgreich sind, beginnt die nächste Stage. Dies erzwingt eine saubere Trennung der Belange.
Ein häufiges Missverständnis ist der Unterschied zwischen Cache und Artefakten in GitLab.
node_modules). Wenn er fehlt, funktioniert der Job trotzdem (nur langsamer).dist/ Ordner), die zwingend an den nächsten Job übergeben werden müssen. Sie werden auf den GitLab-Server hochgeladen und sind im UI herunterladbar.GitLab bietet fortgeschrittene Deployment-Features.
environment: production im Job trackt GitLab jede Bereitstellung. Dies ermöglicht im UI einen “Rollback”-Button, der eine frühere Pipeline erneut ausführt, um einen stabilen Zustand wiederherzustellen.environment: name: review/$CI_COMMIT_REF_SLUG). Dies erlaubt Testern, Änderungen live zu sehen, bevor sie gemergt werden.Jenkins-Pipelines werden in einem Jenkinsfile definiert. Dies ist der “Pipeline-as-Code”-Ansatz, der die alten UI-basierten Jobs abgelöst hat.
pipeline {... }. Bietet eine strikte Struktur und Syntax-Validierung. Dies ist der empfohlene Standard für 90% der Anwendungsfälle, da er lesbarer und wartbarer ist.node {... }. Basiert direkt auf Groovy. Bietet maximale Flexibilität (z.B. komplexe Schleifen, Try-Catch-Blöcke), ist aber schwerer zu verstehen.Statt Software global auf dem Jenkins-Server zu installieren, können Docker-Container als flüchtige Build-Umgebungen genutzt werden.
Syntax: agent { docker { image 'node:18' } }. Jenkins startet diesen Container, mountet den Workspace, führt die Befehle aus und löscht den Container danach. Dies garantiert eine saubere Umgebung für jeden Build und löst Konflikte zwischen Projekten, die unterschiedliche Node-Versionen benötigen.
Jenkins besitzt exzellente Plugins für instabile Tests.
retry(3) {... } wiederholt einen fehlgeschlagenen Schritt bis zu dreimal.Eine Pipeline, die nur Code bewegt, ist wertlos. Sie muss Qualität verifizieren.
Das Parsen von Testergebnissen ist essenziell, um Fehler schnell zu lokalisieren.
junit '**/target/surefire-reports/*.xml' erstellt Trend-Graphen im Dashboard.artifacts:reports:junit integriert die Ergebnisse direkt in den Merge Request. Entwickler sehen sofort “2 Tests failed” im Diff.mikepenz/action-junit-report, um Testergebnisse als Annotationen im Code anzuzeigen.Metriken zur Testabdeckung (Code Coverage) sollten erhoben werden. Fällt die Abdeckung unter einen Schwellenwert (z.B. 80%), kann die Pipeline fehlschlagen (Quality Gate). Dies verhindert schleichenden Qualitätsverfall.
Das “D” in CI/CD steht für Delivery oder Deployment. Hier verlässt der Code die kontrollierte Umgebung der Pipeline.
Das Deployment von Frontend-Apps (React, Vue) erfolgt oft durch Synchronisation mit einem S3-Bucket.
Befehl: aws s3 sync ./build s3://mein-bucket --delete. Das Flag --delete ist wichtig, um veraltete Dateien zu entfernen.
Sicherheit: Authentifizierung erfolgt über aws-actions/configure-aws-credentials (GitHub) oder Umgebungsvariablen (GitLab/Jenkins) unter Nutzung von IAM Usern mit minimalen Rechten (Least Privilege).
Deployments auf Server erfordern SSH.
Workflow:
1. SSH-Verbindung aufbauen.
2. Code pullen (git pull) oder Docker Image pullen.
3. Service neustarten.
Problembehandlung: StrictHostKeyChecking=no ist oft notwendig, um interaktive Prompts zu vermeiden, stellt aber ein Sicherheitsrisiko dar (Man-in-the-Middle). Besser ist das Vorbefüllen der known_hosts Datei im Runner.
Der “Golden Standard” ist das Bauen eines Images, Pushen in eine Registry und anschließendes Triggern der Orchestrierung (Kubernetes/ECS).
Tagging: Nutzung des Commit-SHA als Tag (myapp:a1b2c3d) ermöglicht eindeutige Zuordnung. latest sollte in der Produktion vermieden werden, da es nicht eindeutig ist, welche Version gerade läuft.
Sicherheit darf kein nachgelagerter Gedanke sein.
Harte Codierung von Zugangsdaten ist untersagt.
*** zu ersetzen. Vorsicht bei Base64-Encodings – diese werden oft nicht erkannt und leaken Secrets.npm audit oder OWASP Dependency Check prüfen genutzte Bibliotheken auf bekannte CVEs.Ein fehlgeschlagener Build, den niemand bemerkt, blockiert das Team.
Die Integration mit Slack oder MS Teams reduziert die Reaktionszeit.
rtCamp/action-slack-notify. Kann bedingt ausgeführt werden (if: failure()), um nur bei Fehlern zu alarmieren.Messen Sie die Pipeline selbst.
Diese Daten sind die Basis für kontinuierliche Verbesserung der Infrastruktur.
Der Bau einer eigenen CI/CD-Pipeline ist der Schritt vom Handwerk zur industriellen Fertigung von Software. Ob Sie die Flexibilität von Jenkins, die Integration von GitHub Actions oder die All-in-One-Lösung von GitLab wählen, die Prinzipien bleiben gleich: Automatisieren Sie alles, scheitern Sie früh (Fail Fast) und vertrauen Sie nur immutablen Artefakten.
Die in diesem Handbuch vorgestellten Architekturen und Patterns bieten das Fundament für eine skalierbare, sichere und effiziente Softwarebereitstellung, die den Anforderungen moderner DevOps-Organisationen gerecht wird.
name: Production CI/CD
on:
push:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test
build-push:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- uses: docker/build-push-action@v6
with:
push: true
tags: myapp:latest,myapp:${{ github.sha }}
notify:
if: always()
needs: [test, build-push]
runs-on: ubuntu-latest
steps:
- uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_COLOR: ${{ contains(needs.*.result, 'failure') && 'danger' || 'good' }}
stages:
- build
- deploy
build_image:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
deploy_prod:
stage: deploy
image: alpine:latest
environment:
name: production
url: https://myapp.com
script:
- apk add --no-cache curl
- curl -X POST $DEPLOY_WEBHOOK_URL
only:
- main