Lab 14: Blue/Green Deployment¶
Hintergrund¶
In Lab 13 haben wir eine Anwendung direkt auf den App Service deployt. Das funktioniert, hat aber einen Nachteil: Während des Deployments kann es zu kurzen Ausfallzeiten kommen, und wenn die neue Version fehlerhaft ist, muss man ein erneutes Deployment der alten Version durchführen - was Zeit kostet und während der Fehlerbehebung die Anwendung in einem defekten Zustand belässt.
Blue/Green Deployment löst dieses Problem: Die neue Version (Green) wird neben der aktuellen Version (Blue) deployt, ohne den laufenden Betrieb zu beeinträchtigen. Erst nach erfolgreichen Tests wird der Traffic von Blue auf Green umgeleitet. Bei Problemen kann sofort zurück auf Blue geschaltet werden - ohne erneutes Deployment, ohne Ausfallzeit.
Azure App Service bietet Deployment Slots als Mechanismus für Blue/Green Deployments. Ein Deployment Slot ist eine eigenständige Instanz der Anwendung mit eigener URL, eigenen Umgebungsvariablen und eigener Konfiguration - aber sie teilt sich den App Service Plan (also die Compute-Ressourcen) mit dem Haupt-Slot. Der Ablauf ist:
- Die aktuelle Produktion läuft im Haupt-Slot (Production = Blue).
- Die neue Version wird in den Staging-Slot deployt (Green).
- Tests laufen gegen die Staging-URL (
<app>-staging.azurewebsites.net). - Die Slots werden getauscht (Swap): Der Traffic geht jetzt auf die neue Version. Der Swap ist ein DNS/Routing-Wechsel, kein erneutes Deployment.
- Bei Problemen nach dem Swap: erneut swappen - die alte Version ist noch im Staging-Slot und sofort wieder live.
Slot-Erkennung zur Laufzeit¶
Node.js liest Umgebungsvariablen beim Prozessstart.
Beim Slot-Swap werden Apps nicht in jedem Fall so neu gestartet, dass eine
geänderte Slot-Variable sofort im Prozess sichtbar wird. Für die Anzeige des
aktuellen Slots ermitteln wir ihn daher zur Laufzeit aus dem Hostnamen der
eingehenden Anfrage (<app>.azurewebsites.net vs.
<app>-staging.azurewebsites.net).
Hinweis: Deployment Slots erfordern mindestens den Standard (S1)-Tarif. Der für das Training bereitgestellte S1-Plan unterstützt Deployment Slots und wird am Ende des Trainings automatisch per Terraform aufgeräumt.
Voraussetzungen¶
- Die Service Connection
azure-training-connectionaus Lab 05. - Die Environments
stagingundproductionaus Lab 11. - Deine Web App aus Lab 13 (
app-training-teilnehmerNN) - diese läuft bereits auf einem S1-Plan und unterstützt Deployment Slots.
Aufgabenstellung¶
Schritt 1: Staging-Slot erstellen¶
Deine Web App (app-training-teilnehmerNN) läuft bereits auf einem S1-Plan,
der Deployment Slots unterstützt. In diesem Schritt erstellst du den
Staging-Slot.
Falls noch nicht geschehen in der letzten Übungsaufgabe, setze den APP_NAME als Variable: Bash:
# Setze deinen App-Namen (ersetze NN mit deiner Teilnehmernummer)
APP_NAME="app-training-teilnehmerNN"
PowerShell:
# Setze deinen App-Namen (ersetze NN mit deiner Teilnehmernummer)
$APP_NAME = "app-training-teilnehmerNN"
Erzeuge danach einen Staging-Slot für das Deployment.
Bash:
# Staging Slot erstellen
az webapp deployment slot create \
--name $APP_NAME \
--resource-group rg-pipeline-training \
--slot staging \
--output table
echo "Production URL: https://$APP_NAME.azurewebsites.net"
echo "Staging URL: https://$APP_NAME-staging.azurewebsites.net"
PowerShell:
# Staging Slot erstellen
az webapp deployment slot create `
--name $APP_NAME `
--resource-group rg-pipeline-training `
--slot staging `
--output table
echo "Production URL: https://$APP_NAME.azurewebsites.net"
echo "Staging URL: https://$APP_NAME-staging.azurewebsites.net"
Hier wird nur der Staging-Slot explizit erstellt. Der Production-Slot
ist die Web App selbst - er existiert automatisch seit az webapp create in
Lab 13. Befehle ohne --slot wirken immer auf diesen Production-Slot.
Nach der Erstellung hast du zwei erreichbare URLs: Die Production-URL
(<app>.azurewebsites.net) und die Staging-URL
(<app>-staging.azurewebsites.net). Beide sind unabhängige Instanzen - ein
Deployment in den Staging-Slot hat keine Auswirkung auf die Production-URL.
Schritt 2: App mit Versionsanzeige vorbereiten (v1)¶
Um den Blue/Green-Wechsel visuell nachvollziehen zu können, erstellen wir einen Server mit Versionsanzeige und farblicher Unterscheidung der Slots. Diese erste Version wird unsere "Blue"-Version - die stabile Version in Production.
Erstelle die Datei src/server.js:
const http = require('http');
const port = process.env.PORT || 8080;
// Version aus Umgebungsvariable oder Build-Info
const APP_VERSION = process.env.APP_VERSION || '1.0.0';
function getSlotName(req) {
// Slot aus Hostname ableiten, damit kein App-Restart notwendig ist
const host = (req.headers.host || '').toLowerCase();
return host.includes('-staging.azurewebsites.net') ? 'staging' : 'production';
}
const server = http.createServer((req, res) => {
const SLOT_NAME = getSlotName(req);
if (req.url === '/health') {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({
status: 'healthy',
version: APP_VERSION,
slot: SLOT_NAME,
timestamp: new Date().toISOString()
}));
return;
}
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(`
<!DOCTYPE html>
<html>
<head><title>Hello Pipeline - v${APP_VERSION}</title></head>
<body style="font-family: Arial; padding: 40px; background: ${SLOT_NAME === 'staging' ? '#d4edda' : '#cce5ff'};">
<h1>Hello from Azure Pipelines!</h1>
<p><strong>Version:</strong> ${APP_VERSION}</p>
<p><strong>Slot:</strong> ${SLOT_NAME}</p>
<p><strong>Time:</strong> ${new Date().toISOString()}</p>
</body>
</html>
`);
});
server.listen(port, () => {
console.log(`Server v${APP_VERSION} running on port ${port}`);
});
Die Hauptseite zeigt je nach Slot eine unterschiedliche Hintergrundfarbe: Grün für Staging (Green), Blau für Production (Blue). So siehst du im Browser auf einen Blick, welcher Slot gerade aktiv ist.
Schritt 3: Version 1 nach Production deployen¶
Bevor wir die Pipeline erstellen, deployen wir Version 1 direkt nach Production. So haben wir eine laufende "Blue"-Version, gegen die wir später den Blue/Green-Wechsel demonstrieren können.
az webapp up --name $APP_NAME --resource-group rg-pipeline-training --runtime "NODE:20-lts"
Prüfe, ob die App läuft:
Bash:
curl https://$APP_NAME.azurewebsites.net/health
PowerShell:
Invoke-RestMethod https://$APP_NAME.azurewebsites.net/health
Du solltest diese Antwort sehen:
{"status":"healthy","version":"1.0.0","slot":"production","timestamp":"..."}
Öffne auch https://<app-name>.azurewebsites.net im Browser: Du siehst die
Seite mit blauem Hintergrund, dem Titel "Hello from Azure Pipelines!" und
Slot: production. Das ist deine stabile Blue-Version.
Schritt 4: Code für Version 2 ändern¶
Jetzt ändern wir den Code, um eine sichtbar andere Version zu erstellen. Ändere in src/server.js den Titel von:
<h1>Hello from Azure Pipelines!</h1>
zu:
<h1>Hello from Azure Pipelines v2!</h1>
So ist der Unterschied zwischen v1 (Production) und v2 (Staging) im Browser sofort erkennbar.
Schritt 5: Pipeline mit Blue/Green Deployment¶
Jetzt erstellen wir die Pipeline mit vier Stages, die den Blue/Green-Ablauf abbilden: Build, Deploy in den Staging-Slot (Green), Testen des Staging-Slots, und Swap der Slots.
Ersetze den Inhalt von azure-pipelines.yml. Wichtig: Ersetze den
Platzhalter <dein-app-name> mit deinem tatsächlichen App-Namen:
trigger:
branches:
include:
- master
variables:
azureSubscription: 'azure-training-connection'
appName: '<dein-app-name>'
appVersion: '1.0.$(Build.BuildId)'
stages:
# ===== Build =====
- stage: Build
displayName: 'Build'
jobs:
- job: BuildApp
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: npm install --production
displayName: 'Dependencies installieren'
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: '$(System.DefaultWorkingDirectory)'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/app.zip'
- publish: '$(Build.ArtifactStagingDirectory)/app.zip'
artifact: 'web-app'
# ===== Deploy to Staging Slot (Green) =====
- stage: DeployGreen
displayName: 'Deploy Green (Staging Slot)'
dependsOn: Build
jobs:
- deployment: DeployToSlot
displayName: 'Deploy to Staging Slot'
pool:
vmImage: 'ubuntu-latest'
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
# Deploy in den Staging-Slot
- task: AzureWebApp@1
displayName: 'Deploy to Staging Slot'
inputs:
azureSubscription: '$(azureSubscription)'
appType: 'webAppLinux'
appName: '$(appName)'
package: '$(Pipeline.Workspace)/web-app/app.zip'
deployToSlotOrASE: true
slotName: 'staging'
runtimeStack: 'NODE|20-lts'
# App-Version für den Staging-Slot setzen
- task: AzureCLI@2
displayName: 'Staging-Slot konfigurieren'
inputs:
azureSubscription: '$(azureSubscription)'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az webapp config appsettings set \
--name $(appName) \
--resource-group rg-pipeline-training \
--slot staging \
--settings APP_VERSION=$(appVersion)
# ===== Test Green =====
- stage: TestGreen
displayName: 'Test Green'
dependsOn: DeployGreen
jobs:
- job: SmokeTest
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
echo "=== Smoke Test gegen Staging Slot ==="
STAGING_URL="https://$(appName)-staging.azurewebsites.net"
echo "Warte auf App-Start..."
sleep 30
echo "Test 1: Health Check"
RESPONSE=$(curl -s "$STAGING_URL/health")
echo "Response: $RESPONSE"
echo ""
echo "Test 2: Homepage erreichbar"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$STAGING_URL")
echo "HTTP Status: $HTTP_CODE"
if [ "$HTTP_CODE" = "200" ]; then
echo ""
echo "Alle Smoke Tests bestanden!"
else
echo "FEHLER: Smoke Tests fehlgeschlagen!"
exit 1
fi
displayName: 'Smoke Tests'
# ===== Swap Slots (Blue/Green Switch) =====
- stage: SwapSlots
displayName: 'Swap Blue/Green'
dependsOn: TestGreen
jobs:
- deployment: SwapToProduction
displayName: 'Swap Staging to Production'
pool:
vmImage: 'ubuntu-latest'
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: 'Slots tauschen'
inputs:
azureSubscription: '$(azureSubscription)'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
echo "=== Slot Swap ==="
echo "Tausche Staging <-> Production..."
echo ""
az webapp deployment slot swap \
--name $(appName) \
--resource-group rg-pipeline-training \
--slot staging \
--target-slot production
echo "Swap abgeschlossen!"
echo ""
echo "Production URL: https://$(appName).azurewebsites.net"
- script: |
echo "=== Post-Swap Validierung ==="
sleep 10
PROD_URL="https://$(appName).azurewebsites.net/health"
RESPONSE=$(curl -s "$PROD_URL")
echo "Production Health: $RESPONSE"
displayName: 'Post-Swap Check'
Gehe die Pipeline Abschnitt für Abschnitt durch:
- Build-Stage: Identisch zu Lab 13 - installiert Abhängigkeiten, packt alles in ein ZIP und publiziert es als Artefakt.
- DeployGreen-Stage: Deployt die neue Version in den Staging-Slot
(Green), nicht in den Haupt-Slot. Der entscheidende Unterschied zu Lab 13 sind
die Parameter
deployToSlotOrASE: trueundslotName: 'staging'imAzureWebApp@1-Task. Anschließend setzt einAzureCLI@2-Task das App-SettingAPP_VERSIONfür den Staging-Slot. - TestGreen-Stage: Führt Smoke Tests gegen die Staging-URL aus
(
<app>-staging.azurewebsites.net). Beachte, dass dieser Stage ein normaler Job (kein Deployment Job) ist - er führt nur Tests aus, deployt nichts. Die Tests prüfen den Health-Endpoint und die Erreichbarkeit der Homepage. Nur wenn beide Tests bestehen, geht die Pipeline weiter. - SwapSlots-Stage: Der eigentliche Blue/Green-Switch. Ein Deployment Job,
der auf das
production-Environment verweist (und damit den in Lab 12 konfigurierten Approval Gate auslöst, falls vorhanden). Deraz webapp deployment slot swap-Befehl tauscht Staging und Production atomar: Der Traffic geht sofort auf die neue Version, und die alte Version landet im Staging-Slot (als Rollback-Option). Nach dem Swap prüft ein Post-Swap-Check den Health-Endpoint der Production-URL.
Schritt 6: Committen und Pipeline starten¶
Committe und pushe alle Dateien:
git add src/server.js azure-pipelines.yml
git commit -m "Add blue/green deployment with slot swap"
git push origin master
Falls auf dem Production-Environment ein Approval Gate konfiguriert ist (Lab 12), wird die Pipeline vor der SwapSlots-Stage pausieren. Klicke im Browser auf "Review" > "Approve", um den Swap freizugeben.
Schritt 7: Beide Slots vergleichen¶
Nachdem die Pipeline v2 in den Staging-Slot deployt hat (aber vor dem Swap), laufen zwei verschiedene Versionen nebeneinander. Das ist der Sinn von Blue/Green-Deployments:
Bash:
# Production: v1 (Blue) - die alte, stabile Version
curl https://$APP_NAME.azurewebsites.net/health
# Staging: v2 (Green) - die neue Version
curl https://$APP_NAME-staging.azurewebsites.net/health
PowerShell:
# Production: v1 (Blue) - die alte, stabile Version
Invoke-RestMethod https://$APP_NAME.azurewebsites.net/health
# Staging: v2 (Green) - die neue Version
Invoke-RestMethod https://$APP_NAME-staging.azurewebsites.net/health
Öffne beide URLs auch im Browser, am besten in einer privaten Sitzung, damit kein Caching greift:
- Production (
<app>.azurewebsites.net): Blauer Hintergrund, Titel "Hello from Azure Pipelines!",version: 1.0.0,slot: production. - Staging (
<app>-staging.azurewebsites.net): Grüner Hintergrund, Titel "Hello from Azure Pipelines v2!",version: 1.0.XX,slot: staging.
Du siehst zwei verschiedene Versionen gleichzeitig - die alte und die neue. Die Produktion ist zu keinem Zeitpunkt gestört. Erst wenn du den Swap freigibst, wechselt der Traffic.
Schritt 8: Swap und Post-Swap-Validierung¶
Nachdem du den Swap freigegeben hast (oder falls kein Approval Gate konfiguriert ist: automatisch), tauschen die Slots. Prüfe danach beide URLs erneut:
Bash:
# Production: jetzt v2 (neue Version)
curl https://$APP_NAME.azurewebsites.net/health
# Staging: jetzt v1 (alte Version - bereit für Rollback)
curl https://$APP_NAME-staging.azurewebsites.net/health
PowerShell:
# Production: jetzt v2 (neue Version)
Invoke-RestMethod https://$APP_NAME.azurewebsites.net/health
# Staging: jetzt v1 (alte Version - bereit für Rollback)
Invoke-RestMethod https://$APP_NAME-staging.azurewebsites.net/health
Erwartete Ergebnisse:
- Production:
version: 1.0.XX,slot: production- die neue Version läuft jetzt in Production. - Staging:
version: 1.0.0,slot: staging- die alte Version ist im Staging-Slot gelandet, bereit für einen sofortigen Rollback.
Im Browser siehst du: Production hat weiterhin einen blauen Hintergrund und
Staging einen grünen - obwohl der Code getauscht wurde. Die Farbe folgt dem
Slot, nicht dem Code, weil slot pro Request aus der URL ermittelt wird.
Schritt 9: Rollback testen¶
Ein großer Vorteil von Blue/Green Deployments: Der Rollback ist ein erneuter Swap. Es ist keine erneute Build/Deploy-Kette nötig. Teste das manuell:
Bash:
# Erneuter Swap: Production und Staging werden wieder getauscht
az webapp deployment slot swap \
--name $APP_NAME \
--resource-group rg-pipeline-training \
--slot staging \
--target-slot production
# Prüfe, ob die alte Version wieder in Production ist
curl https://$APP_NAME.azurewebsites.net/health
PowerShell:
# Erneuter Swap: Production und Staging werden wieder getauscht
az webapp deployment slot swap `
--name $APP_NAME `
--resource-group rg-pipeline-training `
--slot staging `
--target-slot production
# Prüfe, ob die alte Version wieder in Production ist
Invoke-RestMethod https://$APP_NAME.azurewebsites.net/health
Aufräumen¶
Die Web App und der App Service Plan werden zentral per Terraform verwaltet. Lösche diese Ressourcen nicht manuell - sie werden am Ende des Trainings automatisch aufgeräumt.
Lösche nur den Staging-Slot, den du in diesem Lab manuell erstellt hast:
az webapp deployment slot delete --name $APP_NAME --resource-group rg-pipeline-training --slot staging