Part 10 of 1617 min read · 2 diagramsAI-assisted

Containers & Serverless

Picks up Part 3's compute spectrum at the container/serverless end — the docs-processor workload flagged back in Part 3 as a natural serverless fit lands here.

Table of Contents#

  1. Container and Serverless Compute — the Remaining Spectrum
  2. Azure Container Registry
  3. AKS — Architecture and Control Plane
  4. AKS Node Pools — System vs. User
  5. AKS Automatic — Managed Simplification
  6. AKS Cluster and Node Image Upgrades
  7. AKS Networking — CNI Modes
  8. AKS Identity — Workload Identity for Pods
  9. AKS Autoscaling — Cluster Autoscaler and KEDA
  10. AKS Storage — Persistent Volumes and CSI Drivers
  11. Azure Container Instances
  12. Azure Container Apps — Architecture
  13. Container Apps Environments and Dapr
  14. Container Apps Scaling Rules
  15. Choosing Between AKS, Container Apps, and ACI
  16. App Service — Web Apps
  17. App Service Plans and Deployment Slots
  18. Azure Functions — Triggers and Bindings
  19. Azure Functions Hosting Plans
  20. Durable Functions
  21. Choosing Between App Service, Functions, and Containers
  22. A Full Worked Compute Bootstrap for Meridian Freight
  23. Part 10 CLI Cheat Sheet
  24. Common Mistakes and Interview Traps
  25. Worked Practice Problems
  26. Summary and What's Next

Container and Serverless Compute — the Remaining Spectrum#

Diagram

docs-processor's document-classification workload — event-driven, bursty, no need for persistent infrastructure between runs — lands on Azure Functions in this chapter; shipment-api's microservices, needing more control but not full Kubernetes complexity, land on Container Apps.


Azure Container Registry#

az acr create --name acrmeridianfreight --resource-group rg-platform --sku Premium

az acr build --registry acrmeridianfreight --image shipment-api:1.0 .

Premium tier unlocks geo-replication (pushing one image, available with low latency in every region a workload deploys to — the same multi-region replication argument Part 3 made for Azure Compute Gallery images) and Private Link support (Part 7) — worth the added cost the moment a registry serves more than one region or needs to avoid a public endpoint entirely.


AKS — Architecture and Control Plane#

az aks create --name aks-meridian --resource-group rg-shipment-api-prod \
  --node-count 3 --vnet-subnet-id "<subnet-resource-id>" \
  --enable-managed-identity --network-plugin azure

Worth stating precisely, since it's a common early misunderstanding: the AKS control plane (API server, etcd, scheduler) is FULLY MANAGED by Azure at no direct compute cost for the Free tier — only the worker nodes (VMs) are billed — the Standard/Premium tiers add an SLA and other guarantees to the control plane itself, worth adopting for any genuinely production cluster.


AKS Node Pools — System vs. User#

az aks nodepool add --cluster-name aks-meridian --resource-group rg-shipment-api-prod \
  --name userpool --mode User --node-count 3 --node-vm-size Standard_D4s_v5
Pool typePurpose
SystemRuns critical system pods (CoreDNS, metrics-server) — should be isolated from user workload scheduling pressure
UserRuns actual application workloads — can be added/removed/scaled independently of the system pool

Why isolating system pods onto a dedicated system pool matters concretely: a user workload that consumes excessive node resources on a SHARED pool can starve critical system pods, degrading cluster-wide DNS resolution or metrics collection — a real, cluster-wide blast radius from what looks like one workload's local resource problem.


AKS Automatic — Managed Simplification#

az aks create --name aks-meridian-auto --resource-group rg-shipment-api-prod --sku automatic

AKS Automatic, production-ready as of 2026, provisions and manages system node pools automatically, with cluster autoupgrade pre-enabled on the Stable channel by default — a genuinely different operational starting point from standard AKS, worth choosing for a team wanting Kubernetes' API surface without taking on full node-pool and upgrade-cadence management themselves.


AKS Cluster and Node Image Upgrades#

az aks upgrade --name aks-meridian --resource-group rg-shipment-api-prod --kubernetes-version 1.31

az aks nodepool update --cluster-name aks-meridian --resource-group rg-shipment-api-prod \
  --name userpool --max-surge 33% --max-unavailable 0

Cluster autoupgrade always upgrades the control plane FIRST, then agent pools one at a time — worth knowing this exact sequence rather than assuming a simultaneous upgrade. Node pool version rollback, now generally available, lets a pool be restored to its previous Kubernetes version and node image after a bad upgrade — worth knowing exists as a genuine recovery path rather than assuming a bad upgrade requires a full cluster rebuild.


AKS Networking — CNI Modes#

ModeIP allocationBest fit
Kubenet (legacy)Pods get IPs from a separate, internal CIDR — NAT'd for VNet communicationSmaller clusters, simpler IP planning
Azure CNI (traditional)Every pod gets a routable IP directly from the VNet's own address spaceDirect VNet-IP addressability for every pod, at the cost of consuming real VNet IP space fast
Azure CNI OverlayPods get IPs from a separate overlay space, NOT consuming VNet IPs, while still routing efficientlyCurrent recommended default — avoids VNet IP exhaustion at scale

Why Azure CNI Overlay is worth treating as the current default recommendation, worth stating the underlying reasoning: traditional Azure CNI's "every pod gets a real VNet IP" model can exhaust a subnet's address space surprisingly fast at real pod-count scale (Part 4's subnet-sizing math applies directly here) — Overlay keeps pod IPs in a separate space, avoiding that exhaustion while still routing pod traffic efficiently within the cluster.


AKS Identity — Workload Identity for Pods#

az aks update --name aks-meridian --resource-group rg-shipment-api-prod --enable-oidc-issuer --enable-workload-identity

kubectl annotate serviceaccount shipment-api-sa \
  azure.workload.identity/client-id="<managed-identity-client-id>"

Workload identity federation (Part 2's mechanism, applied specifically to AKS pods) lets a pod authenticate to Azure resources using a federated credential tied to its Kubernetes service account — no secret stored in the cluster at all, directly extending Part 2's "no stored secret" principle into the Kubernetes workload itself, replacing the older, secret-based pod identity pattern (now retired).


AKS Autoscaling — Cluster Autoscaler and KEDA#

az aks nodepool update --cluster-name aks-meridian --resource-group rg-shipment-api-prod \
  --name userpool --enable-cluster-autoscaler --min-count 3 --max-count 10

Cluster Autoscaler adds/removes NODES based on pod scheduling pressure; KEDA (Kubernetes Event-Driven Autoscaling, built into AKS) scales the number of POD REPLICAS based on external event sources — a queue depth, a Cosmos DB change feed (Part 9) — rather than just CPU/memory, genuinely necessary for docs-processor's queue-depth-driven scaling need if it ever moves to AKS from its current Functions-based design.


AKS Storage — Persistent Volumes and CSI Drivers#

Containers are ephemeral by design, but a genuine subset of workloads need persistent state — AKS provisions storage through CSI (Container Storage Interface) drivers, connecting Kubernetes' own PersistentVolumeClaim model directly to Azure's actual storage services from Parts 3 and 8.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-shipment-cache
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: managed-csi-premium
  resources:
    requests:
      storage: 100Gi
az aks addon list --name aks-meridian --resource-group rg-shipment-api-prod
CSI driverBacks ontoBest fit
Azure Disk CSIManaged Disks (Part 3)Single-pod, ReadWriteOnce persistent storage
Azure Files CSIAzure Files (Part 8)Multi-pod, ReadWriteMany shared storage
Blob CSIBlob Storage (Part 8), via NFS or BlobFuseLarge-scale, throughput-oriented shared access

Why choosing between Disk and Files CSI drivers is worth stating as directly determined by the access mode a workload actually needs, not an arbitrary preference: a Managed Disk can only be attached to ONE pod at a time (ReadWriteOnce) — a workload needing multiple pod replicas to share the SAME persistent volume simultaneously needs Azure Files (ReadWriteMany) instead, the exact same distinction Part 3 and Part 8 already established for VM-attached disks versus shared file access, now applied inside Kubernetes.


Azure Container Instances#

az container create --name aci-one-off-job --resource-group rg-docs-processor \
  --image acrmeridianfreight.azurecr.io/batch-job:1.0 --cpu 2 --memory 4 --restart-policy Never

ACI is worth reaching for specifically for a genuinely one-off or short-lived container execution — a CI/CD build agent, a single batch job — worth stating the underlying reasoning: it has no persistent environment or cluster to manage, billed per-second, but also no built-in orchestration (scaling, service discovery, rolling updates) at all, making it a poor fit for anything needing to run as a continuously available service.


Azure Container Apps — Architecture#

az containerapp env create --name env-meridian --resource-group rg-shipment-api-prod \
  --logs-workspace-id "<log-analytics-workspace-id>"

az containerapp create --name shipment-api --resource-group rg-shipment-api-prod \
  --environment env-meridian --image acrmeridianfreight.azurecr.io/shipment-api:1.0 \
  --target-port 8080 --ingress external --min-replicas 0 --max-replicas 10

Container Apps is built on Kubernetes underneath, but deliberately hides that entirely — no kubectl, no cluster to manage, no node pools to size — a genuinely different operating model from AKS despite sharing container-orchestration DNA, worth choosing specifically when a team wants microservices/event-driven container workloads without taking on Kubernetes operational ownership at all.


Container Apps Environments and Dapr#

An environment is the security/networking boundary containing a group of Container Apps — apps in the same environment can communicate directly, and the environment can be deployed into a VNet (Part 4) for private connectivity.

az containerapp create --name shipment-api --resource-group rg-shipment-api-prod \
  --environment env-meridian --image acrmeridianfreight.azurecr.io/shipment-api:1.0 \
  --dapr-enabled true --dapr-app-id shipment-api --dapr-app-port 8080

Dapr (Distributed Application Runtime), built into Container Apps as a first-class option, provides standardized building blocks (service invocation, state management, pub/sub) that abstract away the specific backing implementation — shipment-api calling driver-portal through Dapr's service invocation API works identically whether the actual transport is HTTP or gRPC, without either service's code needing to know the other's specific networking details.


Container Apps Scaling Rules#

az containerapp update --name shipment-api --resource-group rg-shipment-api-prod \
  --scale-rule-name http-scale --scale-rule-type http \
  --scale-rule-http-concurrency 50

Scale-to-zero is worth calling out as a genuine capability distinguishing Container Apps from AKS's own pod-level autoscaling: an app with zero traffic can scale down to zero replicas entirely, paying nothing for idle compute, then scale back up on the next incoming request — a real cost advantage for a workload with genuinely intermittent traffic that AKS's minimum-node-count model doesn't match as cleanly.


Choosing Between AKS, Container Apps, and ACI#

NeedRecommendation
Full Kubernetes API, custom operators, GPU scheduling, complex stateful workloadsAKS
Microservices/event-driven containers, no Kubernetes operational ownership wanted, scale-to-zeroContainer Apps
A single, short-lived, one-off container executionAzure Container Instances

Meridian Freight's actual split: shipment-api and driver-portal's newer microservices run on Container Apps; a future ML-inference workload needing GPU scheduling and custom operators would justify AKS specifically for that requirement.


App Service — Web Apps#

az webapp create --name shipment-api-legacy --resource-group rg-shipment-api-prod \
  --plan asp-meridian --runtime "NODE:20-lts"

App Service is Azure's fully managed web app hosting — no containers required at all (though container deployment is also supported) — the natural home for a traditional web application that doesn't need microservices decomposition or Kubernetes-level control.


App Service Plans and Deployment Slots#

az appservice plan create --name asp-meridian --resource-group rg-shipment-api-prod \
  --sku P1v3 --number-of-workers 3 --zone-redundant

az webapp deployment slot create --name shipment-api-legacy --resource-group rg-shipment-api-prod \
  --slot staging

az webapp deployment slot swap --name shipment-api-legacy --resource-group rg-shipment-api-prod \
  --slot staging --target-slot production

Deployment slots let a new version deploy and warm up in an isolated staging slot, then swap into production with effectively zero downtime — the swap exchanges the slots' routing rather than redeploying code, directly implementing the blue-green deployment pattern the Automation/CI/CD series covers generically.


Azure Functions — Triggers and Bindings#

az functionapp create --name docs-processor-fn --resource-group rg-docs-processor \
  --storage-account stmeridianfreight --consumption-plan-location eastus --runtime python

A trigger starts a function's execution (an HTTP request, a new blob, a queue message); a binding declaratively connects the function to a data source/destination without hand-written SDK code. docs-processor's core function triggers on a new blob landing in Storage (Part 8), with an output binding writing extracted metadata directly to Cosmos DB (Part 9) — no explicit SDK calls for either the trigger or the output.


Azure Functions Hosting Plans#

PlanBillingCold startBest fit
ConsumptionPer-executionYes, on scale-from-zeroCost-sensitive, tolerant of cold starts
Flex Consumption (current recommendation)Per-execution, with more controlReduced, with "always ready" instance optionsThe current recommended serverless default
PremiumPre-warmed instances, VNet integrationNoLatency-sensitive, needs private networking
Dedicated (App Service Plan)Fixed, plan-basedNoPredictable, already-provisioned compute

A genuinely important current fact worth stating explicitly: Flex Consumption is Microsoft's current recommended serverless hosting plan, adding private networking and instance-size selection the original Consumption plan never offered — and the Linux Consumption plan itself is scheduled for retirement in September 2028, worth planning a migration path for rather than building new Consumption-plan function apps today without awareness of that timeline.


Durable Functions#

# An orchestrator function coordinating multiple steps, with
# automatic checkpointing — surviving a process restart mid-workflow
def orchestrator_function(context):
    result1 = yield context.call_activity("ExtractText", input_document)
    result2 = yield context.call_activity("ClassifyDocument", result1)
    yield context.call_activity("WriteToDatabase", result2)

Durable Functions extend the Functions model with stateful orchestration — genuinely useful for docs-processor's multi-step pipeline (extract, classify, store), since the orchestrator's state is durably checkpointed and the function ISN'T BILLED for time spent waiting on an await inside an orchestrator, a real, meaningful cost difference from a naively-built long-running function.


Choosing Between App Service, Functions, and Containers#

Diagram

A Full Worked Compute Bootstrap for Meridian Freight#

# 1. Container registry with geo-replication
az acr create --name acrmeridianfreight --resource-group rg-platform --sku Premium

# 2. Container Apps environment for shipment-api's microservices
az containerapp env create --name env-meridian --resource-group rg-shipment-api-prod
az containerapp create --name shipment-api --resource-group rg-shipment-api-prod \
  --environment env-meridian --image acrmeridianfreight.azurecr.io/shipment-api:1.0 \
  --min-replicas 1 --max-replicas 10

# 3. Flex Consumption Function App for docs-processor
az functionapp create --name docs-processor-fn --resource-group rg-docs-processor \
  --flexconsumption-location eastus --runtime python

# 4. AKS reserved for the future GPU-inference workload specifically
az aks create --name aks-meridian-ml --resource-group rg-ml-inference --sku automatic

Part 10 CLI Cheat Sheet#

AreaCommandPurpose
Registryaz acr create --sku PremiumCreate a geo-replicable container registry
AKSaz aks createCreate an AKS cluster
AKS node poolsaz aks nodepool add --mode UserAdd a user node pool
AKS identityaz aks update --enable-workload-identityEnable workload identity federation
AKS autoscaleaz aks nodepool update --enable-cluster-autoscalerEnable node-level autoscaling
ACIaz container createRun a one-off serverless container
Container Appsaz containerapp createCreate a Container App
Container Appsaz containerapp create --dapr-enabled trueEnable Dapr for a Container App
App Serviceaz webapp createCreate a web app
Slotsaz webapp deployment slot swapSwap staging into production
Functionsaz functionapp create --flexconsumption-locationCreate a Flex Consumption function app
Storageaz aks addon listCheck enabled CSI drivers on a cluster

Common Mistakes and Interview Traps#

MistakeWhy It's WrongFix
Running user workloads on the AKS system node poolCan starve critical system pods, degrading cluster-wide DNS/metricsIsolate system pods on a dedicated system pool
Using traditional Azure CNI without checking subnet size at real pod-count scaleEvery pod consuming a real VNet IP can exhaust the subnet fastUse Azure CNI Overlay as the current default to avoid VNet IP exhaustion
Storing a secret in a Kubernetes Secret for pod-to-Azure authenticationThe retired pod identity pattern and stored secrets both carry unnecessary credential riskUse workload identity federation — no stored secret
Choosing AKS by default for a simple microservices workloadTakes on full Kubernetes operational ownership unnecessarilyDefault to Container Apps unless a specific AKS-only requirement (GPU scheduling, custom operators) exists
Building new Azure Functions apps on the original Linux Consumption plan without awareness of its 2028 retirementRequires a future forced migrationDefault to Flex Consumption, the current recommended serverless plan
Building a long-running, naively-looping function for a multi-step workflowNo durable checkpointing — a process restart loses all progress, and billing runs continuouslyUse Durable Functions for stateful, multi-step orchestration
Using Azure Disk CSI for a volume multiple pod replicas need to share simultaneouslyManaged Disks only support single-pod (ReadWriteOnce) attachmentUse Azure Files CSI for ReadWriteMany shared access across multiple pods

Worked Practice Problems#

Problem 1: Meridian Freight's AKS cluster experiences a sudden cluster-wide DNS resolution problem after a new, resource-heavy batch workload is deployed. Investigation finds CoreDNS pods being evicted due to resource pressure on the same nodes running the new workload. What's the underlying design gap, and what's the fix?

Answer: The underlying gap is running the new user workload on the same node pool as critical system pods (CoreDNS) rather than isolating them — a resource-heavy workload sharing a pool with system pods can starve them under memory/CPU pressure, exactly what happened here. The fix is ensuring system pods run on a dedicated system node pool (--mode System), with user workloads confined to separate user node pools — this is precisely why AKS distinguishes the two pool types, and mixing them defeats that isolation.

Problem 2: A team building a new microservices-based application for Meridian Freight debates AKS versus Container Apps, ultimately choosing AKS "for maximum flexibility," despite having no current requirement for custom Kubernetes operators, GPU scheduling, or complex stateful workloads. Six months later, the team is spending significant time on cluster upgrades and node pool management instead of application features. What was the actual tradeoff, and what should the original decision have weighed?

Answer: The team correctly identified that AKS offers more flexibility, but didn't weigh the corresponding operational cost against an ACTUAL requirement for that flexibility — "maximum flexibility" without a specific need driving it is exactly the over-engineering pattern this series has flagged elsewhere (Azure Dedicated Hosts, ExpressRoute Direct). Container Apps would have provided the microservices/event-driven container capabilities the application genuinely needed without any cluster or node pool management burden at all. The original decision should have started from "what specific capability does this application need that Container Apps cannot provide" — absent a concrete answer, Container Apps was the correct default, with AKS reserved for when a genuine, specific requirement (not a hypothetical future one) actually demands it.

Problem 3: docs-processor's Durable Functions orchestrator coordinates a three-step pipeline where the middle step occasionally takes several minutes to complete (waiting on an external API). A team member expresses concern that the function app will be billed heavily for this waiting time. Evaluate this concern.

Answer: The concern is based on a misunderstanding of Durable Functions' billing model — you are NOT billed for time spent at an await inside an orchestrator function; billing applies to actual compute time spent executing activity functions, not idle orchestrator wait time. This is a genuine, deliberate cost optimization built into the Durable Functions model specifically for exactly this multi-step-with-waiting pattern, and it's one of the concrete reasons Durable Functions is a better fit than a naively-built long-running function (which WOULD be billed for its entire wall-clock execution time, including any waiting) for a workflow like this one.

Problem 4: A team deploys a stateful application on AKS needing three replicas, each writing to what they intend to be one shared, persistent volume, using the Azure Disk CSI driver. Deployment fails when the second and third pod replicas attempt to mount the same volume. What's the cause, and what's the fix?

Answer: Managed Disks (which Azure Disk CSI provisions) only support ReadWriteOnce access — attachment to a single pod (node) at a time — so the second and third replicas attempting to mount the same disk-backed volume fail outright, since the disk is already attached to the first pod. The fix is switching to Azure Files CSI, which provisions an Azure Files share supporting ReadWriteMany access, allowing all three replicas to mount and write to the same share concurrently. The underlying lesson: the choice between Disk and Files CSI drivers is dictated directly by whether a workload needs single-pod or multi-pod concurrent access, not an arbitrary performance preference.


Summary and What's Next#

  • AKS, Container Apps, and ACI solve genuinely different operational-ownership tradeoffs — full Kubernetes control, managed container orchestration without cluster ownership, and one-off serverless execution respectively.
  • AKS Automatic and cluster autoupgrade meaningfully reduce operational burden for teams choosing AKS without wanting to own node-pool and upgrade-cadence management themselves.
  • Azure CNI Overlay is the current default networking mode — avoiding the VNet IP exhaustion traditional Azure CNI risks at real pod-count scale.
  • Workload identity federation replaces the retired pod identity pattern — no stored secret for pod-to-Azure authentication, extending Part 2's principle directly into Kubernetes workloads.
  • Container Apps' scale-to-zero and Dapr integration provide genuine cost and abstraction benefits AKS doesn't match as cleanly for microservices/event-driven workloads.
  • Flex Consumption is Microsoft's current recommended Functions hosting plan — the original Linux Consumption plan is scheduled for retirement in 2028.
  • Durable Functions aren't billed for orchestrator wait time — a genuine, deliberate cost optimization for multi-step workflows with real waiting periods.
  • AKS storage access mode dictates the CSI driver choice — Azure Disk for single-pod persistent storage, Azure Files for multi-pod shared access.

Continue to Part 11 (11-application-architecture-and-messaging.md) for the messaging, caching, and API management layer connecting the compute services this chapter covered.