Skip to content

AKS: health-gated, region-aware services

This page composes shipped DTM features into a reference architecture for multi-region AKS services: ExternalDNS publishes per-region service hostnames into a shadow subdomain, and one DTM record wraps them into a single stable name that returns only healthy, preferably-local answers, entirely on private IPs.

The composed architecture: AKS clusters in eastus and westus each run ExternalDNS, which publishes its per-region service hostname into the shadow zone svc.aks.internal.example on the DTM cluster. The wrap record web.app.internal, an ALIAS with a region policy and health checks, points at both shadow hostnames. DTM probes the resolved addresses in both regions. A client VM in eastus asking for web.app.internal gets the healthy, local-first answer A 10.10.0.20, and a muted path shows that if eastus goes unhealthy the same name answers with the other region, A 10.20.0.20, with no record edits. The composed architecture: AKS clusters in eastus and westus each run ExternalDNS, which publishes its per-region service hostname into the shadow zone svc.aks.internal.example on the DTM cluster. The wrap record web.app.internal, an ALIAS with a region policy and health checks, points at both shadow hostnames. DTM probes the resolved addresses in both regions. A client VM in eastus asking for web.app.internal gets the healthy, local-first answer A 10.10.0.20, and a muted path shows that if eastus goes unhealthy the same name answers with the other region, A 10.20.0.20, with no record edits.

Each regional cluster runs its own ExternalDNS instance publishing into a shadow subdomain, exactly as on the ExternalDNS page:

  • web-eastus.svc.aks.internal.example from the East US cluster
  • web-westus.svc.aks.internal.example from the West US cluster

Clients never use those names. Instead, a single DTM record, web.app.internal, wraps both. DTM resolves the shadow hostnames, health-checks the addresses behind them, applies a region-aware (or failover) policy, and answers each client with the best healthy private IP. When a region’s service degrades or its address changes, the stable name follows automatically and nobody edits a record.

Azure Private DNS records are static: there is no health awareness, so a dead backend keeps resolving. Azure Traffic Manager is health-aware but probes public endpoints. For private AKS services there is a real gap: server-side, health-gated, region-aware answers on private IPs. That is exactly what this composition provides, with failover decided centrally by DTM rather than by each client.

There are two ways to build the stable name; pick one.

Option A: an ALIAS record over the shadow hostnames

Section titled “Option A: an ALIAS record over the shadow hostnames”

An ALIAS record in a DTM zone points at the shadow hostnames. DTM keeps them resolved, health-checks the resolved addresses, and returns flattened A records.

Terminal window
curl -X POST https://<dtm>/api/v1/zones/<zone-id>/records \
-H "Authorization: Bearer <token>" \
-H "X-DTM-CSRF: 1" \
-H "Content-Type: application/json" \
-d '{
"name": "web",
"type": "ALIAS",
"ttl": 30,
"values": [
{ "value": "web-eastus.svc.aks.internal.example", "region": "eastus" },
{ "value": "web-westus.svc.aks.internal.example", "region": "westus" }
],
"lb_policy": { "method": "region" },
"health_check_template": {
"protocol": "https", "port": 443, "path": "/healthz",
"interval_seconds": 5
}
}'
  • lb_policy: region steers each client to its own region and fails over across regions when one goes unhealthy. failover gives you an active/standby shape, and weighted supports canary-style splits.
  • health_check_template: template-created checks probe every resolved address with an interval of at least 5 seconds (default 15), marking a target down after 3 consecutive failures and up again after 2 consecutive successes.

Option B: a static-mode override record plus a reconciler

Section titled “Option B: a static-mode override record plus a reconciler”

If you prefer not to host a zone for the stable name, use a static-mode override record instead. A small reconciler you run (for example, a controller watching the two regional services) writes the services’ current IPs into the override; DTM’s probes target those IPs directly, and no forwarder is involved:

Terminal window
curl -X POST https://<dtm>/api/v1/overrides \
-H "Authorization: Bearer <token>" \
-H "X-DTM-CSRF: 1" \
-H "Content-Type: application/json" \
-d '{
"fqdn": "web.app.internal.",
"type": "A",
"target_mode": "static",
"ttl": 30,
"values": [
{ "value": "10.10.0.20", "region": "eastus" },
{ "value": "10.20.0.20", "region": "westus" }
],
"lb_policy": { "method": "region" },
"health_check_template": {
"protocol": "https", "port": 443, "path": "/healthz",
"interval_seconds": 5
}
}'

The trade-off is ownership: with Option A, DTM follows address changes automatically; with Option B, your reconciler must update the override whenever a service’s IP changes, but the resolution path is simpler and fully under your control.

  • A pod or backend fails its probe: that region’s address is dropped from the answer after 3 failed probes; clients land on the remaining healthy region.
  • A whole region goes down: with method: region, clients in the failed region are served the other region’s healthy address automatically.
  • The region recovers: after 2 consecutive successful probes the address returns to answers, and region-local clients drift back on the record’s TTL (30 seconds above).
  • Terraform: the wrap record works as code too: dtm_record supports type = "ALIAS", and override records are available as dtm_override_record; see ALIAS, GSLB and overrides.
  • Regions: the region tags on values drive the region policy; see regions and subnet mappings for how DTM decides which region a client is in.
  • One stable name per service: repeat the wrap per service (web, api, and so on); each gets its own policy and health template.

Need a hand? Email [email protected].