Skip to content

Regions and subnet mapping

Region-aware load balancing (region and geo-failover) needs to know which region a client and each backend belong to. DTM works this out from IP addresses using subnet mappings.

A region in DTM is simply a string identifier you choose, such as northeurope or westeurope. Regions are not separate objects to manage; they come into existence when you reference them on a subnet mapping or a record value. The set of known regions is derived from those references.

Region matching is case-insensitive and ignores spaces, the way Azure treats locations: a value tagged East US or EastUS matches a mapping or discovery result that says eastus. A genuinely different region is still remote; only case and spaces are ignored.

Fallback order, from v1.0.7. A client is answered from its own region first. When no value is in that region, the values in its parent or in a sub-region stand in for each other: eastus and eastus/spoke-a match each other, but two sibling sub-regions do not. Only then does DTM answer from every region. When no value passes its health check, a record that uses a script template is answered with the values whose checks have not failed (a script check that has not run yet, for example) instead of the values that have failed. Any other record, or a script-template record whose checks have all failed, is answered with all its enabled values.

A subnet mapping ties a CIDR block to a region. From v1.0.7 the web UI shows them on the Region Mappings card (formerly Subnet Mappings), since a discovered row is a VNet address-space prefix and a manual row can be any range, an on-premises one for example; the API and Terraform keep the subnet mapping names:

{ "subnet": "10.100.0.0/16", "region_id": "northeurope" }

DTM uses these to:

  • Determine a client’s region from the source address of a DNS query.
  • Infer a value’s region when you have not tagged it explicitly.

When CIDRs overlap, a mapping’s optional priority decides which one wins (higher priority wins); otherwise the most specific prefix match applies.

How a query's source IP becomes a region: the source address is matched against the subnet mappings, the most specific prefix wins and an explicit priority breaks overlaps, manual and discovered mappings feed the same table, and the resulting region drives the region-aware load-balancing decision. With no matching mapping the client is assumed to be in the serving node's own region. How a query's source IP becomes a region: the source address is matched against the subnet mappings, the most specific prefix wins and an explicit priority breaks overlaps, manual and discovered mappings feed the same table, and the resulting region drives the region-aware load-balancing decision. With no matching mapping the client is assumed to be in the serving node's own region.

Rather than maintain subnet mappings by hand, you can let DTM discover your Azure VNet address space and generate region mappings for you. You define one or more discovery scopes (a subscription, optionally narrowed to a resource group), and DTM walks them using its managed identity’s read access to build subnet-to-region mappings.

Each node needs outbound HTTPS (port 443) to management.azure.com, which serves both discovery backends below. A new Azure subnet is private by default, so a node with no public IP needs an outbound path, for example a NAT gateway on its subnet or a route through a firewall that allows that host. Without it, every discovery scope times out. Tag registration and Private Endpoint automation need the same path.

Discovery detects on a timer; you apply. The periodic loop queries Azure, diffs the result against the stored mappings, and reports what changed. It does not write mappings on its own: an administrator reviews the pending diff (in the web UI or via POST /api/v1/discovery/run?dry_run=true) and applies it. An unapplied mapping is benign; an unmapped client is treated as being in the serving node’s region, so resolution keeps working while a change waits for review. Set azure.region_discovery_auto_apply: true to restore the previous apply-on-a-timer behaviour.

Two safety rules apply to every cycle:

  • Removals are only ever proposed from a complete observation. Azure filters by RBAC rather than erroring, so a subscription the identity has lost Reader on looks identical to an empty one. If a cycle could not see the whole configured estate, additions are still offered but no removals are, because the likeliest cause of a large proposed removal is a lapsed role assignment, not a deleted estate.
  • Newly observed Azure regions are surfaced in the diff, so an estate expanding into a region with no DTM presence is visible to whoever plans capacity, instead of being absorbed silently.

Discovered mappings are marked as coming from Azure so you can tell them apart from ones you created manually, and manual mappings always carry source: manual (the API refuses any other value, so automation cannot disguise a hand-made mapping as a discovered one, or vice versa). You can add manual mappings alongside discovered ones.

For estates spanning many subscriptions, a discovery scope can name an Azure management group instead of a single subscription (kind: "management-group"). Grant the DTM identity Reader once at the management group and every subscription underneath becomes usable, instead of managing a role assignment per subscription.

A management-group scope is a reviewed shortcut, not an open-ended grant of trust:

  • DTM never expands a management group on its own. Creating the scope requires the explicit list of subscriptions it covers. You preview the group first (in the UI or via GET /api/v1/discovery/available-management-groups), see which member subscriptions the identity can and cannot read, and confirm the set you are authorising. A subscription that joins the group later cannot start being scanned without someone approving it.
  • Preview with verification. The quick preview infers readability from the roles the identity holds anywhere in each subscription, which can be optimistic (Reader on one resource group makes the whole subscription appear). The verified preview issues the same subscription-wide read discovery actually performs, so what it reports readable really is.
  • Membership drift is detected, reviewed, and applied, like mapping changes: each cycle re-reads the group and reports subscriptions that joined (proposed additions), left the group (proposed removals), or are still in the group but no longer readable. That last case is a revoked role assignment, not a membership change, and is deliberately never proposed for removal: dropping it would turn a permissions problem into deleted routing state. Instead, cycles report themselves incomplete until you restore the grant or remove the subscription deliberately. region_discovery_auto_apply: true applies membership drift on the timer too; the default is manual for both.

Subscription scopes keep working unchanged, and both kinds can coexist.

Discovery has two interchangeable backends. The default walks each scope with per-resource ARM calls. The resource-graph mode instead runs a single Azure Resource Graph query across every enabled scope, which is faster and scales better across many subscriptions. Your discovery scopes and the resulting mappings are identical either way.

Switch the mode at runtime, no restart needed, from the Region Discovery settings card in the web UI, the dtm_region_discovery_config Terraform resource, or PUT /api/v1/config/region-discovery (admin). A persisted value wins over the azure.region_discovery_mode server-config seed, which just sets the initial default. For resource-graph, the managed identity needs Resource Graph Reader covering every subscription in scope; an unentitled switch fails closed and is reversible by switching back. Discovery is conservative with cleanup on both backends: stale mappings are pruned only after a fully successful cycle, so a partial or truncated query never removes mappings. Changed in v1.0.7: Resource Graph indexes new subscriptions, resource groups and resources some time after they exist. When it returns nothing for a scope but an ordinary Azure list call shows virtual networks there, the scope is left alone for that cycle and the cycle reports itself incomplete. A scope that is readable and genuinely empty is still pruned.

  • Web UI: the settings pages cover subnet mappings and discovery scopes.
  • REST API: /api/v1/subnet-mappings, /api/v1/discovery/scopes, and /api/v1/regions.
  • Terraform: dtm_subnet_mapping and dtm_discovery_scope.