Desafio 28: Deployments baseados em containers
Habilidades do exame mapeadas
- Implementar deployment de aplicação usando containers, binários e scripts
Cenário
A Contoso Ltd está containerizando sua aplicação monolítica de e-commerce em três microsserviços: Product Catalog API, Order Processing Service e Notification Service. A equipe precisa construir um pipeline de CI/CD abrangente que lide com build de imagens de container, escaneamento de vulnerabilidades, gerenciamento de registry e deployment tanto para Azure Container Apps quanto para Azure Kubernetes Service.
Detalhes do ambiente:
- Azure Container Registry:
acrcontosoprod - Azure Container Apps Environment:
cae-contoso-prod - Cluster AKS:
aks-contoso-prod - Resource group:
rg-contoso-containers - Região: East US 2
Tarefa 1: Construir imagem Docker no GitHub Actions
Dockerfile para a Product Catalog API
Crie src/ProductCatalog/Dockerfile:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["ProductCatalog.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
EXPOSE 8080
# Run as non-root user
RUN adduser --disabled-password --gecos "" appuser
USER appuser
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "ProductCatalog.dll"]
Workflow do GitHub Actions para build de container
Crie .github/workflows/container-build.yml:
name: Container Build and Push
on:
push:
branches: [main]
paths:
- 'src/ProductCatalog/**'
pull_request:
branches: [main]
paths:
- 'src/ProductCatalog/**'
env:
REGISTRY: acrcontosoprod.azurecr.io
IMAGE_NAME: product-catalog-api
RESOURCE_GROUP: rg-contoso-containers
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Azure
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Login to Azure Container Registry
run: az acr login --name acrcontosoprod
- name: Generate image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=,format=short
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: src/ProductCatalog
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Tarefa 2: Fazer push para Azure Container Registry com tagging adequado
Provisionar o ACR
RESOURCE_GROUP="rg-contoso-containers"
LOCATION="eastus2"
ACR_NAME="acrcontosoprod"
# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create ACR with Premium SKU (supports geo-replication, content trust)
az acr create \
--name $ACR_NAME \
--resource-group $RESOURCE_GROUP \
--sku Premium \
--admin-enabled false
# Enable content trust (image signing)
az acr config content-trust update \
--registry $ACR_NAME \
--status enabled
Estratégia de tagging
# Build with multiple tags: semver + git SHA + latest
IMAGE="acrcontosoprod.azurecr.io/product-catalog-api"
VERSION="1.2.3"
SHA=$(git rev-parse --short HEAD)
az acr build \
--registry $ACR_NAME \
--image "${IMAGE}:${VERSION}" \
--image "${IMAGE}:${SHA}" \
--image "${IMAGE}:latest" \
--file src/ProductCatalog/Dockerfile \
src/ProductCatalog/
Configurar políticas de retenção e limpeza
# Set retention policy (delete untagged manifests after 7 days)
az acr config retention update \
--registry $ACR_NAME \
--status enabled \
--days 7 \
--type UntaggedManifests
# Purge old images (keep last 10 tagged versions)
az acr run \
--registry $ACR_NAME \
--cmd "acr purge --filter 'product-catalog-api:.*' --ago 30d --keep 10 --untagged" \
/dev/null
Tarefa 3: Fazer push para GitHub Container Registry (GHCR)
Workflow do GitHub Actions para GHCR
push-to-ghcr:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate image metadata for GHCR
id: meta-ghcr
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}/product-catalog-api
tags: |
type=semver,pattern={{version}}
type=sha,prefix=,format=short
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push to GHCR
uses: docker/build-push-action@v6
with:
context: src/ProductCatalog
push: true
tags: ${{ steps.meta-ghcr.outputs.tags }}
labels: ${{ steps.meta-ghcr.outputs.labels }}
Tarefa 4: Fazer deploy no Azure Container Apps via GitHub Actions
Provisionar ambiente do Azure Container Apps
# Create Container Apps environment
az containerapp env create \
--name cae-contoso-prod \
--resource-group $RESOURCE_GROUP \
--location $LOCATION
# Create the container app
az containerapp create \
--name ca-product-catalog \
--resource-group $RESOURCE_GROUP \
--environment cae-contoso-prod \
--image acrcontosoprod.azurecr.io/product-catalog-api:latest \
--registry-server acrcontosoprod.azurecr.io \
--registry-identity system \
--target-port 8080 \
--ingress external \
--min-replicas 2 \
--max-replicas 10 \
--cpu 0.5 \
--memory 1.0Gi
Workflow de deployment do GitHub Actions
Crie .github/workflows/deploy-container-apps.yml:
name: Deploy to Azure Container Apps
on:
workflow_run:
workflows: ["Container Build and Push"]
types: [completed]
branches: [main]
env:
RESOURCE_GROUP: rg-contoso-containers
CONTAINER_APP: ca-product-catalog
REGISTRY: acrcontosoprod.azurecr.io
IMAGE_NAME: product-catalog-api
jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@v4
- name: Login to Azure
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Get image tag from triggering workflow
id: image-tag
run: |
SHA=$(echo "${{ github.event.workflow_run.head_sha }}" | cut -c1-7)
echo "tag=${SHA}" >> $GITHUB_OUTPUT
- name: Deploy to Container Apps
run: |
az containerapp update \
--name ${{ env.CONTAINER_APP }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image-tag.outputs.tag }}
- name: Verify deployment
run: |
FQDN=$(az containerapp show \
--name ${{ env.CONTAINER_APP }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--query "properties.configuration.ingress.fqdn" -o tsv)
for i in {1..10}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://$FQDN/health")
if [ "$STATUS" == "200" ]; then
echo "Deployment verified successfully"
exit 0
fi
echo "Attempt $i: status=$STATUS, waiting..."
sleep 15
done
echo "Deployment verification failed"
exit 1
- name: Rollback on failure
if: failure()
run: |
# Get the previous revision
PREVIOUS_REVISION=$(az containerapp revision list \
--name ${{ env.CONTAINER_APP }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--query "[-2].name" -o tsv)
az containerapp ingress traffic set \
--name ${{ env.CONTAINER_APP }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--revision-weight "$PREVIOUS_REVISION=100"
Tarefa 5: Fazer deploy no AKS via Azure Pipelines
Manifesto de deployment do Kubernetes
Crie k8s/product-catalog/deployment.yml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-catalog-api
namespace: contoso-apps
labels:
app: product-catalog-api
version: "1.0"
spec:
replicas: 3
selector:
matchLabels:
app: product-catalog-api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: product-catalog-api
spec:
containers:
- name: product-catalog-api
image: acrcontosoprod.azurecr.io/product-catalog-api:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: product-catalog-secrets
key: db-connection-string
---
apiVersion: v1
kind: Service
metadata:
name: product-catalog-api
namespace: contoso-apps
spec:
selector:
app: product-catalog-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
YAML do Azure Pipelines para deployment no AKS
Crie azure-pipelines-aks.yml:
trigger:
branches:
include:
- main
paths:
include:
- src/ProductCatalog/**
pool:
vmImage: 'ubuntu-latest'
variables:
azureSubscription: 'contoso-production-connection'
resourceGroup: 'rg-contoso-containers'
acrName: 'acrcontosoprod'
aksCluster: 'aks-contoso-prod'
imageRepository: 'product-catalog-api'
namespace: 'contoso-apps'
tag: '$(Build.BuildId)'
stages:
- stage: Build
displayName: 'Build and push image'
jobs:
- job: BuildImage
steps:
- task: Docker@2
displayName: 'Build and push to ACR'
inputs:
containerRegistry: 'acr-contoso-connection'
repository: $(imageRepository)
command: 'buildAndPush'
Dockerfile: 'src/ProductCatalog/Dockerfile'
buildContext: 'src/ProductCatalog'
tags: |
$(tag)
latest
- stage: Deploy
displayName: 'Deploy to AKS'
dependsOn: Build
jobs:
- deployment: DeployToAKS
environment: 'production.contoso-apps'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@1
displayName: 'Deploy to AKS'
inputs:
action: 'deploy'
connectionType: 'azureResourceManager'
azureSubscriptionConnection: $(azureSubscription)
azureResourceGroup: $(resourceGroup)
kubernetesCluster: $(aksCluster)
namespace: $(namespace)
manifests: |
k8s/product-catalog/deployment.yml
containers: |
$(acrName).azurecr.io/$(imageRepository):$(tag)
- task: Kubernetes@1
displayName: 'Verify rollout status'
inputs:
connectionType: 'Azure Resource Manager'
azureSubscriptionEndpoint: $(azureSubscription)
azureResourceGroup: $(resourceGroup)
kubernetesCluster: $(aksCluster)
namespace: $(namespace)
command: 'rollout'
arguments: 'status deployment/product-catalog-api --timeout=300s'
Tarefa 6: Escaneamento de imagem com Trivy e Defender for Containers
Escaneamento com Trivy no GitHub Actions
scan-image:
runs-on: ubuntu-latest
needs: build-and-push
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
Habilitar Microsoft Defender for Containers no ACR
# Enable Defender for Containers (includes vulnerability assessment)
az security pricing create \
--name Containers \
--tier Standard
# Check scan results for an image
az acr repository show \
--name $ACR_NAME \
--image product-catalog-api:latest \
--query "changeableAttributes"
Escaneamento durante build no ACR (nativo do Azure)
# Build with automatic Defender scanning
az acr build \
--registry $ACR_NAME \
--image product-catalog-api:$(git rev-parse --short HEAD) \
--file src/ProductCatalog/Dockerfile \
src/ProductCatalog/
# Query vulnerability assessment results
az acr repository show-manifests \
--name $ACR_NAME \
--repository product-catalog-api \
--query "[0].digest" -o tsv
Tarefa 7: Builds multi-arquitetura (linux/amd64, linux/arm64)
Build multi-arch com Docker Buildx no GitHub Actions
build-multi-arch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU (for cross-platform builds)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Azure Container Registry
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Login to ACR
run: az acr login --name acrcontosoprod
- name: Build and push multi-arch image
uses: docker/build-push-action@v6
with:
context: src/ProductCatalog
platforms: linux/amd64,linux/arm64
push: true
tags: |
acrcontosoprod.azurecr.io/product-catalog-api:${{ github.sha }}
acrcontosoprod.azurecr.io/product-catalog-api:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Verificar manifesto multi-arch
# Inspect the multi-arch manifest
az acr manifest list-metadata \
--registry $ACR_NAME \
--name product-catalog-api \
--query "[0].{digest:digest, architecture:architecture, os:os}"
# Check specific architecture support
docker manifest inspect acrcontosoprod.azurecr.io/product-catalog-api:latest
Exercícios de quebra e conserto
Exercício 1: Falha de autenticação no ACR no pipeline
Sintoma: O workflow do GitHub Actions falha com unauthorized: authentication required ao fazer push para o ACR.
Investigar:
# Check if the service principal has AcrPush role
az role assignment list \
--scope "/subscriptions/<sub-id>/resourceGroups/rg-contoso-containers/providers/Microsoft.ContainerRegistry/registries/acrcontosoprod" \
--query "[?roleDefinitionName=='AcrPush'].{principal:principalName, role:roleDefinitionName}"
Mostrar solução
Causa raiz: O service principal no secret AZURE_CREDENTIALS possui apenas a role AcrPull, não AcrPush.
Correção:
# Assign AcrPush role to the service principal
SP_ID=$(az ad sp show --id <app-id> --query id -o tsv)
az role assignment create \
--assignee $SP_ID \
--role AcrPush \
--scope "/subscriptions/<sub-id>/resourceGroups/rg-contoso-containers/providers/Microsoft.ContainerRegistry/registries/acrcontosoprod"
Exercício 2: Container app falhando health checks após deployment
Sintoma: Após fazer deploy de uma nova imagem no Azure Container Apps, a revisão nunca se torna ativa. Os logs mostram tentativas repetidas de reinicialização.
Investigar:
# Check revision status
az containerapp revision list \
--name ca-product-catalog \
--resource-group $RESOURCE_GROUP \
--query "[0].{name:name, healthy:properties.healthState, running:properties.runningState}"
# Check container logs
az containerapp logs show \
--name ca-product-catalog \
--resource-group $RESOURCE_GROUP \
--type console
Mostrar solução
Causa raiz: O Dockerfile expõe a porta 8080 mas o ingress do Container App está configurado para a porta 80.
Correção:
az containerapp update \
--name ca-product-catalog \
--resource-group $RESOURCE_GROUP \
--set-env-vars "ASPNETCORE_URLS=http://+:8080"
az containerapp ingress update \
--name ca-product-catalog \
--resource-group $RESOURCE_GROUP \
--target-port 8080
Exercício 3: Escaneamento do Trivy bloqueando deployment com falso positivo
Sintoma: O pipeline falha porque o Trivy reporta uma vulnerabilidade CRITICAL em um pacote da imagem base que não tem correção disponível ainda.
Mostrar solução
Causa raiz: A vulnerabilidade está em um pacote de sistema na imagem base sem correção upstream.
Correção: Crie um arquivo .trivyignore na raiz do projeto:
# No fix available - tracked in issue #1234
CVE-2024-XXXXX
Atualize o workflow para referenciar o arquivo de ignore:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
trivyignores: '.trivyignore'
Verificação de conhecimento
1. A Contoso quer construir imagens de container em seu pipeline de CI e fazer push para o Azure Container Registry. O pipeline usa um service principal para autenticação. Qual é a role RBAC MÍNIMA necessária no ACR para o service principal fazer push de imagens?
2. Um container implantado no Azure Container Apps continua reiniciando. A aplicação escuta na porta 8080, mas o ingress do Container App está configurado com 'targetPort: 80'. Qual é a correção correta?
3. A Contoso constrói suas imagens de container com tags 'semver' (ex: '1.2.3') e tags 'sha' (ex: 'abc1234'). Nos manifestos Kubernetes de produção, qual tipo de tag deve ser referenciado para deployments reproduzíveis?
4. A Contoso precisa que suas imagens de container executem tanto em arquiteturas AMD64 (VMs na nuvem) quanto ARM64 (dispositivos edge). O que deve ser configurado no pipeline de CI para produzir imagens para ambas as plataformas?
Limpeza
# Delete Container App
az containerapp delete \
--name ca-product-catalog \
--resource-group $RESOURCE_GROUP \
--yes
# Delete Container Apps environment
az containerapp env delete \
--name cae-contoso-prod \
--resource-group $RESOURCE_GROUP \
--yes
# Delete ACR
az acr delete --name $ACR_NAME --yes
# Delete resource group
az group delete --name rg-contoso-containers --yes --no-wait