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#
- Container and Serverless Compute — the Remaining Spectrum
- Azure Container Registry
- AKS — Architecture and Control Plane
- AKS Node Pools — System vs. User
- AKS Automatic — Managed Simplification
- AKS Cluster and Node Image Upgrades
- AKS Networking — CNI Modes
- AKS Identity — Workload Identity for Pods
- AKS Autoscaling — Cluster Autoscaler and KEDA
- AKS Storage — Persistent Volumes and CSI Drivers
- Azure Container Instances
- Azure Container Apps — Architecture
- Container Apps Environments and Dapr
- Container Apps Scaling Rules
- Choosing Between AKS, Container Apps, and ACI
- App Service — Web Apps
- App Service Plans and Deployment Slots
- Azure Functions — Triggers and Bindings
- Azure Functions Hosting Plans
- Durable Functions
- Choosing Between App Service, Functions, and Containers
- A Full Worked Compute Bootstrap for Meridian Freight
- Part 10 CLI Cheat Sheet
- Common Mistakes and Interview Traps
- Worked Practice Problems
- Summary and What's Next
Container and Serverless Compute — the Remaining Spectrum#
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 azureWorth 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 type | Purpose |
|---|---|
| System | Runs critical system pods (CoreDNS, metrics-server) — should be isolated from user workload scheduling pressure |
| User | Runs 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 automaticAKS 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 0Cluster 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#
| Mode | IP allocation | Best fit |
|---|---|---|
| Kubenet (legacy) | Pods get IPs from a separate, internal CIDR — NAT'd for VNet communication | Smaller clusters, simpler IP planning |
| Azure CNI (traditional) | Every pod gets a routable IP directly from the VNet's own address space | Direct VNet-IP addressability for every pod, at the cost of consuming real VNet IP space fast |
| Azure CNI Overlay | Pods get IPs from a separate overlay space, NOT consuming VNet IPs, while still routing efficiently | Current 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 10Cluster 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: 100Giaz aks addon list --name aks-meridian --resource-group rg-shipment-api-prod| CSI driver | Backs onto | Best fit |
|---|---|---|
| Azure Disk CSI | Managed Disks (Part 3) | Single-pod, ReadWriteOnce persistent storage |
| Azure Files CSI | Azure Files (Part 8) | Multi-pod, ReadWriteMany shared storage |
| Blob CSI | Blob Storage (Part 8), via NFS or BlobFuse | Large-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 NeverACI 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 10Container 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 8080Dapr (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 50Scale-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#
| Need | Recommendation |
|---|---|
| Full Kubernetes API, custom operators, GPU scheduling, complex stateful workloads | AKS |
| Microservices/event-driven containers, no Kubernetes operational ownership wanted, scale-to-zero | Container Apps |
| A single, short-lived, one-off container execution | Azure 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 productionDeployment 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 pythonA 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#
| Plan | Billing | Cold start | Best fit |
|---|---|---|---|
| Consumption | Per-execution | Yes, on scale-from-zero | Cost-sensitive, tolerant of cold starts |
| Flex Consumption (current recommendation) | Per-execution, with more control | Reduced, with "always ready" instance options | The current recommended serverless default |
| Premium | Pre-warmed instances, VNet integration | No | Latency-sensitive, needs private networking |
| Dedicated (App Service Plan) | Fixed, plan-based | No | Predictable, 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#
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 automaticPart 10 CLI Cheat Sheet#
| Area | Command | Purpose |
|---|---|---|
| Registry | az acr create --sku Premium | Create a geo-replicable container registry |
| AKS | az aks create | Create an AKS cluster |
| AKS node pools | az aks nodepool add --mode User | Add a user node pool |
| AKS identity | az aks update --enable-workload-identity | Enable workload identity federation |
| AKS autoscale | az aks nodepool update --enable-cluster-autoscaler | Enable node-level autoscaling |
| ACI | az container create | Run a one-off serverless container |
| Container Apps | az containerapp create | Create a Container App |
| Container Apps | az containerapp create --dapr-enabled true | Enable Dapr for a Container App |
| App Service | az webapp create | Create a web app |
| Slots | az webapp deployment slot swap | Swap staging into production |
| Functions | az functionapp create --flexconsumption-location | Create a Flex Consumption function app |
| Storage | az aks addon list | Check enabled CSI drivers on a cluster |
Common Mistakes and Interview Traps#
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Running user workloads on the AKS system node pool | Can starve critical system pods, degrading cluster-wide DNS/metrics | Isolate system pods on a dedicated system pool |
| Using traditional Azure CNI without checking subnet size at real pod-count scale | Every pod consuming a real VNet IP can exhaust the subnet fast | Use Azure CNI Overlay as the current default to avoid VNet IP exhaustion |
| Storing a secret in a Kubernetes Secret for pod-to-Azure authentication | The retired pod identity pattern and stored secrets both carry unnecessary credential risk | Use workload identity federation — no stored secret |
| Choosing AKS by default for a simple microservices workload | Takes on full Kubernetes operational ownership unnecessarily | Default 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 retirement | Requires a future forced migration | Default to Flex Consumption, the current recommended serverless plan |
| Building a long-running, naively-looping function for a multi-step workflow | No durable checkpointing — a process restart loses all progress, and billing runs continuously | Use Durable Functions for stateful, multi-step orchestration |
| Using Azure Disk CSI for a volume multiple pod replicas need to share simultaneously | Managed Disks only support single-pod (ReadWriteOnce) attachment | Use 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.