Project 8: Azure Container Solutions β
Overview β
Build a complete container solution using Azure Container Instances (ACI), Azure Container Registry (ACR), and understand when to use AKS. This project covers the container deployment scenarios commonly tested on AZ-104.
Difficulty: Intermediate
Duration: 3-4 hours
Cost: ~$20-30/month (ACR Basic, ACI consumption)
Exam Weight: Part of Compute domain (20-25%)
Architecture Diagram β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DEVELOPMENT WORKFLOW β
β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Developer βββββΆβ Build βββββΆβ Push to βββββΆβ Deploy to β β
β β Dockerfile β β Image β β ACR β β ACI β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AZURE CONTAINER REGISTRY (ACR) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β acrcontainerlab.azurecr.io β β
β β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββββββββ β β
β β β Repository: β β Repository: β β Repository: β β β
β β β webapp β β api β β worker β β β
β β β β β β β β β β
β β β Tags: β β Tags: β β Tags: β β β
β β β - v1.0 β β - v1.0 β β - v1.0 β β β
β β β - v1.1 β β - v2.0 β β - latest β β β
β β β - latest β β - latest β β β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββββββββ β β
β β β β
β β Features: β β
β β β
Geo-replication (Premium) β
Content Trust β β
β β β
Private Link β
Vulnerability Scanning β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β Pull Images
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AZURE CONTAINER INSTANCES (ACI) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β CONTAINER GROUP: cg-webapp β β
β β (Multi-container deployment) β β
β β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β SHARED RESOURCES β β β
β β β - IP Address: 20.x.x.x (Public) β β β
β β β - DNS Label: webapp-demo-12345.eastus.azurecontainer.io β β β
β β β - CPU: 2 cores (shared) β β β
β β β - Memory: 4 GB (shared) β β β
β β β - Volume: Azure Files (shared storage) β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β
β β ββββββββββββββββββββββββ ββββββββββββββββββββββββ β β
β β β Container: webapp β β Container: sidecar β β β
β β β β β β β β
β β β Image: webapp:v1.0 β β Image: nginx:latest β β β
β β β Port: 80 β β Port: 8080 β β β
β β β CPU: 1.5 cores β β CPU: 0.5 cores β β β
β β β Memory: 3 GB β β Memory: 1 GB β β β
β β β β β β β β
β β β Environment: β β Purpose: β β β
β β β - DB_HOST=... β β - Reverse proxy β β β
β β β - API_KEY=... β β - SSL termination β β β
β β ββββββββββββββββββββββββ ββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β CONTAINER GROUP: cg-worker β β
β β (Background processing) β β
β β β β
β β ββββββββββββββββββββββββ β β
β β β Container: worker β Restart Policy: Always β β
β β β β OS Type: Linux β β
β β β Image: worker:v1.0 β β β
β β β CPU: 1 core β Volume Mount: β β
β β β Memory: 2 GB β - Azure Files: /data β β
β β β No public IP β β β
β β ββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β NETWORK INTEGRATION β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β VNet: vnet-containers (10.0.0.0/16) β β
β β β β
β β ββββββββββββββββββββββββ ββββββββββββββββββββββββ β β
β β β Subnet: snet-aci β β Subnet: snet-data β β β
β β β (10.0.1.0/24) β β (10.0.2.0/24) β β β
β β β β β β β β
β β β Delegated to: β β Contains: β β β
β β β Microsoft.Container β β - Azure SQL β β β
β β β InstanceService β β - Storage Account β β β
β β β β β - Redis Cache β β β
β β β ACI containers can β β β β β
β β β access data tier β β β β β
β β β privately β β β β β
β β ββββββββββββββββββββββββ ββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββACI vs AKS Decision Matrix β
| Scenario | Use ACI | Use AKS |
|---|---|---|
| Simple single-container apps | β | β |
| Short-lived batch jobs | β | β |
| Dev/test environments | β | β |
| Event-driven workloads | β | β |
| Complex microservices | β | β |
| Need auto-scaling | β | β |
| Service mesh required | β | β |
| Persistent workloads | β | β |
What You'll Learn β
- Create and manage Azure Container Registry
- Build and push Docker images to ACR
- Deploy containers using ACI
- Configure multi-container groups
- Set up VNet integration for ACI
- Mount Azure Files volumes
- Configure restart policies and probes
Phase 1: Create Azure Container Registry β
Step 1.1: Create Resource Group β
bash
# Set variables
LOCATION="eastus"
RG_NAME="rg-containers-lab"
ACR_NAME="acrcontainerlab$(date +%s | tail -c 6)"
# Create resource group
az group create \
--name $RG_NAME \
--location $LOCATION \
--tags Project=Containers Environment=Lab
echo "Resource group created"Step 1.2: Create Container Registry β
bash
# Create ACR with Basic SKU
az acr create \
--resource-group $RG_NAME \
--name $ACR_NAME \
--sku Basic \
--admin-enabled true
# Get ACR login server
ACR_LOGIN_SERVER=$(az acr show \
--name $ACR_NAME \
--query loginServer -o tsv)
echo "ACR created: $ACR_LOGIN_SERVER"Step 1.3: Get ACR Credentials β
bash
# Get admin credentials
ACR_USERNAME=$(az acr credential show \
--name $ACR_NAME \
--query username -o tsv)
ACR_PASSWORD=$(az acr credential show \
--name $ACR_NAME \
--query "passwords[0].value" -o tsv)
echo "ACR Username: $ACR_USERNAME"
echo "ACR Password: $ACR_PASSWORD"Phase 2: Build and Push Container Images β
Step 2.1: Create Sample Web Application β
bash
# Create project directory
mkdir -p container-app
cd container-app
# Create a simple Node.js application
cat > app.js << 'EOF'
const http = require('http');
const os = require('os');
const port = process.env.PORT || 80;
const message = process.env.MESSAGE || 'Hello from Azure Container Instance!';
const server = http.createServer((req, res) => {
const response = {
message: message,
hostname: os.hostname(),
platform: os.platform(),
uptime: process.uptime(),
timestamp: new Date().toISOString()
};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(response, null, 2));
});
server.listen(port, () => {
console.log(`Server running on port ${port}`);
});
EOF
# Create Dockerfile
cat > Dockerfile << 'EOF'
FROM node:18-alpine
WORKDIR /app
COPY app.js .
EXPOSE 80
ENV PORT=80
ENV MESSAGE="Hello from Azure Container Instance!"
CMD ["node", "app.js"]
EOF
echo "Application files created"Step 2.2: Build Image Using ACR Tasks β
bash
# Build image directly in ACR (no local Docker needed!)
az acr build \
--registry $ACR_NAME \
--image webapp:v1.0 \
--file Dockerfile \
.
# Tag as latest
az acr build \
--registry $ACR_NAME \
--image webapp:latest \
--file Dockerfile \
.
echo "Image built and pushed to ACR"Step 2.3: Verify Image in Registry β
bash
# List repositories
az acr repository list \
--name $ACR_NAME \
--output table
# List tags for webapp repository
az acr repository show-tags \
--name $ACR_NAME \
--repository webapp \
--output table
# Show image details
az acr repository show-manifests \
--name $ACR_NAME \
--repository webapp \
--output tablePhase 3: Deploy Azure Container Instances β
Step 3.1: Deploy Simple Container β
bash
# Deploy container from ACR
az container create \
--resource-group $RG_NAME \
--name aci-webapp-simple \
--image $ACR_LOGIN_SERVER/webapp:v1.0 \
--registry-login-server $ACR_LOGIN_SERVER \
--registry-username $ACR_USERNAME \
--registry-password $ACR_PASSWORD \
--cpu 1 \
--memory 1.5 \
--ports 80 \
--dns-name-label "webapp-$(date +%s | tail -c 8)" \
--environment-variables MESSAGE="Hello from simple ACI deployment!" \
--restart-policy Always \
--location $LOCATION
# Get container details
az container show \
--resource-group $RG_NAME \
--name aci-webapp-simple \
--query "{FQDN:ipAddress.fqdn, IP:ipAddress.ip, State:instanceView.state}" \
--output tableStep 3.2: Test the Container β
bash
# Get FQDN
FQDN=$(az container show \
--resource-group $RG_NAME \
--name aci-webapp-simple \
--query ipAddress.fqdn -o tsv)
# Test the application
curl http://$FQDN
# View container logs
az container logs \
--resource-group $RG_NAME \
--name aci-webapp-simplePhase 4: Multi-Container Groups β
Step 4.1: Create Multi-Container YAML Definition β
bash
cat > container-group.yaml << EOF
apiVersion: '2021-09-01'
location: $LOCATION
name: cg-multicontainer
properties:
containers:
- name: webapp
properties:
image: $ACR_LOGIN_SERVER/webapp:v1.0
ports:
- port: 80
protocol: TCP
resources:
requests:
cpu: 1.0
memoryInGB: 1.5
environmentVariables:
- name: MESSAGE
value: "Hello from multi-container group!"
- name: PORT
value: "80"
- name: sidecar
properties:
image: nginx:alpine
ports:
- port: 8080
protocol: TCP
resources:
requests:
cpu: 0.5
memoryInGB: 0.5
command:
- "/bin/sh"
- "-c"
- |
echo 'server { listen 8080; location / { proxy_pass http://localhost:80; } }' > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
osType: Linux
ipAddress:
type: Public
ports:
- port: 80
protocol: TCP
- port: 8080
protocol: TCP
dnsNameLabel: multicontainer-$(date +%s | tail -c 8)
restartPolicy: Always
imageRegistryCredentials:
- server: $ACR_LOGIN_SERVER
username: $ACR_USERNAME
password: $ACR_PASSWORD
tags:
environment: lab
project: containers
type: Microsoft.ContainerInstance/containerGroups
EOF
echo "Container group YAML created"Step 4.2: Deploy Multi-Container Group β
bash
# Deploy using YAML
az container create \
--resource-group $RG_NAME \
--file container-group.yaml
# Verify deployment
az container show \
--resource-group $RG_NAME \
--name cg-multicontainer \
--query "{State:instanceView.state, IP:ipAddress.ip, FQDN:ipAddress.fqdn}" \
--output table
# View logs from specific container
az container logs \
--resource-group $RG_NAME \
--name cg-multicontainer \
--container-name webapp
az container logs \
--resource-group $RG_NAME \
--name cg-multicontainer \
--container-name sidecarPhase 5: Mount Azure Files Volume β
Step 5.1: Create Storage Account and File Share β
bash
# Create storage account
STORAGE_NAME="staci$(date +%s | tail -c 8)"
az storage account create \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--sku Standard_LRS \
--location $LOCATION
# Get storage key
STORAGE_KEY=$(az storage account keys list \
--resource-group $RG_NAME \
--account-name $STORAGE_NAME \
--query "[0].value" -o tsv)
# Create file share
az storage share create \
--name "container-data" \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
# Upload a test file
echo "Configuration data from Azure Files" > config.txt
az storage file upload \
--share-name "container-data" \
--source "config.txt" \
--account-name $STORAGE_NAME \
--account-key $STORAGE_KEY
echo "Storage account and file share created"Step 5.2: Deploy Container with Volume Mount β
bash
# Deploy container with Azure Files mount
az container create \
--resource-group $RG_NAME \
--name aci-with-volume \
--image $ACR_LOGIN_SERVER/webapp:v1.0 \
--registry-login-server $ACR_LOGIN_SERVER \
--registry-username $ACR_USERNAME \
--registry-password $ACR_PASSWORD \
--cpu 1 \
--memory 1.5 \
--ports 80 \
--dns-name-label "aci-volume-$(date +%s | tail -c 8)" \
--azure-file-volume-account-name $STORAGE_NAME \
--azure-file-volume-account-key $STORAGE_KEY \
--azure-file-volume-share-name "container-data" \
--azure-file-volume-mount-path "/mnt/data" \
--location $LOCATION
echo "Container with volume mount deployed"
# Verify volume mount by executing command in container
az container exec \
--resource-group $RG_NAME \
--name aci-with-volume \
--exec-command "ls -la /mnt/data"Phase 6: VNet Integration β
Step 6.1: Create Virtual Network β
bash
# Create VNet
az network vnet create \
--resource-group $RG_NAME \
--name vnet-containers \
--address-prefix 10.0.0.0/16 \
--subnet-name snet-aci \
--subnet-prefix 10.0.1.0/24 \
--location $LOCATION
# Delegate subnet to ACI
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name vnet-containers \
--name snet-aci \
--delegations Microsoft.ContainerInstance/containerGroups
echo "VNet created with ACI delegation"Step 6.2: Deploy Container in VNet β
bash
# Get subnet ID
SUBNET_ID=$(az network vnet subnet show \
--resource-group $RG_NAME \
--vnet-name vnet-containers \
--name snet-aci \
--query id -o tsv)
# Deploy container in VNet (no public IP)
az container create \
--resource-group $RG_NAME \
--name aci-vnet-private \
--image $ACR_LOGIN_SERVER/webapp:v1.0 \
--registry-login-server $ACR_LOGIN_SERVER \
--registry-username $ACR_USERNAME \
--registry-password $ACR_PASSWORD \
--cpu 1 \
--memory 1.5 \
--ports 80 \
--subnet $SUBNET_ID \
--location $LOCATION
# Get private IP
PRIVATE_IP=$(az container show \
--resource-group $RG_NAME \
--name aci-vnet-private \
--query ipAddress.ip -o tsv)
echo "Container deployed in VNet with private IP: $PRIVATE_IP"Phase 7: Container Management Operations β
Step 7.1: Start, Stop, and Restart Containers β
bash
# Stop container
az container stop \
--resource-group $RG_NAME \
--name aci-webapp-simple
# Check state
az container show \
--resource-group $RG_NAME \
--name aci-webapp-simple \
--query instanceView.state -o tsv
# Start container
az container start \
--resource-group $RG_NAME \
--name aci-webapp-simple
# Restart container
az container restart \
--resource-group $RG_NAME \
--name aci-webapp-simpleStep 7.2: View Container Metrics β
bash
# Get container events
az container show \
--resource-group $RG_NAME \
--name aci-webapp-simple \
--query instanceView.events \
--output table
# Stream logs (real-time)
az container attach \
--resource-group $RG_NAME \
--name aci-webapp-simple
# Execute command in running container
az container exec \
--resource-group $RG_NAME \
--name aci-webapp-simple \
--exec-command "/bin/sh"Phase 8: ACR Advanced Features β
Step 8.1: Configure ACR Tasks for Auto-Build β
bash
# Create a quick task that builds on git push (requires GitHub token)
# This is a simulation - in real scenario, connect to your repo
# Manual trigger build with custom tag
az acr build \
--registry $ACR_NAME \
--image webapp:v2.0 \
--build-arg VERSION=2.0 \
.
# List all tasks
az acr task list \
--registry $ACR_NAME \
--output tableStep 8.2: Enable Content Trust (Premium SKU) β
bash
# Note: Requires Premium SKU
# az acr update --name $ACR_NAME --sku Premium
# Enable content trust
# az acr config content-trust update \
# --registry $ACR_NAME \
# --status enabledStep 8.3: Purge Old Images β
bash
# Delete images older than 30 days (dry run)
az acr run \
--registry $ACR_NAME \
--cmd "acr purge --filter 'webapp:.*' --ago 30d --dry-run" \
/dev/null
# Actually purge (remove --dry-run)
# az acr run \
# --registry $ACR_NAME \
# --cmd "acr purge --filter 'webapp:.*' --ago 30d" \
# /dev/nullSummary Table β
| Component | Purpose | Configuration |
|---|---|---|
| ACR | Image registry | Basic SKU, admin enabled |
| ACI Simple | Single container | 1 CPU, 1.5 GB RAM |
| ACI Multi-container | Webapp + sidecar | 1.5 CPU, 2 GB RAM total |
| ACI with Volume | Persistent storage | Azure Files mount |
| ACI in VNet | Private networking | Delegated subnet |
Exam Tips β
- ACR SKUs: Basic (10GB), Standard (100GB), Premium (500GB + geo-replication)
- ACI Restart Policies: Always, OnFailure, Never
- ACI Resource Limits: Max 4 CPUs, 16 GB memory per container group
- VNet Integration: Requires subnet delegation
- Multi-container: Containers share localhost networking
Cleanup β
bash
# Delete all containers
az container delete --resource-group $RG_NAME --name aci-webapp-simple --yes
az container delete --resource-group $RG_NAME --name cg-multicontainer --yes
az container delete --resource-group $RG_NAME --name aci-with-volume --yes
az container delete --resource-group $RG_NAME --name aci-vnet-private --yes
# Delete resource group
az group delete --name $RG_NAME --yes --no-wait
# Cleanup local files
cd ..
rm -rf container-app
echo "Cleanup initiated"Key Takeaways β
- ACR Tasks: Build images without local Docker
- ACI: Serverless containers, pay per second
- Multi-container: Sidecar pattern for auxiliary services
- Volume Mounts: Azure Files for persistent data
- VNet Integration: Private container deployments
- ACI vs AKS: ACI for simple/short-lived, AKS for complex orchestration