Most teams deploy their app to two Azure regions, wire up a load balancer between them, and check "high availability" off the list.
That's not resilience. That's a backup you haven't tested.
True resilience on Azure isn't about having a second region — it's about knowing which layer owns which failure, so no single blast radius can take down the whole system. I call this the Three-Ring Model, and it's the mental model I use every time I design a distributed system on Azure.
This post walks through the full stack — Azure Front Door, API Management, and AKS — layer by layer, failure scenario by failure scenario. By the end, you'll have a blueprint you can take into your next architecture review.
The Architecture in One View
Before we go deep, here's the full system. Three distinct zones. Each owns a completely different concern.
Three layers. Three ownership boundaries. None of them care what the others are doing internally — and that separation is the whole point.
Ring 1 — The Edge: Azure Front Door
The question this layer answers: How does a user in Tokyo reach my app running in East US — without waiting for a TCP handshake to cross the Pacific?
Most people think of Azure Front Door as "a load balancer that sits in front of two regions." That's wrong, and the misunderstanding leads to misconfigured architectures.
Azure Front Door is a global anycast network. Microsoft operates 190+ Point-of-Presence (PoP) locations worldwide. When a user in Tokyo hits api.yourdomain.com, DNS resolves to the nearest AFD PoP — not to your East US region. The user's TLS handshake terminates in Tokyo. The long-haul connection from Tokyo to East US happens on Microsoft's private backbone, not the public internet. This alone cuts latency by 40–60ms in cross-continental scenarios.
What AFD owns at the edge:
The failover mechanism — how AFD decides to switch regions:
AFD sends a continuous stream of health probes to each origin (your APIM endpoints in Region A and Region B). The probe is a lightweight HTTP GET to a /health path you define. When Region A fails, here's the sequence:
- Probe fails → AFD marks that sample as unhealthy
- After n consecutive failures (configurable, default ~3 across a probe interval), AFD changes the origin's health state to
Degraded - After the origin crosses the unhealthy threshold, AFD removes it from the routing pool
- 100% of traffic shifts to Region B — no DNS TTL wait, no manual intervention
- Total switchover: ~90 seconds from first probe failure to full reroute
This is why your /health endpoint matters more than most engineers realize. A bad health check implementation (returning 200 when the database is down) will fool AFD into sending traffic to a broken region.
What AFD does NOT do: It doesn't understand your API contracts. It doesn't know what a subscription key is. It doesn't do per-request business logic. Once a request clears WAF and routing, AFD's job is done.
Ring 2 — The Contract Layer: Azure API Management
The question this layer answers: Who is allowed to call this API, how often, and in what shape?
By the time a request reaches APIM, Azure Front Door has already handled TLS, DDoS filtering, and CDN logic. APIM's job is entirely different — it enforces the contract between the public internet and your internal services.
Why APIM is not just a reverse proxy:
A reverse proxy (NGINX, Traefik) forwards requests. APIM governs them. The distinction matters at enterprise scale.
- API versioning — APIM lets you publish
/v1/loansand/v2/loansfrom the same backend service, or route them to different backend versions, without the caller knowing the difference. Deprecating v1 becomes a policy change, not a code deployment. - Subscription keys — Every external consumer gets a key tied to a named subscription. You can revoke a single consumer's access without touching the backend. You can see per-consumer traffic in Analytics.
- Rate limiting at the contract layer — Throttling in APIM runs before the request touches AKS. A badly-behaved client gets a
429 Too Many Requestsat the APIM layer, never consuming a pod's resources. - Backend URL abstraction — APIM knows
https://loans-service.internal.aks.cluster:8080. Your API consumers knowhttps://api.yourdomain.com/v1/loans. These are never the same string. APIM is the firewall between those two worlds.
What APIM does NOT do: It doesn't run your business logic. It doesn't know what's inside a loan record. Once a request passes policy evaluation, APIM forwards it to AKS and gets out of the way.
Ring 3 — The Compute Layer: AKS
The question this layer answers: How does a request get from the cluster's front door to the right container — and what happens when that container dies?
From AKS's perspective, every request looks the same: an HTTP call arriving at the cluster boundary. It doesn't know if it came from AFD or APIM or a developer's curl command. This is the right design — compute should be dumb about routing concerns.
The internal flow, explained:
-
Azure Load Balancer (L4) — This is not the same as AFD. The Azure LB is a regional, Layer 4 load balancer. It sees TCP connections, not HTTP requests. Its job is to distribute incoming connections across the NGINX Ingress pods. If an NGINX pod dies, the LB's health probe detects it within 5 seconds and stops sending connections to that pod.
-
NGINX Ingress Controller (L7) — NGINX runs inside the cluster as a regular deployment. It speaks HTTP and HTTPS. It reads your
Ingressresources and builds routing rules:api.yourdomain.com/v1/loans→loans-service:8080. NGINX also handles TLS termination for internal cluster traffic if you're running mutual TLS (mTLS) between services. -
kube-proxy / iptables — This is the lowest-level routing layer in Kubernetes. Every
Serviceobject gets a virtual ClusterIP. kube-proxy programsiptablesrules that translate that ClusterIP into the actual IP addresses of healthy pods. When a pod crashes, theEndpointsobject is updated within seconds, kube-proxy rewrites the iptables rules, and the dead pod's IP is removed from the round-robin pool. This happens automatically, without any human intervention. -
topologySpreadConstraints — This is the Kubernetes feature that makes zone-level resilience real. Without it, the scheduler might place all four pods in AZ1. With it, you declare: spread pods evenly across AZ1 and AZ2. Now an AZ1 failure takes out two pods, not all four.
The Three-Ring Model — All Together
Here's the mental model that ties the three layers into a coherent system:
Each ring handles a different blast radius. The key insight: a pod crash should never become a region incident. If it does, your rings aren't properly isolated.
Five Failure Scenarios — One Architecture
Scenario 1: Pod Crash
A memory leak causes one pod to OOM-kill.
- Pod exits. Kubernetes detects the exit via the container runtime.
- The
Endpointscontroller removes the pod's IP from the Service's endpoint list. - kube-proxy rewrites iptables rules. The dead pod's IP is no longer in the round-robin pool.
- The next request routes to a healthy pod.
Time to recovery: 2–5 seconds. No human action. No AFD involvement. No APIM involvement. The blast radius stays inside Ring 3, pod level.
Scenario 2: Availability Zone Failure
AZ1 loses power. Half your nodes go dark.
- Pods on AZ1 nodes stop responding. Kubelet heartbeats stop reaching the control plane.
- After the
node-monitor-grace-period(default: 40s), the node is markedNotReady. - Pods on the dead nodes are evicted. The scheduler places replacement pods on AZ2 nodes.
- Because of
topologySpreadConstraints, AZ2 already had pods running — traffic shifts there immediately via iptables. - Azure Load Balancer health probes detect the dead NGINX pods and drain them from the backend pool.
Time to recovery: 30–60 seconds. Users see slightly slower responses as AZ2 absorbs load. No regional failover. No AFD involvement.
Scenario 3: Region Failure
East US region has a full outage — network partition, power failure, Azure incident.
RTO: ~90 seconds. RPO: near zero — because Cosmos DB geo-replication is async with typical lag under 1 second.
The important detail: AFD does this automatically. No ops team needs to update DNS. No manual failover script. The health probe threshold crossing is the entire trigger.
Scenario 4: Traffic Spike
A viral event drives 50x normal traffic in 60 seconds. This is not a failure scenario — but the architecture has to handle it, and most teams forget to model it.
The layers absorb the spike from the outside in:
- AFD's CDN serves cached responses at the edge — these never reach APIM or AKS.
- APIM's spike-arrest policy queues or rejects excess requests before they consume pod resources.
- HPA detects rising CPU/memory on existing pods and schedules new pods (30–60s).
- Cluster Autoscaler provisions new nodes when the scheduler can't place pods (2–5 min).
The key: HPA and Cluster Autoscaler together. HPA adds pods. If there's no node capacity to place them, pods stay in Pending. Cluster Autoscaler watches for Pending pods and triggers node provisioning. Without both working in tandem, one of them stalls.
Scenario 5: Silent Data Corruption / Partial Degradation
A bug in a new deployment causes one service to return malformed data — not a crash, not a 500, just wrong data.
This is the hardest scenario and the one most architectures handle worst. The mitigation is not in the traffic routing layer — it's in the deployment strategy:
- Canary deployments via APIM policies — route 5% of traffic to the new version. APIM's
set-backend-servicepolicy can split traffic by percentage before the request reaches AKS. Monitor error rates in App Insights. If the canary is healthy after N minutes, shift to 100%. - Readiness probes in AKS — a pod reports
Ready: falseuntil its own self-check passes. The scheduler won't send traffic to it until it's ready. This prevents partially-started pods from receiving requests during startup. - Azure Monitor alerts on business metrics — not just HTTP 200/500. Alert on "loan processing latency > 2s" or "approval rate dropped 30%". Infrastructure metrics alone won't catch data-layer bugs.
What I Would Do Differently
Active-Passive vs. Active-Active — The Cost Question
This blueprint is active-passive: Region A handles all traffic; Region B is warm but idle, scaled down. The cost model is roughly 1.6x a single-region deployment (Region B runs smaller node pools).
Active-active (both regions handling live traffic simultaneously) cuts RTO to near-zero but roughly doubles cost and adds significant complexity: you need conflict-free data replication, globally consistent session state, and careful geographic routing logic in AFD. For most enterprise workloads, active-passive with 90-second RTO is the right tradeoff. Active-active is worth it when RTO is a contractual SLA, not just a goal.
The AFD + APIM Latency Tax
AFD adds ~5–10ms in the happy path (edge processing, policy evaluation, backbone routing). APIM adds ~10–20ms (policy pipeline execution). Combined, you're paying ~20–30ms per request for the resilience and governance these layers provide.
For sub-50ms SLA APIs, this matters. The mitigation: APIM response caching for idempotent GET endpoints, and co-locating your primary Azure region with the AFD PoP that serves your highest-traffic geography.
The WAF Rule Gap Nobody Talks About
AFD's WAF runs OWASP rulesets — excellent at blocking known attack patterns. They are poor at detecting valid requests with malicious business logic — for example, a valid OAuth token being used to enumerate loan IDs by brute force.
That threat lives in Ring 2 (APIM), not Ring 1. You need APIM's per-subscription rate limiting and anomaly detection (via App Insights custom alerts) to catch it. WAF and APIM together cover the full threat surface. Neither alone does.
The One-Liner for Your Next Architecture Review
"Azure Front Door owns the edge. APIM owns the contract. AKS owns the compute. None of them care what the others are doing internally — and that's exactly why this architecture holds."
The separation of concerns is not an accident. It's the design. Each layer can fail, scale, or be replaced without touching the others. That's what resilience actually means.
What's Next
This post covered the traffic path and resilience model. The natural next step is the data layer — how Cosmos DB geo-replication actually works, what "near-zero RPO" means in practice, and when you should consider active-active writes instead of geo-read replicas. That's the post that most architecture guides skip.
If you're designing a similar system, the five failure scenarios above are your review checklist. Run each one in a tabletop exercise before you go live. The region failure scenario in particular is worth simulating with Azure Chaos Studio — the 90-second AFD failover is only guaranteed if your health check endpoint is correctly implemented.