Lab 06: Multi-Stage Pipelines¶
Hintergrund¶
Bisher hatten unsere Pipelines eine flache Struktur: ein Pool, eine Liste von Steps. Das genügt für einfache Builds, aber reale Projekte durchlaufen mehrere Phasen - Build, Test, Deploy nach Staging, Deploy nach Production. Jede Phase hat eigene Anforderungen: unterschiedliche Agents, Bedingungen oder manuelle Genehmigungen.
Azure Pipelines bildet das über eine dreistufige Hierarchie ab:
- Stages: Die oberste Ebene. Jede Stage repräsentiert eine Phase im
Gesamtprozess (z. B. "Build", "Test", "Deploy"). Stages laufen standardmäßig
nacheinander, können aber mit
dependsOnauch parallel geschaltet werden. - Jobs: Innerhalb einer Stage können mehrere Jobs definiert werden. Jobs
ohne
dependsOnlaufen parallel auf unterschiedlichen Agents - das beschleunigt z. B. das gleichzeitige Ausführen von Unit Tests und Lint Checks. - Steps: Die kleinste Einheit. Jeder Step ist ein einzelner Befehl oder Task, der innerhalb eines Jobs sequenziell ausgeführt wird.
Zusätzlich lernen wir in diesem Lab zwei wichtige Konzepte kennen:
- Artefakte (
publish/download): Da jede Stage auf einem eigenen Agent läuft, gehen Dateien zwischen Stages verloren. Mitpublishspeicherst du Dateien als Artefakt, und mitdownloadlädst du sie in einer späteren Stage wieder herunter. - Conditions: Mit
conditionkannst du steuern, ob eine Stage überhaupt ausgeführt wird - z. B. nur auf demmaster-Branch oder nur wenn alle vorherigen Stages erfolgreich waren.
Aufgabenstellung¶
Schritt 1: Node.js-Beispielprojekt erstellen¶
Für eine Multi-Stage-Pipeline brauchen wir eine Anwendung, die tatsächlich gebaut und getestet werden kann. Wir erstellen ein einfaches Node.js-Projekt mit Build-Skript, Tests und Lint-Check. Node.js eignet sich gut, da es auf den Microsoft-hosted Agents vorinstalliert ist und keine Kompilierung erfordert.
Erstelle die folgenden Dateien in deinem hello-pipeline-Repository:
package.json - definiert die Projekt-Metadaten und die Skripte, die die
Pipeline später aufruft (npm run build, npm test, npm run lint):
{
"name": "hello-pipeline",
"version": "1.0.0",
"description": "Demo-App für Multi-Stage Pipeline",
"main": "src/app.js",
"scripts": {
"build": "echo 'Building app...' && mkdir -p dist && cp src/*.js dist/ && cp index.html dist/",
"test": "echo 'Running tests...' && node test/test.js",
"lint": "echo 'Linting...' && echo 'No lint errors found.'"
}
}
Erstelle die Verzeichnisse und die Anwendungsdatei:
Bash:
mkdir -p src test
PowerShell:
New-Item -ItemType Directory -Force -Path src, test | Out-Null
src/app.js - eine einfache Node.js-Anwendung mit zwei Funktionen, die wir später testen:
function greet(name) {
return `Hello, ${name}! Welcome to Azure Pipelines.`;
}
function add(a, b) {
return a + b;
}
module.exports = {greet, add};
test/test.js - ein minimales Test-Framework ohne externe Abhängigkeiten. Die
Tests prüfen, ob die Funktionen aus app.js korrekt arbeiten. Bei einem
fehlgeschlagenen Test endet das Skript mit Exit-Code 1, was die Pipeline als
"failed" markiert:
const {greet, add} = require('../src/app');
let passed = 0;
let failed = 0;
function assert(condition, message) {
if (condition) {
console.log(` PASS: ${message}`);
passed++;
} else {
console.log(` FAIL: ${message}`);
failed++;
}
}
console.log('Running tests...\n');
assert(greet('World') === 'Hello, World! Welcome to Azure Pipelines.', 'greet returns correct message');
assert(add(2, 3) === 5, 'add(2,3) returns 5');
assert(add(-1, 1) === 0, 'add(-1,1) returns 0');
console.log(`\nResults: ${passed} passed, ${failed} failed`);
if (failed > 0) process.exit(1);
Schritt 2: Multi-Stage Pipeline erstellen¶
Jetzt erstellen wir die Pipeline mit vier Stages, die eine typische CI/CD- Kette abbilden:
- Build: Baut die Anwendung und publiziert die Artefakte.
- Test: Führt Unit Tests und Lint-Check parallel in zwei Jobs aus.
- Deploy Staging: Simuliert ein Deployment in die Staging-Umgebung (nur auf
master). - Deploy Production: Simuliert ein Deployment in die Produktion (nur auf
master, nach erfolgreichem Staging-Deployment).
Ersetze den Inhalt von azure-pipelines.yml:
trigger:
branches:
include:
- master
variables:
nodeVersion: '20.x'
buildConfiguration: 'Release'
stages:
# ===== Stage 1: Build =====
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
displayName: 'Build Application'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '$(nodeVersion)'
displayName: 'Node.js installieren'
- script: npm run build
displayName: 'Anwendung bauen'
- script: |
echo "Build-Artefakte:"
ls -la dist/
displayName: 'Build-Ergebnis prüfen'
# Artefakte für nächste Stage bereitstellen
- publish: $(System.DefaultWorkingDirectory)/dist
artifact: app-build
displayName: 'Build-Artefakte publizieren'
# ===== Stage 2: Test (parallel Jobs) =====
- stage: Test
displayName: 'Test Stage'
dependsOn: Build
jobs:
- job: UnitTests
displayName: 'Unit Tests'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '$(nodeVersion)'
displayName: 'Node.js installieren'
- script: npm test
displayName: 'Unit Tests ausführen'
- job: LintCheck
displayName: 'Code Quality'
pool:
vmImage: 'ubuntu-latest'
steps:
- script: npm run lint
displayName: 'Lint ausführen'
# ===== Stage 3: Deploy Staging (nur auf master) =====
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Test
condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master'))
jobs:
- job: DeployJob
displayName: 'Deploy to Staging'
pool:
vmImage: 'ubuntu-latest'
steps:
- download: current
artifact: app-build
displayName: 'Build-Artefakte herunterladen'
- script: |
echo "=== Deployment nach Staging ==="
echo "Artefakte:"
ls -la $(Pipeline.Workspace)/app-build/
echo ""
echo "Simuliere Deployment..."
echo "Deployment nach Staging erfolgreich!"
displayName: 'Staging Deployment (simuliert)'
# ===== Stage 4: Deploy Production =====
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: DeployStaging
condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master'))
jobs:
- job: DeployProd
displayName: 'Deploy to Production'
pool:
vmImage: 'ubuntu-latest'
steps:
- download: current
artifact: app-build
displayName: 'Build-Artefakte herunterladen'
- script: |
echo "=== Deployment nach Production ==="
echo "Artefakte:"
ls -la $(Pipeline.Workspace)/app-build/
echo ""
echo "Simuliere Production Deployment..."
echo "Deployment nach Production erfolgreich!"
displayName: 'Production Deployment (simuliert)'
Gehe die Pipeline Abschnitt für Abschnitt durch:
- Build-Stage: Verwendet den
NodeTool@0-Task, um eine bestimmte Node.js-Version zu installieren. Nach dem Build wird dasdist/-Verzeichnis mitpublishals Artefakt namensapp-buildgespeichert. Ohne diesen Schritt wären die Build-Ergebnisse in den nachfolgenden Stages nicht verfügbar, da jede Stage auf einem frischen Agent läuft. - Test-Stage: Hat
dependsOn: Build, wartet also auf die Build-Stage. Enthält zwei Jobs (UnitTestsundLintCheck), die parallel laufen, da sie keinedependsOn-Beziehung zueinander haben. Das spart Zeit. - Deploy-Stages: Beide haben eine
condition, die prüft, ob der Build auf demmaster-Branch läuft.and(succeeded(), eq(...))stellt sicher, dass die Stage nur ausgeführt wird, wenn (a) alle vorherigen Stages erfolgreich waren und (b) der Branchmasterist. Auf Feature-Branches werden die Deploy-Stages übersprungen. - Artefakt-Download: In den Deploy-Stages wird mit
download: currentdas zuvor publizierte Artefakt heruntergeladen.currentbezieht sich auf den aktuellen Pipeline-Run.
Schritt 3: Committen und Pipeline beobachten¶
Committe alle neuen Dateien und pushe auf master. Die Pipeline wird automatisch
gestartet und durchläuft alle vier Stages:
git add package.json src/app.js test/test.js azure-pipelines.yml
git commit -m "Add multi-stage pipeline with Node.js app"
git push origin master
Schritt 4: Pipeline-Visualisierung anschauen¶
Azure DevOps zeigt Multi-Stage-Pipelines in einer grafischen Übersicht an, die den Ablauf und die Abhängigkeiten zwischen den Stages visualisiert.
- Öffne die Pipeline im Browser unter Pipelines und klicke auf den laufenden Build.
- Du siehst jetzt die Stage-Übersicht - eine horizontale Kette mit vier Stages: Build > Test > Deploy Staging > Deploy Production. Jede Stage zeigt ihren Status (wartend, laufend, erfolgreich, fehlgeschlagen).
- Klicke auf eine einzelne Stage, um deren Jobs und Steps zu sehen. Die Detail-Ansicht zeigt die Konsolenausgabe jedes Steps.
- Beachte in der Test-Stage: Die zwei Jobs (
UnitTestsundLintCheck) werden nebeneinander dargestellt, da sie parallel laufen. Vergleiche die Start- und Endzeiten, um zu sehen, dass sie tatsächlich gleichzeitig ausgeführt werden.
Schritt 5: Feature-Branch testen¶
Um die Conditions in Aktion zu sehen, erstellen wir einen Feature-Branch und beobachten, welche Stages übersprungen werden:
Bash:
git checkout -b feature/test-condition
echo "// neue Funktion" >> src/app.js
git add src/app.js
git commit -m "Test branch condition"
git push origin feature/test-condition
PowerShell:
git checkout -b feature/test-condition
Add-Content -Path src/app.js -Value "// neue Funktion" -NoNewline:$false
git add src/app.js
git commit -m "Test branch condition"
git push origin feature/test-condition
Da feature/test-condition nicht im CI-Trigger steht, wird der Build nicht
automatisch gestartet. Starte die Pipeline manuell im Browser: Gehe zu
Pipelines > hello-pipeline, klicke auf "Run pipeline" und wähle den
Branch feature/test-condition.
Beobachte das Ergebnis: Build und Test werden ausgeführt, aber die
beiden Deploy-Stages werden übersprungen (grau dargestellt). Der Grund ist
die condition: eq(variables['Build.SourceBranchName'], 'master') - auf einem
Feature-Branch ist diese Bedingung nicht erfüllt. So stellst du sicher, dass nur
getesteter Code auf master deployed wird.
Aufräumen¶
Lösche den Feature-Branch, da er nur für den Test benötigt wurde:
git checkout master
git branch -d feature/test-condition
git push origin --delete feature/test-condition