01 - Multi-Region Architecture on Azure
Why this matters
A DR strategy on paper (module 00) needs a substrate: somewhere to fail over to, a way to keep data there, and a router that sends users to whichever region is alive. This module builds all three on Azure — a global traffic router (Traffic Manager or Front Door), geo-redundant data, and a second AKS region — reusing the exact Terraform modules from track 09 and the networking concepts from track 05, now applied to a second region. Getting the traffic router and DNS behaviour right here is what makes the failover drills in module 02 actually redirect users instead of quietly doing nothing.
Concepts
Two regions, and what "paired" buys you
From module 00 you have a primary region and a candidate DR region. Choosing
your DR region to be Azure's paired region for the primary gives you two
free platform behaviours: GRS storage replicates there automatically, and
Azure sequences planned platform maintenance so a pair isn't updated
simultaneously. You don't have to use the pair (latency or data-residency
may push you elsewhere), but if you have no strong reason otherwise, the pair
is the sensible default. The rest of this module assumes primary =
eastus, secondary = its pair (e.g. westus), but every command takes the
region as a parameter — swap freely.
Reusing track 09's Terraform modules for a second region
The critical realization: you already wrote reusable infrastructure. Track
09's capstone had a network module and a platform/aks module driven by
variables (region, address space, names). A second region is not new
code — it's the same modules invoked a second time with a different
region and a non-overlapping address space:
module "primary" {
source = "./modules/region"
location = "eastus"
address_space = "10.10.0.0/16"
name_prefix = "app-eus"
}
module "secondary" {
source = "./modules/region"
location = "westus"
address_space = "10.20.0.0/16" # MUST NOT overlap the primary
name_prefix = "app-wus"
}
This is the concrete payoff of doing IaC properly in track 09: "add a DR
region" becomes a second module block, and "redeploy the whole region from
scratch" (module 03's DR mechanism) becomes terraform apply. The
non-overlapping CIDR is not optional — if you ever peer the two VNets (for
cross-region replication traffic), overlapping ranges make routing
impossible, exactly as track 05 taught.
Data replication options and their RPO
Compute is stateless and easy to duplicate; data is the hard part of multi-region, and each option is a different RPO:
- Geo-redundant storage (GRS / RA-GRS). For Blob/queue/table/file data. GRS asynchronously copies to the paired region (RPO minutes, no read access until failover); RA-GRS adds read access to the secondary at any time — essential if your DR region needs to read that data before an official failover. This is track 14 / module 04's backup destination, now seen as a DR primitive.
- Managed database geo-replication. Azure SQL active geo-replication, Cosmos DB multi-region, or PostgreSQL Flexible Server read replicas in another region — continuous async replication with an RPO of seconds and a promotable secondary. This is the managed-service path from track 14 / module 05, now used for cross-region DR.
- Self-managed replication. The CloudNativePG / streaming-replication approach from track 14 / module 03, with a replica in the second region. More control, more operational burden.
- Zone-redundant vs. geo-redundant. Don't confuse them: zone-redundant (ZRS) survives an AZ failure within one region and is far cheaper than a second region. If your target failure domain is "one datacenter," ZRS may be the whole answer (module 00's failure-domain point).
The RPO of your whole system is the worst RPO among its data stores — a 5-second database RPO paired with hourly blob backups gives you an hourly RPO overall.
Traffic Manager: DNS-level global routing
Azure Traffic Manager routes at the DNS layer. Clients resolve a
Traffic Manager name (myapp.trafficmanager.net, usually behind your own
CNAME), and Traffic Manager answers with the IP of a healthy endpoint chosen
by a routing method:
- Priority — always send to endpoint 1 unless it's unhealthy, then endpoint 2. This is the failover routing method: primary region normally, secondary only when the primary's health probe fails.
- Performance — send each client to the lowest-latency region (good for active-active).
- Weighted — split by ratio (canary-style, or gradual cutover).
- Geographic — route by client geography (data residency).
The catch that dominates module 02's drill: DNS caching. Traffic Manager health probes detect a dead region in probe-interval seconds, but clients and resolvers cache the DNS answer for the record's TTL. A 300-second TTL means users can keep hitting the dead region for up to 5 minutes after Traffic Manager has already switched. Low TTLs shrink this window but increase DNS query volume. This TTL-vs-failover-speed tension is the single most common reason a "successful" failover doesn't actually redirect traffic — you'll reproduce it deliberately in module 02.
Front Door: an L7 global entry point
Azure Front Door operates at Layer 7 (HTTP/S) as a global reverse proxy with anycast: clients connect to Front Door's nearest edge POP, and Front Door forwards to a healthy backend (origin) over Microsoft's backbone. Because clients keep a connection to the edge, not to a region directly, failover doesn't wait on client DNS TTLs — Front Door re-routes at the edge in seconds. It also brings TLS termination, WAF, caching, and path-based routing (conceptually the global cousin of the Application Gateway from track 05). Trade-offs vs. Traffic Manager:
- Traffic Manager: DNS-level, protocol-agnostic (works for any TCP service, not just HTTP), cheaper, but bounded by DNS TTL on failover.
- Front Door: HTTP/S only, faster failover (no client DNS dependency), more features (WAF/caching/TLS), higher cost.
For a web/API system needing fast regional failover, Front Door is usually the better DR entry point precisely because it sidesteps the DNS-TTL problem. For non-HTTP services or cost sensitivity, Traffic Manager. You'll build both here so you understand the difference in your hands.
AKS multi-region patterns
There is no "stretch one AKS cluster across two regions" — a cluster lives in one region. Multi-region AKS means two independent clusters, one per region, with:
- Independent provisioning — the same Terraform
aksmodule (track 09) applied per region. - Independent, geo-replicated images — one ACR with geo-replication enabled (a single registry that stores images in multiple regions, so each cluster pulls locally and an ACR outage in one region doesn't stop the other). Cheaper and simpler than two separate registries.
- A global router in front — Traffic Manager or Front Door pointing at each cluster's ingress/LoadBalancer IP as an endpoint/origin.
- Config/secret parity — both clusters need the same Deployments, ConfigMaps, and Secrets. GitOps (track 10 / ArgoCD) is the clean way: one Git repo, both clusters reconciling from it, so the standby is never a hand-maintained divergent copy. A standby whose config has silently drifted is a DR that fails at the worst moment.
The strategy from module 00 decides how the second cluster runs: pilot light = scaled to zero until needed; warm standby = running small; active-active = full capacity behind performance/weighted routing.
Command reference
| Command | What it does | Example |
|---|---|---|
az network traffic-manager profile create |
Creates a Traffic Manager profile (the DNS router) | see breakdown |
az network traffic-manager endpoint create |
Adds a region as an endpoint to the profile | see breakdown |
az network traffic-manager profile show |
Shows profile FQDN, routing method, endpoint health | az network traffic-manager profile show -g <rg> -n <prof> |
az afd profile create |
Creates a Front Door (Standard/Premium) profile | az afd profile create -g <rg> --profile-name fd --sku Standard_AzureFrontDoor |
az afd origin-group create |
Creates an origin group with health probes | see breakdown |
az afd origin create |
Adds a regional backend as an origin | see breakdown |
az acr replication create |
Geo-replicates an ACR to a second region | az acr replication create -r <acr> -l westus |
az storage account create --sku Standard_RA-GRS |
Creates read-access geo-redundant storage | az storage account create -n <n> -g <rg> --sku Standard_RA-GRS |
dig +short <name> / nslookup |
Observes what DNS actually returns (and TTL) — critical for module 02 | dig +short myapp.trafficmanager.net |
Flag breakdown — a priority (failover) Traffic Manager profile:
az network traffic-manager profile create \
-g dr-rg -n app-tm \
--routing-method Priority \
--unique-dns-name app-tm-$RANDOM \
--ttl 30 \
--protocol HTTP --port 80 --path "/healthz" \
--interval 10 --timeout 5 --max-failures 3
--routing-method Priority— failover routing: endpoint 1 unless unhealthy, then 2.--unique-dns-name— the label in<name>.trafficmanager.net; must be globally unique.--ttl 30— the DNS TTL clients cache for. Low on purpose so failover isn't stuck behind a long cache — this single flag is the crux of module 02's DNS-TTL diagnose-and-fix.--protocol HTTP --port 80 --path "/healthz"— the health probe: GEThttp://<endpoint>:80/healthz. A non-2xx marks the endpoint unhealthy and triggers failover.--interval 10 --timeout 5 --max-failures 3— probe every 10s, fail a probe after 5s, mark unhealthy after 3 consecutive failures (~30s to detect an outage). Detection time + TTL = total failover time.
Flag breakdown — adding the two regional endpoints (priority order):
az network traffic-manager endpoint create \
-g dr-rg --profile-name app-tm -n primary \
--type externalEndpoints --target <primary-ingress-ip> \
--priority 1 --endpoint-status Enabled
az network traffic-manager endpoint create \
-g dr-rg --profile-name app-tm -n secondary \
--type externalEndpoints --target <secondary-ingress-ip> \
--priority 2 --endpoint-status Enabled
--type externalEndpoints— the target is an IP/FQDN you supply (your cluster's public ingress IP), not an Azure resource ID.--target— the regional entry point's IP.--priority 1/2— 1 is primary; traffic goes to 2 only when 1 is unhealthy. This is the failover behaviour.
Flag breakdown — a Front Door origin group with health probes:
az afd origin-group create \
-g dr-rg --profile-name fd --origin-group-name app-og \
--probe-request-type GET --probe-protocol Http --probe-path "/healthz" \
--probe-interval-in-seconds 30 \
--sample-size 4 --successful-samples-required 3 \
--additional-latency-in-milliseconds 50
--probe-path "/healthz"— Front Door health-probes each origin here.--probe-interval-in-seconds 30— how often edges probe origins.--sample-size 4 --successful-samples-required 3— need 3 of the last 4 probes healthy to keep an origin in rotation. Failover happens at the edge when an origin drops below this — no client DNS TTL involved, which is Front Door's DR advantage.
Hands-on exercises
These build a real second region. It is billable — a second AKS cluster
plus a global router. Keep the standby small (or scaled to zero) and run the
full teardown in exercise 9 the same day. If you have your track 09 capstone
Terraform, reuse it; if not, the exercises use az directly so you can still
do them.
1. Confirm your region pair and reserve non-overlapping CIDRs
az account list-locations \
--query "[?name=='eastus'].metadata.pairedRegion[0].name" -o tsv
Write down primary (eastus, 10.10.0.0/16) and secondary (the pair,
10.20.0.0/16). Non-overlapping is mandatory (track 05) — verify the two
/16s don't intersect.
2. Stand up the second region from your track 09 modules
If you have the track 09 capstone modules, add a secondary module block
(as in Concepts) with the DR region and its CIDR, terraform plan, read the
N to add line (it should be a second copy of the region's resources and
nothing destroyed in the primary), and apply. If you don't have the
Terraform, create a minimal second region by hand:
az group create -n dr-secondary-rg -l westus
az aks create -g dr-secondary-rg -n aks-wus \
--node-count 1 --node-vm-size Standard_B2s --generate-ssh-keys \
--tier free --load-balancer-sku standard
Expected: a second cluster in the DR region. Note in writing that with real IaC this was one module block, not a from-scratch build — that reuse is the DR mechanism module 03 formalizes.
3. Deploy the same app to both regions
Deploy an identical workload (the simplest whole-cluster echo works) to each cluster and expose it via a LoadBalancer so each has a public IP:
# For each cluster (get-credentials first):
kubectl create deployment web --image=mcr.microsoft.com/azuredocs/aks-helloworld:v1
kubectl expose deployment web --type=LoadBalancer --port=80 --target-port=80
kubectl get svc web -w # note the EXTERNAL-IP
Record both external IPs. These are the two endpoints your global router will front. (In a real system this parity is enforced by GitOps/track 10 — here you're doing it by hand to see the moving parts.)
4. Put a priority Traffic Manager profile in front
az group create -n dr-rg -l eastus
az network traffic-manager profile create \
-g dr-rg -n app-tm --routing-method Priority \
--unique-dns-name app-tm-$RANDOM --ttl 30 \
--protocol HTTP --port 80 --path "/" \
--interval 10 --timeout 5 --max-failures 3
az network traffic-manager endpoint create -g dr-rg --profile-name app-tm \
-n primary --type externalEndpoints --target <primary-ip> --priority 1
az network traffic-manager endpoint create -g dr-rg --profile-name app-tm \
-n secondary --type externalEndpoints --target <secondary-ip> --priority 2
az network traffic-manager profile show -g dr-rg -n app-tm \
--query "{fqdn:dnsConfig.fqdn, method:trafficRoutingMethod}" -o table
Expected: a *.trafficmanager.net FQDN that resolves to your primary
region's IP while it's healthy.
5. Observe the DNS answer and its TTL
FQDN=$(az network traffic-manager profile show -g dr-rg -n app-tm --query dnsConfig.fqdn -o tsv)
dig +short "$FQDN" # the IP it hands out
dig "$FQDN" | grep -A1 "ANSWER SECTION" # note the TTL value
curl -s "http://$FQDN/" | head -5
Expected: dig returns the primary IP, and the TTL matches your --ttl 30.
Internalize this: clients will cache that answer for the TTL — the number
that will bite you in module 02.
6. Geo-replicate the ACR (multi-region image pulls)
# Assuming an ACR from an earlier track; enable geo-replication to the DR region:
az acr replication create -r <youracr> -l westus
az acr replication list -r <youracr> -o table
Expected: the registry now shows a replica in the DR region. Each cluster pulls images from its local replica; an ACR regional outage no longer blocks the surviving cluster. Note this is one registry, not two — cheaper and no image-drift risk.
7. Build a Front Door in front of the same two regions
az afd profile create -g dr-rg --profile-name fd --sku Standard_AzureFrontDoor
az afd endpoint create -g dr-rg --profile-name fd --endpoint-name app-$RANDOM --enabled-state Enabled
az afd origin-group create -g dr-rg --profile-name fd --origin-group-name app-og \
--probe-request-type GET --probe-protocol Http --probe-path "/" \
--probe-interval-in-seconds 30 --sample-size 4 --successful-samples-required 3 \
--additional-latency-in-milliseconds 50
az afd origin create -g dr-rg --profile-name fd --origin-group-name app-og \
--origin-name primary --host-name <primary-ip> --origin-host-header <primary-ip> \
--http-port 80 --priority 1 --weight 1000 --enabled-state Enabled
az afd origin create -g dr-rg --profile-name fd --origin-group-name app-og \
--origin-name secondary --host-name <secondary-ip> --origin-host-header <secondary-ip> \
--http-port 80 --priority 2 --weight 1000 --enabled-state Enabled
# then create a route binding the endpoint to the origin group (see az afd route create)
Expected: an *.azurefd.net endpoint that reaches your app. The key
conceptual note to write down: Front Door failover happens at its edge based
on the health probe — there is no client DNS TTL in the failover path,
unlike Traffic Manager.
8. Diagnose-and-fix: the health probe path that lies
Point the Traffic Manager probe at a path your app doesn't serve as 200
(e.g. /healthz when the app only serves /), then observe:
az network traffic-manager profile update -g dr-rg -n app-tm --path "/healthz"
sleep 40
az network traffic-manager endpoint show -g dr-rg --profile-name app-tm \
-n primary --query endpointMonitorStatus -o tsv # likely Degraded
dig +short "$FQDN"
You'll find both endpoints go Degraded (neither serves /healthz 200),
and Traffic Manager, with no healthy endpoint, falls back to returning all
endpoints — so it looks like it's still working but has no real health
signal. The lesson: a health probe that doesn't hit a real health endpoint
gives you fake failover — it either never fails over (probe too lenient) or
fails over constantly / degrades everything (probe hitting the wrong path).
Fix by pointing the probe at a path the app genuinely returns 200 for (add a
real /healthz), and re-verify both endpoints go Online. This same
mistake — a probe that doesn't reflect true health — is why some failovers in
module 02 don't trigger when the region is actually sick.
9. Clean up (this is the expensive part — do it today)
az afd profile delete -g dr-rg --profile-name fd --yes
az network traffic-manager profile delete -g dr-rg -n app-tm
az acr replication delete -r <youracr> -l westus --yes # drop the geo-replica if not keeping it
az group delete -n dr-secondary-rg --yes --no-wait # the whole second region
az group delete -n dr-rg --yes --no-wait
az aks list -o table # confirm the DR cluster is gone
Expected: the second cluster, both routers, and the ACR replica are gone. Leaving a second AKS cluster running is the exact way this track produces a real bill — the second region roughly doubles cost for as long as it's up.
Independent challenge
Using the same reusable region module from track 09 (and the ACR
geo-replication and one global router from this module), stand up a warm
standby for a small web app: primary region full-size, secondary region
running but at reduced capacity (e.g. one node, one replica), both fronted by
a single global router with the primary preferred. Verify with dig/curl
that traffic normally lands in the primary. Then write — don't yet execute,
that's module 02 — the exact sequence you'd run to promote the standby if the
primary died, including how you'd scale the secondary up to full capacity.
State the RPO/RTO you think this warm-standby build achieves and which
region's data store is authoritative. This draws on track 09 (reusable
modules), track 05 (non-overlapping VNets, DNS), and module 00's strategy
choice. Tear the second region down the moment you've verified it — a warm
standby left running is a doubled bill.
Stuck? One hint
The warm standby differs from active-active only in capacity and traffic
share, not in architecture — same two regions, same router, but the
secondary runs --node-count 1 and replicas: 1, and the router uses
Priority (primary preferred) rather than Performance/Weighted. "Promotion" is
therefore two steps: scale the secondary out (az aks nodepool scale +
kubectl scale deployment), then let the router's health probe (or a manual
endpoint disable on the primary) shift traffic. The RTO is dominated by how
long the scale-out takes plus the router's detection + TTL window — which is
exactly why warm standby is faster than pilot light (already running) but
slower than active-active (already full-size).
Common mistakes & troubleshooting
- Overlapping CIDRs across regions. If you ever peer the VNets for replication traffic, overlapping address spaces make it unroutable (track 05). Reserve non-overlapping ranges up front even if you don't peer yet.
- Confusing zone-redundant with geo-redundant. ZRS/AZ-spread survives a datacenter failure within one region and is far cheaper than a second region; GRS/multi-region survives a regional failure. Match the mechanism to the failure domain you actually need (module 00).
- A standby whose config has silently drifted. A second cluster hand-maintained apart from the primary will have missing secrets or stale images exactly when you fail over. Drive both from one Git source (GitOps, track 10) so the standby can't diverge.
- Health probe pointing at the wrong path (exercise 8). A probe that doesn't reflect real health gives fake failover — degrading everything or never triggering. Probe a genuine health endpoint that returns 200 only when the app can actually serve.
- Assuming Traffic Manager fails over instantly. It's DNS — clients cache the answer for the TTL, so real failover time is detection time plus TTL. If you need sub-TTL failover, use Front Door (edge-level) instead. This is module 02's headline pitfall.
- Cost pitfall — running a full-size second region 24/7 (ties to track 21). A second full-capacity region is the single largest DR cost, and for most systems a pilot light (secondary scaled to zero, only data replicating) meets the RTO at a fraction of the price. Geo-replicated ACR, cross-region egress, RA-GRS read charges, and the global router all add up too — none are free. Right-size the standby to the strategy module 00 chose, and scale it to zero (or destroy it) whenever you're not drilling.
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
- What does choosing Azure's paired region as your DR region give you automatically?
- Why must the second region use a non-overlapping CIDR, and which earlier track established that rule?
- Explain the core difference between how Traffic Manager and Front Door fail over, and why that determines failover speed.
- In a priority Traffic Manager profile, which two durations add up to total failover time, and which flags control each?
- What is the difference between GRS and RA-GRS, and when does the distinction matter for DR?
- There's no single AKS cluster stretched across two regions — so what is the multi-region AKS pattern, and how do you stop the two clusters' images and config from diverging?
- In exercise 8, a health probe pointed at the wrong path produced "fake failover." Explain what went wrong and the fix.
- Give the one-sentence FinOps rule (track 21) for how large to run the standby region.
Show answers
- Automatic GRS replication to that region and Azure sequencing platform maintenance so the pair isn't updated simultaneously.
- Because peering VNets with overlapping address ranges is unroutable, so overlapping CIDRs make cross-region replication traffic impossible; track 05 (azure-networking) established it.
- Traffic Manager routes at the DNS layer, so failover is bounded by the client-cached TTL (failover = detection + TTL); Front Door routes at its L7 edge with no client DNS in the failover path, so it re-routes in seconds regardless of TTL.
- Detection time (set by the probe's
--interval,--timeout, and--max-failures) plus the DNS--ttlthat clients cache. - GRS replicates data to the paired region but you can't read the secondary copy until Microsoft initiates failover; RA-GRS adds read access to the secondary at any time. It matters when your DR region needs to read that data before an official failover.
- Two independent clusters, one per region, provisioned by the same Terraform module and fronted by a global router, pulling from one geo-replicated ACR; keep them in sync by reconciling both from one Git repo (GitOps, track 10) so config can't drift.
- The probe hit a path the app didn't serve as 200, so every endpoint went Degraded and Traffic Manager, with no healthy endpoint, fell back to returning all of them — a health signal that reflects nothing. Fix: point the probe at a real health endpoint that returns 200 only when the app can actually serve, and confirm endpoints go Online.
- Run the standby at the smallest size that still meets the chosen strategy's RTO — pilot light or warm standby for most systems, full-size only if active-active is genuinely required.
Next
02-designing-and-testing-a-dr-plan — you have two regions and a router that can fail over. Now write the actual runbook for a regional outage — and, the part almost everyone skips, really execute a failover drill and measure whether traffic redirected.