Lab 13b: Docker-Container auf Azure Container Instances deployen¶
Hintergrund¶
In Lab 13 haben wir eine Anwendung per ZIP-Paket auf einen App Service deployt. Aber was, wenn wir unsere Anwendung als Docker-Container betreiben wollen? In diesem Lab bauen wir ein Docker-Image, pushen es in eine Azure Container Registry (ACR) und deployen es auf Azure Container Instances (ACI) - den einfachsten Weg, einen Container in Azure zu starten.
Azure Container Instances (ACI) ist ein serverloser Container-Dienst: Kein Cluster, kein App Service Plan, keine VM - du gibst ein Image an und Azure startet es. Die Abrechnung erfolgt pro Sekunde nach tatsächlich genutzter CPU und RAM. Das macht ACI ideal für:
- Dev/Test: Schnell einen Container starten und wieder löschen
- Batch-Jobs: Einmalige Verarbeitungsaufgaben, die nach Abschluss gestoppt werden
- Einfache Microservices: Einzelne Container mit wenig Orchestrierungsbedarf
Der Unterschied zu den anderen Deployment-Optionen:
- App Service (Lab 13): PaaS für Webanwendungen, ZIP- oder Container-Deployment, mit Scaling, Slots, Custom Domains
- ACI (dieses Lab): Einzelne Container, schnell gestartet, Pay-per-Second
- AKS (Azure Kubernetes Service): Container-Orchestrierung für komplexe Microservice-Architekturen
Voraussetzungen¶
- Die Service Connection
azure-training-connection(Lab 05). - Das Environment
devaus Lab 11.
Aufgabenstellung¶
Schritt 1: Neues Repository erstellen¶
Erstelle in deinem Azure DevOps Projekt ein neues Git-Repository für dieses Lab:
- Gehe zu Repos, dann oben den Dropdown öffnen mit dem aktuellen Repo-Namen.
- Klicke auf "New Repository".
- Repository-Name:
aci-demo. - Klicke auf "Create".
- Klicke auf "Clone", kopiere die URL und klone das neue Repository lokal:
git clone <url-des-neuen-repos>
cd aci-demo
Schritt 2: Azure Container Registry erstellen¶
ACR-Namen müssen global eindeutig sein und dürfen nur Kleinbuchstaben und Zahlen enthalten (keine Bindestriche oder Unterstriche). Wir generieren einen eindeutigen Namen mit deinen Initialien:
Bash:
# Eindeutigen ACR-Namen generieren
INITIALS="deine_initialien_kleingeschrieben"
ACR_NAME="acraci$INITIALS$RANDOM"
echo "ACR Name: $ACR_NAME"
# ACR erstellen (Basic SKU: günstigste Option)
az acr create \
--name $ACR_NAME \
--resource-group rg-pipeline-training \
--location westeurope \
--sku Basic \
--admin-enabled true \
--output table
PowerShell:
# Eindeutigen ACR-Namen generieren
$INITIALS = "deine_initialien_kleingeschrieben"
$ACR_NAME = "acraci$INITIALS$(Get-Random -Maximum 32768)"
echo "ACR Name: $ACR_NAME"
# ACR erstellen (Basic SKU: günstigste Option)
az acr create `
--name $ACR_NAME `
--resource-group rg-pipeline-training `
--location westeurope `
--sku Basic `
--admin-enabled true `
--output table
Schritt 3: Anwendung und Dockerfile erstellen¶
Erstelle die Datei index.html im Repository-Wurzelverzeichnis:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>ACI Demo</title>
</head>
<body>
<h1>Hello from Azure Container Instances!</h1>
<p>Dieses Image wurde per Azure Pipeline gebaut und auf ACI deployt.</p>
</body>
</html>
Erstelle die Datei Dockerfile:
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -q --spider http://localhost/ || exit 1
Erstelle außerdem eine .dockerignore-Datei, um unnötige Dateien vom Docker-Build-Kontext auszuschließen:
.git
*.md
azure-pipelines.yml
Schritt 4: Service Connection für ACR erstellen¶
Damit die Pipeline Images in die ACR pushen kann, braucht sie eine authentifizierte Verbindung. Wir erstellen eine Docker Registry Service Connection in Azure DevOps:
- Gehe zu Project Settings > Service connections.
- Klicke auf "New service connection".
- Wähle "Docker Registry".
- Registry type: "Azure Container Registry".
- Authentication Type: "Service Principal".
- Subscription: Wähle die Trainings-Subscription.
- Azure Container Registry: Wähle deine soeben erstellte ACR aus der Dropdown-Liste.
- Service Connection Name:
acr-aci-connection. - Setze den Haken bei "Grant access permission to all pipelines".
- Klicke auf "Save".
Schritt 5: Pipeline mit Build und ACI-Deployment¶
Wir erstellen eine Pipeline mit drei Stages: Build baut das Docker-Image und pusht es in die ACR, Deploy erstellt eine Container Instance mit dem Image, und Verify prüft, ob der Container läuft und erreichbar ist.
Erstelle die Datei azure-pipelines.yml. Wichtig: Ersetze dabei den
Platzhalter <dein-acr-name> an beiden Stellen mit deinem tatsächlichen
ACR-Namen (den du in Schritt 2 generiert hast):
trigger:
branches:
include:
- main
- master
variables:
azureSubscription: 'azure-training-connection'
# Ersetze mit deinem ACR-Namen aus Schritt 2
acrName: '<dein-acr-name>'
acrLoginServer: '<dein-acr-name>.azurecr.io'
imageName: 'aci-demo'
imageTag: '$(Build.BuildNumber)'
containerPrefix: 'aci-demo'
stages:
# ===== Build =====
- stage: Build
displayName: 'Build Docker Image'
jobs:
- job: DockerBuild
displayName: 'Build and Push'
pool:
vmImage: 'ubuntu-latest'
steps:
# ACR-Login für den Cache-Pull
- task: Docker@2
displayName: 'ACR Login'
inputs:
containerRegistry: 'acr-aci-connection'
command: 'login'
# Vorheriges Image als Cache-Quelle pullen
- script: |
docker pull $(acrLoginServer)/$(imageName):latest || true
displayName: 'Cache: Vorheriges Image pullen'
- script: |
docker build \
--cache-from=$(acrLoginServer)/$(imageName):latest \
-t $(acrLoginServer)/$(imageName):$(imageTag) \
-t $(acrLoginServer)/$(imageName):latest \
.
displayName: 'Docker Build (mit Layer Cache)'
- script: |
docker push $(acrLoginServer)/$(imageName):$(imageTag)
docker push $(acrLoginServer)/$(imageName):latest
displayName: 'Docker Push'
# ===== Deploy to ACI =====
- stage: Deploy
displayName: 'Deploy to ACI'
dependsOn: Build
jobs:
- deployment: DeployACI
displayName: 'Deploy Container'
pool:
vmImage: 'ubuntu-latest'
environment: 'dev'
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: 'Container auf ACI deployen'
inputs:
azureSubscription: '$(azureSubscription)'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Projektname in ACI-konformen Slug umwandeln
RAW_PROJECT="$(System.TeamProject)"
PROJECT_SLUG=$(echo "$RAW_PROJECT" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')
PROJECT_SLUG="${PROJECT_SLUG:0:20}"
CONTAINER_NAME="$(containerPrefix)-${PROJECT_SLUG}"
DNS_LABEL="${CONTAINER_NAME}-$(Build.BuildId)"
echo "Container-Name: $CONTAINER_NAME"
echo "DNS-Label: $DNS_LABEL"
# ACR-Credentials holen
ACR_PASSWORD=$(az acr credential show \
--name $(acrName) \
--query "passwords[0].value" -o tsv)
# Bestehenden Container löschen (falls vorhanden)
az container delete \
--name "$CONTAINER_NAME" \
--resource-group rg-pipeline-training \
--yes 2>/dev/null || true
# Auf asynchrone Löschung warten, damit create nicht kollidiert
for i in {1..24}; do
if ! az container show \
--name "$CONTAINER_NAME" \
--resource-group rg-pipeline-training \
--output none 2>/dev/null; then
echo "Vorheriger Container ist gelöscht."
break
fi
echo "Container wird noch gelöscht... warte 5s"
sleep 5
done
# Container erstellen
az container create \
--name "$CONTAINER_NAME" \
--resource-group rg-pipeline-training \
--image $(acrLoginServer)/$(imageName):$(imageTag) \
--registry-login-server $(acrLoginServer) \
--registry-username $(acrName) \
--registry-password "$ACR_PASSWORD" \
--dns-name-label "$DNS_LABEL" \
--ports 80 \
--os-type Linux \
--cpu 1 \
--memory 0.5 \
--output table
echo ""
echo "Container erstellt. FQDN:"
az container show \
--name "$CONTAINER_NAME" \
--resource-group rg-pipeline-training \
--query "ipAddress.fqdn" -o tsv
# ===== Verify =====
- stage: Verify
displayName: 'Container prüfen'
dependsOn: Deploy
jobs:
- job: VerifyContainer
pool:
vmImage: 'ubuntu-latest'
steps:
- task: AzureCLI@2
displayName: 'Container-Status und Health Check'
inputs:
azureSubscription: '$(azureSubscription)'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
RAW_PROJECT="$(System.TeamProject)"
PROJECT_SLUG=$(echo "$RAW_PROJECT" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')
PROJECT_SLUG="${PROJECT_SLUG:0:20}"
CONTAINER_NAME="$(containerPrefix)-${PROJECT_SLUG}"
echo "=== Container Status ==="
az container show \
--name "$CONTAINER_NAME" \
--resource-group rg-pipeline-training \
--query "{name:name, state:instanceView.state, image:containers[0].image, fqdn:ipAddress.fqdn}" \
--output table
FQDN=$(az container show \
--name "$CONTAINER_NAME" \
--resource-group rg-pipeline-training \
--query "ipAddress.fqdn" -o tsv)
echo ""
echo "=== Health Check ==="
echo "Warte 15 Sekunden auf Container-Start..."
sleep 15
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://$FQDN")
if [ "$HTTP_STATUS" = "200" ]; then
echo "HTTP $HTTP_STATUS - Container ist erreichbar unter http://$FQDN"
else
echo "WARNUNG: HTTP $HTTP_STATUS - Container antwortet nicht wie erwartet"
fi
Schritt 6: Committen und Pipeline starten¶
Committe alle Dateien und pushe:
git add index.html Dockerfile .dockerignore azure-pipelines.yml
git commit -m "Add ACI deployment pipeline"
git push
Erstelle anschließend die Pipeline in Azure DevOps:
- Gehe zu Pipelines > New pipeline.
- Wähle "Azure Repos Git".
- Wähle das Repository "aci-demo".
- Wähle "Existing Azure Pipelines YAML file" und wähle
/azure-pipelines.yml. - Klicke auf "Run".
Beim ersten Lauf mit den Service Connections und dem Environment muss die Nutzung möglicherweise einmalig genehmigt werden. Öffne den Pipeline-Run im Browser - du siehst eine Meldung wie "This pipeline needs permission to access a resource before this run can continue". Klicke auf "View" und dann auf "Permit".
Schritt 7: Container im Browser prüfen¶
Nach erfolgreichem Deployment findest du den FQDN (Fully Qualified Domain Name) des Containers im Log der Deploy- oder Verify-Stage.
Öffne http://<fqdn> im Browser - du solltest die HTML-Seite mit "Hello from
Azure Container Instances!" sehen.
Im Azure Portal kannst du unter Container Instances deinen Container inspizieren:
- Containers > Logs: Stdout/Stderr des laufenden Containers
- Containers > Events: Start- und Pull-Events
- Monitoring > Metrics: CPU- und Speicherverbrauch