Add a Region¶
Common request: Add a region to the hub (for example a second region for disaster recovery or data residency).
Effect: the platform environment gets a second virtual hub in the new region, connected to the same Virtual WAN, with its own firewall, DNS, identity and management networks. Spokes can then use the region.
Status of this runbook
This runbook is written from the code (Get-XebiaRegion.ps1, Set-XebiaRegionMatrix.ps1, the layer count guards)
and the two-region Demo configuration (config/Demo/canary, weu + neu). Validate it on canary and refine it after
the first customer region add.
Request fields¶
| Request field | Where it goes | Notes |
|---|---|---|
| Region | active_regions, active_regions_names, new <region>/ folder |
Azure name (swedencentral) and short name (sdc). The short name is used in every resource name and state key. |
| Rationale | PR / change record | DR, latency, data residency, … |
| Environment | canary and/or prod |
Do canary first. |
| Address ranges | <region>/connectivity.auto.tfvars, identity.auto.tfvars, management.auto.tfvars |
Virtual hub prefix, hub VNet subnets, identity and management VNets. Must not overlap with any other region or spoke. |
| Features in the region | <region>/<layer>.auto.tfvars |
Firewall, VPN/ExpressRoute, App Gateway, Bastion, private DNS zones. |
| Comments | – |
How regions work in the code¶
-
Regions come from
<env-folder>/global.auto.tfvars:primary_region = "weu" active_regions = ["weu", "neu"] active_regions_names = ["westeurope", "northeurope"] -
Level 1 runs
foundationandconnectivityin the primary region; level 2 runs them in every other region; level 3 runsfirewall,identityandmanagementin all regions; level 4 runspostdeploymentin the primary region only. - Resources that exist once per platform environment (management groups, policy definitions and assignments, alert
initiatives) are guarded with
count = var.scaffold_location_short_name == var.primary_region ? 1 : 0, so they are not duplicated in the new region. - The Virtual WAN is created by the primary region; the new region adds a virtual hub to it.
- Never change
primary_regionof a running platform. That moves every primary-only resource to a new state key.
Steps¶
1. Allocate address ranges¶
Reserve from the IP plan, for example (Demo uses 10.160.0.0/… for weu and 10.160.20.0/24 as neu virtual hub):
| Range | Variable |
|---|---|
| Virtual hub | virtual_hub_address_prefix (connectivity) |
| Hub VNet and subnets (Bastion, DNS resolver in/out, App Gateway) | hub VNet subnet map (connectivity) |
| DNS server IP of the region | virtual_network_dns_servers (connectivity); spokes in the region use it by default |
| Identity VNet | identity |
| Management VNet | management |
2. Add the region to global.auto.tfvars¶
Append the short name to active_regions and the Azure name to active_regions_names, in the same position:
primary_region = "gwc"
active_regions = ["gwc", "sdc"]
active_regions_names = ["germanywestcentral", "swedencentral"]
3. Create the region folder¶
Create <env-folder>/<region>/. Copy an existing non-primary region folder if you have one, otherwise the primary
region folder, and then adjust:
| File | Change |
|---|---|
global.auto.tfvars |
scaffold_location = "swedencentral", scaffold_location_short_name = "sdc" |
connectivity.auto.tfvars |
New address ranges and DNS server IP. Set feature flags for this region. Point application_gateway_json_file_relative_path / web_application_firewall_policy_json_file_relative_path at sdc/… folders. |
firewall.auto.tfvars |
firewall_rule_collection_file_location = "sdc/firewall_rule_configuration", ip_group_json_file_relative_path = "sdc/ip_groups/". Keep the global paths. |
identity.auto.tfvars, management.auto.tfvars |
New VNet ranges. |
foundation.auto.tfvars |
Only the regional values (for example action group receivers). Primary-only settings such as management_group_structure have no effect outside the primary region; Demo neu uses management_group_structure = {}. |
postdeployment.auto.tfvars |
Not read: postdeployment runs only in the primary region. You can leave it out. |
Write every path relative to the <env-folder> (why).
4. Add firewall rules for the region¶
- Create
<region>/firewall_rule_configuration/and<region>/ip_groups/with the rules the region needs (at least the platform rules: job runners, DNS, Azure services). - With
use_multiregion_fwp_rulecollectiongroup_naming = true, files in the globalfirewall_rule_configuration/are merged in for every region; a regional file with the same name wins. - Add rules for traffic between regions where needed.
5. Validate and open a pull request¶
Follow Making a change safely, steps 4–5. The config tests also validate the new region's JSON.
6. Plan and apply on canary¶
Run hub in Plan or ApproveModifications mode. Expect:
| Layer | Existing region | New region |
|---|---|---|
foundation |
no changes | regional resources only (action groups, …) |
connectivity |
no changes (check the Virtual WAN) | virtual hub, firewall, firewall policy, hub VNet, DNS resolver, enabled gateways |
firewall |
no changes | IP groups and rule collection groups |
identity, management |
no changes | VNets and hub connections, regional management resources |
postdeployment |
no changes | – |
Stop if the existing region shows destroys or replacements. Creating a virtual hub and firewall takes 30–60 minutes.
7. Verify¶
- Connectivity subscription → Virtual WAN → Hubs: the new hub is listed and Succeeded.
- Firewall Manager: the new hub is a secured hub with the expected policy.
- The identity and management VNets of the new region show as connected.
- Name resolution from the new region's DNS resolver works for a private endpoint zone.
8. Repeat for prod¶
9. Let spokes use the region¶
Per spoke: add the region to targetRegions.additional and add a network[] entry with "region": "sdc" and at least
one subnet with "isprivatelinksubnet": true. Run spoke for that file. See
Change spoke.
Roll back¶
Remove the region from active_regions / active_regions_names only after destroying its resources: a region that
is no longer in the matrix is not planned at all, so its resources stay in Azure with orphaned state. To remove a region,
first remove all spokes from it, then destroy its layers locally with Remove-XebiaLocal -DeployName <layer> -Region <region>
in reverse order, and only then remove it from global.auto.tfvars.
Common problems¶
| Symptom | Cause |
|---|---|
primary_region '<x>' is not in active_regions |
primary_region missing from active_regions |
| Region not deployed at all | Region missing from active_regions, or the regex in Get-XebiaRegion.ps1 can't parse the list (keep it on one line, double quotes) |
| Firewall layer plans no rules for the new region | Wrong relative path in <region>/firewall.auto.tfvars |
| Name conflicts in Key Vault or storage | Global names include the region short name; choose a short name that keeps them under the Azure limits |