Creating a New Virtual Network (VNET)
Creating a New VNET
Location: terraform/coreservices/region
This guide walks you through creating a new VNET using Terraform, including route tables, peering, and firewall ticketing.
Prerequisites
Before creating a new VNET, ensure you have:
- Terraform version: 1.5.0 or later
- Azure permissions: Contributor or Network Contributor role on the target subscription
- Repository access: Read/write access to the
terraform/coreservicesandterraform/connectivityrepositories - Pre-existing infrastructure:
- Resource group for network resources (or plan to create one)
- Hub VNET for peering (if applicable)
- Firewall or network virtual appliance (if routing through a firewall)
- Tools: Azure CLI, Git, and Terraform CLI installed and configured
Table of Contents
- Creating a New VNET
Step 1: Define the VNET
Note: All configuration changes are made locally, validated with terraform plan, then committed to a PR. The actual infrastructure deployment happens automatically when the PR is merged via the TFE VCS-backed workspace.
Edit var_vnets.auto.tfvars:
vnet_wus3_app_dev_001 = {
resource_group_key = "ohemr-rg-epic_network-dev-wus3-001"
region = "wus3"
vnet = {
name = "ohemr-vnet-app-dev-wus3-001"
address_space = ["10.1.0.0/16"]
dns_servers = ["10.0.0.10"]
}
subnets = {
app_subnet_01 = {
name = "ohemr-subnet-app-dev-wus3-001"
cidr = ["10.1.1.0/24"]
route_table_key = "rt_app-dev-wus3-001" # Reference the key from route_tables map in Step 2, not the name value
nsg_key = "empty_nsg"
}
}
}
Step 2: Create Route Table and Routes
Edit var_routes.auto.tfvars:
route_tables = {
rt_app-dev-wus3-001 = {
name = "ohemr-rt-app-dev-wus3-001"
resource_group_key = "ohemr-rg-epic_network-dev-wus3-001"
}
}
azurerm_routes = {
default_route = {
name = "default-route"
resource_group_key = "ohemr-rg-epic_network-dev-wus3-001"
route_table_key = "rt_app-dev-wus3-001"
address_prefix = "0.0.0.0/0"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = "<load_balancer_ip>"
}
}
Note: When routing private traffic through a firewall or virtual appliance (e.g., for internet egress or inspection), you must add explicit routes for RFC1918 address ranges:
10.0.0.0/8172.16.0.0/12192.168.0.0/16
These routes ensure that all private network traffic is directed through the firewall or appliance as required by security policy.
Example route configuration for RFC1918:
azurerm_routes = {
default_route = {
name = "default-route"
resource_group_key = "ohemr-rg-epic_network-dev-wus3-001"
route_table_key = "rt_app-dev-wus3-001"
address_prefix = "0.0.0.0/0"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = "<firewall_or_appliance_ip>"
}
rfc1918_10 = {
name = "rfc1918-10"
resource_group_key = "ohemr-rg-epic_network-dev-wus3-001"
route_table_key = "rt_app-dev-wus3-001"
address_prefix = "10.0.0.0/8"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = "<firewall_or_appliance_ip>"
}
rfc1918_172 = {
name = "rfc1918-172"
resource_group_key = "ohemr-rg-epic_network-dev-wus3-001"
route_table_key = "rt_app-dev-wus3-001"
address_prefix = "172.16.0.0/12"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = "<firewall_or_appliance_ip>"
}
rfc1918_192 = {
name = "rfc1918-192"
resource_group_key = "ohemr-rg-epic_network-dev-wus3-001"
route_table_key = "rt_app-dev-wus3-001"
address_prefix = "192.168.0.0/16"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = "<firewall_or_appliance_ip>"
}
}
Step 3: Create VNET Peering
Important: VNET peering must be configured in the terraform/connectivity repository, not in the VNET's home repository.
Repository: ohemr-epic-connectivity-{env}-001 (locate via GitHub organization: optum-tech-compute)
Workflow:
- Clone the appropriate connectivity repository for your environment
- Navigate to the regional connectivity configuration
- Choose the appropriate configuration file based on peering type:
- Cross-subscription peering: Edit
var_crosssubscriptionpeering.auto.tfvars - Same-subscription peering: Edit
var_peering.auto.tfvars
- Cross-subscription peering: Edit
- Add data blocks for both VNETs (hub and spoke)
- Create bidirectional peering resources (hub-to-spoke and spoke-to-hub)
Example data blocks
data "azurerm_virtual_network" "awx_pro_wus3" {
provider = azurerm.OHEMR-SUB-EPIC-SHARED-001
name = "ohemr-vnet-awx-pro-wus3-001"
resource_group_name = "ohemr-rg-epic_network-shared-wus3-001"
}
Example peering resources
resource "azurerm_virtual_network_peering" "hubconnectivity_wus3_TO_awx_pro_wus3" {
name = "hubconnectivity_wus3-TO-awx_pro_wus3"
resource_group_name = data.azurerm_virtual_network.hub_vnet.resource_group_name
virtual_network_name = data.azurerm_virtual_network.hub_vnet.name
remote_virtual_network_id = data.azurerm_virtual_network.awx_pro_wus3.id
allow_virtual_network_access = true
allow_forwarded_traffic = false
allow_gateway_transit = true
use_remote_gateways = false
}
resource "azurerm_virtual_network_peering" "awx_pro_wus3_TO_hubconnectivity_wus3" {
provider = azurerm.OHEMR-SUB-EPIC-SHARED-001
name = "awx_pro_wus3-TO-hubconnectivity_wus3"
resource_group_name = data.azurerm_virtual_network.awx_pro_wus3.resource_group_name
virtual_network_name = data.azurerm_virtual_network.awx_pro_wus3.name
remote_virtual_network_id = data.azurerm_virtual_network.hub_vnet.id
allow_virtual_network_access = true
allow_forwarded_traffic = true
allow_gateway_transit = false
use_remote_gateways = true
}
Step 4: Request Firewall Access for Active Directory Connectivity (if applicable)
Prerequisites:
- ServiceNow access to the DSI team's ticket queue
- VNET CIDR block from Step 1 (e.g.,
10.1.0.0/16) - Business justification for Active Directory connectivity
- Approval from network security team (for production environments)
Procedure:
- Open a ServiceNow ticket to the DSI (Directory Services Infrastructure) team
- Use ticket template: "Network Firewall Rule Request"
- Provide the following information:
- VNET Name:
ohemr-vnet-{app}-{env}-{region}-001 - VNET CIDR: Full address space (e.g.,
10.1.0.0/16) - Environment:
dev,tst,stg, orprd - Business Justification: Describe the Active Directory integration requirement
- Target AD Controllers: Specify which AD environment (e.g., Optum corporate AD)
- VNET Name:
- Attach Terraform plan output showing the VNET configuration
- Include network diagram if available (showing VNET topology)
Important: No changes are required on Epic on Azure firewalls. The DSI team manages firewall rules for AD connectivity.
Timeline & Escalation:
- Standard turnaround: 3-5 business days
- Expedited requests: Contact DSI team lead (requires VP approval)
- Validation: Test AD connectivity after firewall rules are implemented
Verification After Implementation:
# Test connectivity from a VM in the new VNET
nslookup <ad_domain_controller_hostname>
ping <ad_domain_controller_ip>
# Verify DNS resolution for AD domains
nslookup optum.com
Verification
After completing all configuration steps, perform the following validation procedures:
1. Terraform Plan Validation
Verify that your Terraform configuration is syntactically correct and shows expected changes:
# Log in to Terraform Enterprise
terraform login
# Initialize Terraform (if not already done)
terraform init
# Validate configuration syntax
terraform validate
# Review planned changes (read-only, does not apply)
terraform plan
# Expected output should show:
# + azurerm_virtual_network (new resource)
# + azurerm_route_table (new resource)
# + azurerm_route (4+ new resources: default + RFC1918 routes)
# + azurerm_subnet (1+ new resources)
Important: Do NOT run terraform apply locally. Infrastructure changes are applied automatically by TFE when your PR is merged.
Checklist:
- Successfully authenticated to TFE via
terraform login - No syntax errors in
terraform validate - Plan shows creation of VNET with correct CIDR
- Route table and routes are properly associated
- Subnet configuration matches requirements
- No unexpected resource deletions or modifications
Next Steps:
- Commit your changes to a feature branch
- Create a Pull Request to the main branch
- Request code review from the platform team
- Once approved and merged, TFE will automatically apply the changes via the VCS-backed workspace
2. Post-Deployment Verification
After your PR is merged and TFE completes the automatic deployment:
Note: Monitor the TFE workspace run to ensure successful deployment before proceeding with verification.
# Verify VNET exists in Azure
az network vnet show \
--name ohemr-vnet-{app}-{env}-{region}-001 \
--resource-group ohemr-rg-epic_network-{env}-{region}-001 \
--output table
# Verify route table association
az network vnet subnet show \
--vnet-name ohemr-vnet-{app}-{env}-{region}-001 \
--resource-group ohemr-rg-epic_network-{env}-{region}-001 \
--name {subnet-name} \
--query routeTable.id \
--output tsv
# List all routes in the route table
az network route-table route list \
--route-table-name ohemr-rt-{app}-{env}-{region}-001 \
--resource-group ohemr-rg-epic_network-{env}-{region}-001 \
--output table
Checklist:
- VNET exists with correct name and CIDR
- DNS servers configured correctly
- Route table associated with subnets
- All routes present (default + RFC1918)
- Next hop IP addresses correct
3. Connectivity Testing
Test network connectivity from resources deployed in the new VNET:
# Deploy a test VM in the VNET (if not already present)
# Then test connectivity:
# Test connectivity to firewall/NVA
ping <firewall_or_appliance_ip>
# Test DNS resolution
nslookup optum.com
nslookup <internal_resource>
# Verify routing to RFC1918 addresses
tracert 10.0.0.1
tracert 172.16.0.1
tracert 192.168.0.1
# Test peering connectivity (if peering configured)
ping <resource_ip_in_peered_vnet>
Checklist:
- Firewall/NVA reachable from VNET
- DNS resolution working
- RFC1918 traffic routed through firewall
- Peered VNETs accessible (if applicable)
- Active Directory connectivity working (if DSI ticket completed)
Troubleshooting
Common issues and their resolutions:
Issue 1: Terraform Apply Fails with "Resource Already Exists"
Symptoms: Error message indicating VNET or route table already exists
Diagnosis:
# Check if resource exists in Azure
az network vnet show --name <vnet-name> --resource-group <rg-name>
Resolution:
-
If resource exists and is managed elsewhere: Change your resource name/key
-
If resource exists and should be managed by this config: Import into Terraform state:
terraform import azurerm_virtual_network.vnet_wus3_app_dev_001 \ /subscriptions/<sub-id>/resourceGroups/<rg-name>/providers/Microsoft.Network/virtualNetworks/<vnet-name> -
If resource is orphaned: Delete manually and re-run
terraform apply
Issue 2: Routes Not Working (Traffic Not Reaching Destination)
Symptoms: Connectivity failures, traceroute shows incorrect path
Diagnosis:
# Verify effective routes on subnet
az network nic show-effective-route-table \
--name <nic-name> \
--resource-group <rg-name> \
--output table
# Check if route table is associated
az network vnet subnet show \
--vnet-name <vnet-name> \
--name <subnet-name> \
--resource-group <rg-name> \
--query routeTable
Common Causes:
- Route table not associated with subnet
- Incorrect next hop IP address
- Firewall/NVA not configured to accept traffic
- Network Security Group (NSG) blocking traffic
Resolution:
- Verify route table association in Terraform configuration
- Confirm next hop IP addresses are correct and reachable
- Check firewall/NVA configuration and routing tables
- Review NSG rules for the subnet
Issue 3: VNET Peering Not Working
Symptoms: Cannot reach resources in peered VNET
Diagnosis:
# Check peering status
az network vnet peering list \
--vnet-name <vnet-name> \
--resource-group <rg-name> \
--output table
# Verify peering state is "Connected"
# Check if both directions are configured
Common Causes:
- Peering only configured in one direction
- Incorrect
use_remote_gatewaysorallow_gateway_transitsettings - Overlapping CIDR blocks
- Peering configured in wrong repository (must be in connectivity repo)
Resolution:
- Verify bidirectional peering (hub-to-spoke AND spoke-to-hub)
- Check gateway transit settings:
- Hub:
allow_gateway_transit = true - Spoke:
use_remote_gateways = true
- Hub:
- Confirm no CIDR overlap between VNETs
- Ensure peering is in
terraform/connectivityrepository
Issue 4: DNS Resolution Failures
Symptoms: Cannot resolve internal hostnames
Diagnosis:
# Check configured DNS servers
az network vnet show \
--name <vnet-name> \
--resource-group <rg-name> \
--query dhcpOptions.dnsServers
# Test DNS resolution from VM
nslookup <internal-hostname>
nslookup <external-hostname>
Resolution:
- Verify
dns_serversin VNET configuration points to correct DNS resolver - Common DNS IPs:
- Azure-provided DNS:
168.63.129.16 - Custom DNS resolver: (verify with network team)
- Azure-provided DNS:
- Ensure DNS resolver is reachable from VNET
- Check DNS resolver configuration and forwarding rules
Rollback Procedure
If you need to remove the VNET and start over:
Important: Deletions must follow the same TFE workflow as creation. Do NOT run terraform destroy locally.
Procedure:
-
Local validation:
# Review what will be destroyed terraform login terraform plan -destroy -
Create rollback PR:
- Remove or comment out the VNET configuration from
.tfvarsfiles - Commit changes to a feature branch
- Create a Pull Request titled "chore: remove VNET {name}"
- Include justification and approval from stakeholders
- Remove or comment out the VNET configuration from
-
PR approval and merge:
- Request review from platform team
- Obtain approval from infrastructure lead
- Merge PR to trigger automatic TFE destruction
-
Verify removal:
# After TFE run completes az network vnet show --name <vnet-name> --resource-group <rg-name> # Should return: ERROR: Resource not found
Before destroying:
- Verify no workloads are running in the VNET
- Remove any VNET peerings first (separate PR in connectivity repo)
- Document any manual changes made outside Terraform
- Obtain stakeholder approval for deletion
- Notify teams that will be affected by VNET removal
After rollback:
- Review what went wrong and update configuration
- Validate changes with
terraform planbefore creating new PR - Consider testing in dev environment first