Continuous Integration und Continuous Delivery (CI/CD) transformieren Softwareentwicklung von manuellem, fehleranfälligem Prozess zu automatisierter, reproduzierbarer Pipeline. GitLab CI/CD ist nicht nur ein Feature von GitLab – es ist eine vollständige DevOps-Plattform, die direkt in Git integriert ist.
Dieses Kapitel erklärt die fundamentalen Konzepte von CI/CD, wie
GitLab CI/CD technisch funktioniert, und führt praktisch in
.gitlab-ci.yml, Pipelines, und Runner ein. Am Ende
verstehst du, warum CI/CD essentiell ist und wie du deine erste Pipeline
erstellst.
Stell dir vor: Zehn Entwickler arbeiten parallel an Features für zwei
Wochen. Jeder entwickelt auf eigenem Branch. Am “Integration Day” mergen
alle ihre Branches in main.
Was passiert:
Developer 1: git merge feature-a → 50 Konflikte
Developer 2: git merge feature-b → Kompilierungsfehler
Developer 3: git merge feature-c → Tests schlagen fehl
Developer 4: git merge feature-d → Feature-a und Feature-d sind inkompatibel
...
Ergebnis: Tagelange Integration, Debugging von Konflikten, frustrierte Teams. Dies ist Integration Hell.
Continuous Integration (CI): Entwickler integrieren Code kontinuierlich (mehrmals täglich) in gemeinsamen Branch.
Wie CI hilft:
Developer 1: git push → Pipeline läuft → Tests OK → Merged
Developer 2: git push → Pipeline läuft → Tests OK → Merged
Developer 3: git push → Pipeline läuft → Tests FAIL → Fix, dann Merged
Vorteile: - Konflikte sind klein: Stündliche Integration statt wöchentlich - Probleme werden sofort gefunden: Tests laufen bei jedem Push - main ist immer stabil: Nur Code, der Tests besteht, wird integriert
Continuous Delivery (CD): Code ist immer bereit, deployed zu werden. Deployment ist automatisiert, aber manuell getriggert.
Continuous Deployment: Code wird automatisch in Production deployed, wenn Tests passen.
CI: Automatisierte Tests, Builds, Quality Checks Continuous Delivery: Code kann deployed werden (button-click away) Continuous Deployment: Code wird deployed (fully automated)
Im Gegensatz zu externen CI/CD-Tools (Jenkins, CircleCI) ist GitLab CI/CD built-in:
Vorteile: - Single Platform: Git + CI/CD + Issue Tracking + Registry in einem - Tight Integration: Push-Events triggern Pipelines automatisch - Permissions: GitLab-Permissions gelten für CI/CD (keine separate User-DB) - Unified UI: Pipeline-Status direkt in Merge Requests, Commits
Architektur-Rückblick:
Flow: 1. Developer pusht Code 2. Rails empfängt
Push-Event 3. Rails parst .gitlab-ci.yml, erstellt Pipeline
in DB 4. Sidekiq queued Jobs 5. Runner pollt für verfügbare Jobs 6.
Runner führt Job aus (Build, Test, Deploy) 7. Runner reportet Result
zurück 8. Rails updated Pipeline-Status
.gitlab-ci.yml:
Configuration as CodeDie .gitlab-ci.yml-Datei im Repository-Root definiert
die gesamte Pipeline.
# .gitlab-ci.yml
test:
script:
- echo "Hello, GitLab CI/CD!"Was passiert: 1. Datei wird committed und gepusht 2.
GitLab detected .gitlab-ci.yml 3. Pipeline mit einem Job
“test” wird erstellt 4. Runner führt
echo "Hello, GitLab CI/CD!" aus 5. Job-Status wird
angezeigt (passed)
Im GitLab UI:
Pipeline #123 (main) ✓ passed
└─ test ✓ passed (5s)
Stages organisieren Jobs in Phasen. Jobs innerhalb einer Stage laufen parallel, Stages laufen sequenziell.
stages:
- build
- test
- deploy
build_job:
stage: build
script:
- echo "Building application..."
- npm install
- npm run build
test_unit:
stage: test
script:
- echo "Running unit tests..."
- npm test
test_integration:
stage: test
script:
- echo "Running integration tests..."
- npm run test:integration
deploy_staging:
stage: deploy
script:
- echo "Deploying to staging..."
- ./deploy.sh stagingExecution-Flow:
Stage: build
└─ build_job ✓ (30s)
Stage: test (runs after build)
├─ test_unit ✓ (10s) ← parallel
└─ test_integration ✓ (20s) ← parallel
Stage: deploy (runs after test)
└─ deploy_staging ✓ (15s)
Visualisierung:
job_name:
stage: test
image: node:18-alpine # Docker Image für Job
before_script: # Läuft vor script
- npm install
script: # Hauptkommandos
- npm test
after_script: # Läuft nach script (auch bei Failure)
- echo "Job finished"
artifacts: # Files zum Download
paths:
- coverage/
expire_in: 1 week
cache: # Beschleunigt Builds
paths:
- node_modules/
only: # Wann Job läuft
- main
- merge_requests
variables: # Job-spezifische Variablen
NODE_ENV: test# .gitlab-ci.yml
stages:
- build
- test
- deploy
variables:
NODE_VERSION: "18"
build:
stage: build
image: node:${NODE_VERSION}-alpine
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 day
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
test:unit:
stage: test
image: node:${NODE_VERSION}-alpine
script:
- npm ci
- npm test
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
test:lint:
stage: test
image: node:${NODE_VERSION}-alpine
script:
- npm ci
- npm run lint
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
deploy:staging:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
script:
- ssh user@staging.example.com "cd /app && ./deploy.sh"
only:
- main
environment:
name: staging
url: https://staging.example.comPipeline-Flow:
1. build ✓ (45s)
→ Creates dist/ artifacts
2. test:unit ✓ (15s) ║ test:lint ✓ (10s)
↓ (parallel) ║
3. deploy:staging ✓ (20s)
→ Only if on main branch
Runner sind separate Prozesse/Server, die CI/CD-Jobs ausführen. Sie sind nicht Teil des GitLab-Servers.
Typen:
1. Shared Runners: - Verfügbar für alle Projekte - Auf GitLab.com: Von GitLab managed - Self-Managed: Von Admin konfiguriert
2. Specific Runners: - Registriert für spezifisches Projekt/Gruppe - Typisch: Eigene Hardware für spezielle Jobs (GPU, Large Memory)
3. Group Runners: - Verfügbar für alle Projekte in einer Gruppe
Executor definiert, wie Jobs ausgeführt werden:
1. Docker Executor (Empfohlen):
# Job läuft in Docker-Container
job:
image: node:18-alpine
script:
- npm testVorteile: - Isoliert (jeder Job in fresh Container) - Reproduzierbar (gleiche Environment jedes Mal) - Kein Cleanup nötig (Container wird destroyed)
2. Shell Executor: - Job läuft direkt auf Runner-Maschine - Schneller (kein Container-Overhead) - Weniger isoliert (Job kann Runner-System modifizieren)
3. Kubernetes Executor: - Job läuft als Kubernetes-Pod - Hochskalierbar (dynamic Pod-Creation) - Cloud-Native
4. VirtualBox/Parallels: - Job läuft in VM - Maximale Isolation - Langsamer
# Ubuntu/Debian
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt-get install gitlab-runner
# Registration
sudo gitlab-runner register
# Prompted for:
# - GitLab URL: https://gitlab.com/
# - Registration Token: (from Project Settings → CI/CD → Runners)
# - Description: my-runner
# - Tags: docker, linux
# - Executor: docker
# - Default Docker Image: alpine:latestConfig
(/etc/gitlab-runner/config.toml):
[[runners]]
name = "my-runner"
url = "https://gitlab.com/"
token = "abc123"
executor = "docker"
[runners.docker]
image = "alpine:latest"
privileged = false
volumes = ["/cache"]
[runners.cache]
[runners.cache.s3]
ServerAddress = "s3.amazonaws.com"
BucketName = "gitlab-runner-cache"Tags matchen Jobs zu Runnern:
# Job mit Tag "docker"
job:
tags:
- docker
script:
- echo "Runs on docker runner"
# Job mit Tag "gpu"
ml_training:
tags:
- gpu
script:
- python train_model.pyRunner mit Tag “docker” führt erste Job aus, Runner mit Tag “gpu” führt zweite Job aus.
git commit -m "Add feature"
git push origin feature-branch→ Pipeline wird automatisch getriggert.
job:
script:
- echo "Runs on MR"
only:
- merge_requestsPipeline läuft bei MR-Creation und jedem Push zum MR-Branch.
UI: CI/CD → Schedules → New Schedule -
Cron-Expression: 0 2 * * * (täglich um 2 Uhr) - Target
Branch: main
job:
script:
- echo "Nightly build"
only:
- schedulesUI: CI/CD → Pipelines → Run Pipeline - Branch auswählen - Variablen setzen (optional)
deploy_production:
stage: deploy
script:
- ./deploy.sh production
when: manual # Requires manual trigger in UIcurl --request POST \
--form "token=$CI_TRIGGER_TOKEN" \
--form ref=main \
"https://gitlab.com/api/v4/projects/:id/trigger/pipeline"Nützlich für externe Systeme (Webhooks von Dritt-Services).
# Project A: Trigger Pipeline in Project B
trigger_downstream:
trigger:
project: user/project-b
branch: mainMulti-Project-Pipelines für Microservices.
GitLab stellt hunderte Variablen automatisch bereit:
job:
script:
- echo "Project: $CI_PROJECT_NAME"
- echo "Branch: $CI_COMMIT_REF_NAME"
- echo "Commit: $CI_COMMIT_SHA"
- echo "Runner: $CI_RUNNER_DESCRIPTION"
- echo "Job: $CI_JOB_NAME"Wichtige Variablen: - CI_PROJECT_DIR:
Working Directory - CI_COMMIT_SHA: Commit Hash -
CI_COMMIT_REF_NAME: Branch/Tag Name -
CI_COMMIT_TAG: Tag (wenn getriggert von Tag) -
CI_MERGE_REQUEST_IID: MR ID - CI_PIPELINE_ID:
Pipeline ID - CI_JOB_ID: Job ID
Projekt-Level (Settings → CI/CD → Variables):
Key: DATABASE_URL
Value: postgresql://localhost/db
Protected: ☑ (nur auf protected branches)
Masked: ☑ (nicht in logs sichtbar)
In .gitlab-ci.yml:
variables:
DEPLOY_ENV: staging
NODE_VERSION: "18"
job:
variables:
CUSTOM_VAR: value
script:
- echo $DEPLOY_ENV
- echo $NODE_VERSION
- echo $CUSTOM_VARNIEMALS Secrets in .gitlab-ci.yml
committen:
# ✗ FALSCH
job:
script:
- deploy --api-key sk_live_abc123 # SECRET EXPOSED!✓ RICHTIG: Via GitLab UI:
Settings → CI/CD → Variables
Key: API_KEY
Value: sk_live_abc123
Protected: ☑
Masked: ☑
# ✓ RICHTIG
job:
script:
- deploy --api-key $API_KEYFür Multi-Line-Secrets (SSL-Certs, SSH-Keys):
Settings → CI/CD → Variables
Key: SSH_PRIVATE_KEY
Value: [paste entire key]
Type: File
job:
script:
- cat $SSH_PRIVATE_KEY # Variable ist jetzt Pfad zu temp-file
- ssh -i $SSH_PRIVATE_KEY user@hostArtifacts sind Files, die von Jobs generiert und zwischen Stages weitergegeben werden:
build:
script:
- npm run build
artifacts:
paths:
- dist/ # Build-Output
expire_in: 1 day # Auto-delete nach 1 Tag
deploy:
script:
- cp dist/* /var/www/ # dist/ verfügbar von build-Job
dependencies:
- build # Explizit: Nutze Artifacts von buildDownload: Im GitLab UI → Pipeline → Job → Browse/Download Artifacts
Cache beschleunigt Builds durch Speichern von Dependencies:
# Cache node_modules zwischen Jobs
variables:
CACHE_KEY: ${CI_COMMIT_REF_SLUG}
job:
cache:
key: $CACHE_KEY
paths:
- node_modules/
script:
- npm ci # Wenn Cache hit, sehr schnell
- npm testArtifacts vs. Cache:
| Artifacts | Cache | |
|---|---|---|
| Purpose | Job-Output (builds, reports) | Beschleunigung (dependencies) |
| Reliability | Garantiert verfügbar | Best-Effort (kann fehlen) |
| Sharing | Zwischen Jobs in Pipeline | Zwischen Pipelines |
| Storage | GitLab Server | Runner-lokal oder S3 |
GitLab visualisiert Pipelines im UI:
Pipeline Graph:
┌─────────┐
│ build │
└────┬────┘
│
├──────┬──────┐
▼ ▼ ▼
┌────┐ ┌────┐ ┌────┐
│test│ │lint│ │scan│
└──┬─┘ └──┬─┘ └──┬─┘
└──────┴──────┘
│
▼
┌────────┐
│ deploy │
└────────┘
Job-Logs: Live-Streaming während Job läuft, persistent nach Completion.
Pipeline Status in MRs:
Merge Request #123
Pipeline #456: ✓ passed (2m 34s)
├─ build ✓ 45s
├─ test ✓ 30s
└─ deploy ✓ 20s
[Merge] button enabled
.gitlab-ci.yml erstellenmkdir my-first-pipeline
cd my-first-pipeline
git init
# Create simple script
echo 'console.log("Hello, CI/CD!");' > app.js
# Create .gitlab-ci.yml
cat > .gitlab-ci.yml << 'EOF'
stages:
- test
- deploy
test_job:
stage: test
image: node:18-alpine
script:
- node app.js
- echo "Tests passed!"
deploy_job:
stage: deploy
script:
- echo "Deploying application..."
- echo "Deployment complete!"
only:
- main
EOF
git add .
git commit -m "Initial commit with CI/CD"# Create project on GitLab.com
# Then:
git remote add origin git@gitlab.com:username/my-first-pipeline.git
git push -u origin mainGitLab UI: 1. Navigate to project 2. CI/CD → Pipelines 3. Click on Pipeline #1 4. Watch jobs execute in real-time
Ergebnis:
Pipeline #1 (main) ✓ passed (15s)
├─ test_job ✓ passed (8s)
│ Output: Hello, CI/CD!
│ Tests passed!
└─ deploy_job ✓ passed (5s)
Output: Deploying application...
Deployment complete!
git checkout -b feature-update
echo 'console.log("Updated!");' > app.js
git add app.js
git commit -m "Update message"
git push origin feature-updateCreate Merge Request in GitLab UI → Pipeline läuft
automatisch auf feature-update.
Jenkins: - Self-Hosted only - Plugin-basiert (tausende Plugins) - Groovy-basierte Pipelines oder UI-Konfiguration - Sehr flexibel, aber komplex
GitLab CI/CD: - Native Integration in GitLab - YAML-basierte Konfiguration - Einfacher für Standard-Workflows - Keine separate Installation nötig
GitHub Actions: - Native in GitHub - YAML-basierte Workflows - Marketplace für Actions (wiederverwendbare Components) - Enge GitHub-Integration
GitLab CI/CD: - Mehr DevOps-Features (Registry, Security Scanning, etc.) - Unified Platform (nicht nur Git + CI) - Self-Managed-Option
Externe SaaS-Tools: - Weniger Integration mit Git-Hosting - Separate Accounts, Permissions - Oft pro-Minute-Billing
GitLab CI/CD: - Single Platform, single Login - GitLab-Permissions gelten - Self-Managed = unbegrenzte CI/CD
GitLab CI/CD transformiert Code-Commits zu deployed Applikationen – automatisiert, reproduzierbar, schnell.
Core Concepts: -
.gitlab-ci.yml: Configuration as Code -
Stages & Jobs: Organisierte Execution -
Runner: Execution-Engine (Docker, Shell, K8s) -
Artifacts & Cache: Data-Sharing und Performance -
Variables: Configuration und Secrets - Pipeline
Triggers: Push, MR, Schedule, Manual, API
Workflow:
Developer → git push → GitLab → Pipeline Created → Runner → Job Execution → Results
Vorteile: - Frühe Feedback: Tests laufen sofort bei Push - Konsistenz: Gleiche Build-Environment jedes Mal - Schnelligkeit: Parallele Jobs, Caching - Qualität: Automatisierte Tests, Linting, Security Scans - Confidence: Nur getesteter Code in Production
Die folgenden Kapitel vertiefen diese Konzepte: YAML-Syntax, Runner-Konfiguration, Advanced Patterns, Security-Integration, Deployment-Strategien.
GitLab CI/CD ist nicht nur Automation – es ist die Infrastruktur, die moderne Software-Delivery ermöglicht. Master es, und du transformierst Entwicklung von manuell zu automatisiert, von langsam zu schnell, von fragil zu robust.