Infrastructure as Code (IaC) is the practice of managing and provisioning computing infrastructure through machine-readable definition files rather than physical hardware configuration or interactive configuration tools. In this post, we will combine Terraform for infrastructure provisioning and Ansible for post-deployment configuration.
This article explores the repository structure, architectural design, and how to leverage it for building enterprise-grade Azure environments with full automation.
The iachubv2preprod repository (https://github.com/MourIdri/iachubv2preprod) provides a complete automation framework for deploying a Hub and Spoke network topology on Azure with integrated security, monitoring, and management capabilities.
Repository Composition:
✓ Complete Hub and Spoke architecture automation ✓ Enterprise-grade security with NSGs and firewalls ✓ Native Azure VPN site-to-site connectivity ✓ Linux-based NVA for advanced routing (iptables) ✓ Nginx WAF for web protection ✓ Comprehensive logging and monitoring ✓ Azure DevOps pipeline integration ready ✓ Modular Terraform design for reusability

The topology implements a centralized hub infrastructure with spoke environments, providing:
terraform-iac/
├── main.tf # Main module orchestration
├── variables.tf # Variable definitions
├── variables.tfvars # Variable values
├── provider.tf # Azure provider config
├── output.tf # Output values
├── tags.json # Resource tagging
└── modules/
├── rg/ # Resource Group
├── network/ # VNet and VNet config
├── logging/ # Storage and Analytics
├── generic-subnet-nsg/ # Subnet + NSG templates
├── vm-win-pub/ # Windows public VM
├── vm-win-pri/ # Windows private VM
├── vm-lnx-pri/ # Linux private VM
├── security-appliance-lnx/# NVA Linux appliance
├── waf-lnx/ # WAF/Nginx Linux
└── vpn-onprem-cloud/ # VPN connectivity
module "rg" {
source = "./modules/rg"
current-name-convention-core-module = var.current-name-convention-core-main
preferred-location-module = var.preferred-location-main
tags-rg-module = {
environment = "production"
scope_1 = "shared_infrastructure"
scope_2 = "core_infrastructure"
type_1 = "network_security"
type_2 = "network"
}
}
Core virtual network with subnet configuration:
module "network" {
source = "./modules/network"
current-name-convention-core-public-module = var.current-name-convention-core-public-main
current-name-convention-core-module = var.current-name-convention-core-main
preferred-location-module = var.preferred-location-main
ip-range-module = var.current-vnet-space # e.g., 10.255.254.0/23
}
Centralized logging infrastructure:
module "logging" {
source = "./modules/logging"
current-name-convention-core-public-module = var.current-name-convention-core-public-main
current-name-convention-core-module = var.current-name-convention-core-main
preferred-location-module = var.preferred-location-main
# Outputs: Storage account, Log Analytics workspace, Event Hub
}
The NVA is a dual-NIC Linux VM acting as router and firewall:
module "security-appliance-dmz" {
source = "./modules/security-appliance-lnx"
subnet_in_id_module = module.subnet-nsg-privatedmzin.subnet-iac-id
subnet_out_id_module = module.subnet-nsg-privatedmzoutlan.subnet-iac-id
ip-in-nva-module = var.security-appliance-dmz-private-ip-address-in # 10.255.255.36
ip-out-nva-module = var.security-appliance-dmz-private-ip-address-out # 10.255.255.52
nva-size = var.vmsize_small_1_2 # Standard_F1s
tags-security-appliance-dmz-module = {
type_1 = "network_security"
type_2 = "router"
}
}
NVA Capabilities:
Windows Domain Controller for identity and DNS:
module "mt-area-mgmt" {
source = "./modules/vm-win-pri"
subnet_in_id_module = module.subnet-nsg-privatedmzoutlan.subnet-iac-id
ip-in-dcaddns-module = var.mt-area-dc-dns-private-ip-address # 10.255.255.53
dcaddns-size = var.vmsize_small_1_2
dcaddns-login = var.current-vm-default-username-main
dcaddns-passwd = var.current-vm-default-pass-main
tags = {
type_1 = "domain_identity"
type_2 = "ad_dc_dns"
}
}
Plus admin management Linux VM for operations:
module "mtl-area-1" {
source = "./modules/vm-lnx-pri"
subnet_in_id_module = module.subnet-nsg-mt.subnet-iac-id
ip-in-mtl-module = var.mtl-vm-private-ip-address # 10.255.255.21
tags = {
type_2 = "adminlnx"
}
}
Web Application Firewall using Nginx:
module "waf-public-dmz" {
source = "./modules/waf-lnx"
subnet_in_id_module = module.subnet-nsg-publicdmzin.subnet-iac-id
ip-in-waf-module = var.waf-vm-private-public-ip-address # 10.255.254.68
waf-size = var.vmsize_small_1_2
tags = {
type_2 = "waf"
}
}
Site-to-site VPN to on-premises:
module "vpn-standard-connect" {
source = "./modules/vpn-onprem-cloud"
iprange-onprem-module = [var.iprange-onprem-vpn] # e.g., ["192.168.0.0/16"]
ipaddress-routeur-onprem-1-module = var.ipaddress-routeur-onprem-1-azuredevops
subnet-vpn-target-id-module = module.subnet-nsg-vpn.subnet-iac-id
corph-s2s-connection-pass = var.current-vm-default-pass-main
}
Key variables from variables.tfvars:
# Network Configuration
current-vnet-space = "10.255.254.0/23"
preferred-location-main = "westeurope"
second-location-main = "northeurope"
# Naming Convention
current-name-convention-core-main = "crpc-prod-shar-hub"
current-name-convention-core-public-main = "crcprdshrhubstolog"
# Subnet Definitions
subnet-privatedmzin = "10.255.254.32/28" # Private DMZ Inbound
subnet-privatedmzoutlan = "10.255.254.48/28" # Private DMZ Outbound
subnet-mt = "10.255.255.16/28" # Management
subnet-publicdmzin = "10.255.255.64/28" # Public DMZ
subnet-GatewaySubnet = "10.255.254.0/28" # VPN Gateway
# VM Credentials
current-vm-default-username-main = "demouser"
current-vm-default-pass-main = "M@nP@ssw@rd!"
# VPN Configuration
iprange-onprem-vpn = "192.168.0.0/16"
ipaddress-routeur-onprem-1-azuredevops = "40.89.184.82"
# VM Sizes
vmsize_small_1_2 = "Standard_F1s"
vmsize_medium_2_4 = "Standard_F2s_v2"
vmsize_high_4_8 = "Standard_F4s_v2"
After Terraform provisions the infrastructure, Ansible handles configuration management and software deployment:
ansible-postconf/
├── ansible/ # Core Ansible playbooks and roles
├── nginx/ # Nginx/WAF configuration playbooks
└── letsencrypt/ # SSL/TLS certificate automation
Ansible playbooks handle:
Nginx post-configuration includes:
Automated certificate management:
# 1. Clone the repository
git clone https://github.com/MourIdri/iachubv2preprod.git
cd iachubv2preprod/terraform-iac
# 2. Initialize Terraform
terraform init -backend-config="key=hub.tfstate"
# 3. Plan infrastructure deployment
terraform plan -var-file="variables.tfvars" -out=plan.tfplan
# 4. Apply Terraform configuration
terraform apply plan.tfplan
# 5. Wait for VMs to be ready (~10-15 minutes)
# 6. Configure Ansible inventory with new VM IPs
# 7. Execute Ansible playbooks for post-configuration
cd ../ansible-postconf
ansible-playbook -i inventory.ini site.yml
# 8. Deploy Nginx configuration
ansible-playbook -i inventory.ini nginx/main.yml
# 9. Deploy SSL certificates
ansible-playbook -i inventory.ini letsencrypt/main.yml
This repository is designed to integrate with Azure DevOps:
trigger:
- master
pool:
vmImage: 'ubuntu-latest'
steps:
- task: TerraformInstaller@0
inputs:
terraformVersion: '0.13.0'
- task: TerraformTaskV1@0
displayName: 'Terraform Init'
inputs:
provider: 'azurerm'
command: 'init'
- task: TerraformTaskV1@0
displayName: 'Terraform Plan'
inputs:
provider: 'azurerm'
command: 'plan'
- task: TerraformTaskV1@0
displayName: 'Terraform Apply'
inputs:
provider: 'azurerm'
command: 'apply'
- task: AnsiblePlaybook@0
displayName: 'Run Ansible Post-Configuration'
inputs:
ansibleVersion: 'latest'
playbookPathOnAgentMachine: 'ansible-postconf/site.yml'
Each component is isolated in its own Terraform module, enabling:
The solution uses a Linux VM with iptables for routing instead of Azure's managed solutions:
Advantages:
Trade-offs:
All components stream logs to:
Native Azure VPN Gateway provides:
✓ Azure subscription with appropriate permissions ✓ Terraform installed (v0.12+) ✓ Azure CLI configured with credentials ✓ Ansible installed for post-configuration ✓ SSH keys configured for Linux VM access
Before deployment, customize:
To add additional spokes:
module "spoke-prod-vnet" {
source = "./modules/network"
ip-range-module = "10.1.0.0/16"
# Additional configuration...
}
module "spoke-prod-peering" {
source = "./modules/vnet-peering"
hub_vnet_id = module.network.vnet_id
spoke_vnet_id = module.spoke-prod-vnet.vnet_id
}
Post-deployment with Ansible:
- name: Deploy custom application
hosts: app_servers
roles:
- common
- monitoring
- your-app-name
The iachubv2preprod repository demonstrates enterprise-grade Infrastructure as Code practices combining Terraform's declarative infrastructure provisioning with Ansible's configuration management capabilities. This complete automation framework enables rapid, repeatable, and reliable Azure deployments while maintaining security, monitoring, and operational best practices.
By leveraging this repository as a foundation, organizations can:
Repository: https://github.com/MourIdri/iachubv2preprod License: MIT (or as specified by repository owner)
This Infrastructure as Code approach represents the future of cloud operations, where infrastructure is version-controlled, tested, and deployed like application code.