+++
title = "Chapter 8: Infrastructure as Code: Terraform for Cloud and On-Prem VLANs"
topic = "devops"
date = 2026-01-24
draft = false
weight = 8
description = "Explore Infrastructure as Code (IaC) for VLAN management using Terraform across cloud environments (AWS, Azure) and on-premises networks. This chapter covers declarative configuration, state management, multi-vendor automation strategies, security best practices, and advanced troubleshooting for modern network infrastructures."
slug = "terraform-vlan-iac"
keywords = ["Terraform", "Infrastructure as Code", "IaC", "VLAN", "AWS VLAN", "Azure VLAN", "On-Prem VLAN", "Network Automation", "Cloud Networking", "Hybrid Cloud", "NetDevOps", "802.1Q", "802.1ad"]
tags = ["Networking", "VLAN", "IaC", "Terraform", "Cloud", "Automation", "NetDevOps"]
categories = ["Networking"]
+++

Introduction

In the rapidly evolving landscape of network engineering, manual configuration of Virtual Local Area Networks (VLANs) across diverse environments — from traditional on-premises data centers to dynamic cloud platforms — is becoming increasingly unsustainable. This chapter introduces Infrastructure as Code (IaC) principles, specifically focusing on Terraform, as the cornerstone for modern, automated VLAN management.

We will explore how Terraform enables declarative configuration of network segmentation, whether it’s provisioning Virtual Private Clouds (VPCs) and subnets in AWS or Azure, or orchestrating VLANs on multi-vendor on-premises switches. By treating network infrastructure as code, engineers can achieve unparalleled consistency, version control, auditability, and speed in deployments.

What this chapter covers:

  • Core principles of Infrastructure as Code and its application to VLANs.
  • Terraform’s architecture and its role in provisioning network resources.
  • Configuring cloud-native network segmentation (VPC, VNet, subnets) using Terraform.
  • Strategies for managing on-premises VLANs with Terraform, including integration with configuration management tools like Ansible.
  • Security considerations, verification, troubleshooting, and performance optimization in an IaC context.
  • Practical hands-on lab exercises to solidify your understanding.

Why it’s important:

Automating VLAN deployment and management with Terraform dramatically reduces human error, accelerates deployment cycles, and ensures configurations are always aligned with desired state, which is critical for compliance and operational efficiency in large-scale enterprise networks. This approach embodies NetDevOps principles, bringing software development best practices to network operations.

What you’ll be able to do after:

Upon completing this chapter, you will be able to design, implement, and manage VLANs and network segmentation across hybrid cloud environments using Terraform, troubleshoot common IaC and VLAN-related issues, and apply best practices for secure and performant network automation.


Technical Concepts

8.1. Infrastructure as Code (IaC) Principles

Infrastructure as Code is the management of infrastructure (networks, virtual machines, load balancers, etc.) in a descriptive model, using the same versioning and deployment practices as software code. Key principles include:

  • Declarative vs. Imperative:
    • Declarative (Terraform): You describe the desired end state of your infrastructure. Terraform figures out the steps to get there.
    • Imperative (Traditional CLI, Ansible scripts): You define the sequence of commands to execute to achieve a state.
  • Idempotency: Applying the same configuration multiple times yields the same result without unintended side effects. Terraform is inherently idempotent.
  • Version Control: Infrastructure definitions are stored in Git, allowing for change tracking, collaboration, and easy rollback.
  • Automation: Automates the provisioning, updating, and de-provisioning of infrastructure, reducing manual effort and errors.
  • Immutability: Resources are often replaced rather than modified in-place, promoting consistency and easier rollbacks.

IaC Workflow with Terraform

digraph IaC_Workflow {
    rankdir=LR;
    node [shape=box, style="rounded,filled", fillcolor="#F0F4FF", fontname="Arial"];
    edge [color="#555555", arrowsize=0.8];

    User [label="Network Engineer"];
    CodeEditor [label="Terraform HCL\n(VLAN Definitions)"];
    GitRepository [label="Git Repository"];
    CI_CD [label="CI/CD Pipeline\n(e.g., GitLab CI, GitHub Actions)"];
    TerraformCLI [label="Terraform CLI"];
    TerraformState [label="Terraform State File\n(Remote Backend)"];
    CloudProvider [label="Cloud Provider APIs\n(AWS, Azure)"];
    OnPremAPI_CLI [label="On-Prem APIs/\nNetwork Device CLIs"];
    NetworkDevices [label="Network Devices\n(Switches, Routers)"];

    User -> CodeEditor [label="Writes Code"];
    CodeEditor -> GitRepository [label="Commits Code"];
    GitRepository -> CI_CD [label="Triggers Pipeline"];
    CI_CD -> TerraformCLI [label="Executes Terraform Commands"];
    TerraformCLI -> TerraformState [label="Reads/Writes State"];
    TerraformCLI -> CloudProvider [label="Manages Cloud Resources"];
    TerraformCLI -> OnPremAPI_CLI [label="Manages On-Prem Resources\n(via Providers/Local-Exec)"];
    CloudProvider -> NetworkDevices [label="Provisions Cloud Network\n(VPCs, Subnets)"];
    OnPremAPI_CLI -> NetworkDevices [label="Configures On-Prem VLANs"];
}

8.2. Terraform Fundamentals for Network Engineering

Terraform, developed by HashiCorp, is an open-source IaC tool used to provision and manage cloud infrastructure. It uses a declarative configuration language called HashiCorp Configuration Language (HCL).

  • Providers: Plugins that Terraform uses to interact with various cloud providers (AWS, Azure, GCP) and on-premises systems (vSphere, Kubernetes, network devices if specific providers exist, or via generic HTTP/Ansible integrations).
  • Resources: The fundamental building blocks of infrastructure. Each resource block describes one or more infrastructure objects, like a VPC, a subnet, a virtual machine, or a VLAN.
  • Data Sources: Allow Terraform to fetch information about existing infrastructure resources, which can then be used to configure other resources.
  • Modules: Reusable, encapsulated sets of Terraform configurations that create multiple resources. They promote modularity and reduce code duplication.
  • State File: Terraform stores the state of your managed infrastructure in a state file (terraform.tfstate). This file maps the real-world resources to your configuration, tracks metadata, and improves performance for large infrastructures. The state file is highly sensitive and must be stored securely (e.g., S3, Azure Blob Storage) and encrypted.

8.3. VLANs in a Declarative IaC Context

Traditional VLANs (IEEE 802.1Q, IEEE 802.1ad for QinQ) provide Layer 2 segmentation. In an IaC context, the goal is to define these segments and their properties (ID, name, associated interfaces) declaratively.

IEEE 802.1Q and 802.1ad (QinQ) Overview

  • IEEE 802.1Q (VLAN Tagging): The standard for VLAN trunking, adding a 4-byte tag to Ethernet frames to identify the VLAN. This allows multiple VLANs to share a single physical link (trunk).
    • Tag Structure: Type (0x8100), Priority Code Point (PCP), Drop Eligible Indicator (DEI), VLAN ID (VID).
  • IEEE 802.1ad (Provider Bridges / QinQ): An amendment to 802.1Q that allows for “stacked” VLAN tags, essentially encapsulating customer VLANs within a service provider’s VLAN. This is often called QinQ. The outer tag is a Service VLAN (S-VLAN), and the inner tag is a Customer VLAN (C-VLAN).
    • Tag Structure: Uses EtherType 0x88a8 for the outer tag.

When using Terraform, you won’t typically define the bits of the 802.1Q tag, but rather the logical configuration parameters like VLAN ID, name, interface assignments (access or trunk), and native VLANs. The underlying network devices then implement the 802.1Q/ad standards based on your declarative input.

8.4. Cloud-Native Network Segmentation (VPC/VNet)

In cloud environments, the concept of a “VLAN” is often abstracted into constructs like Virtual Private Clouds (AWS VPC) or Virtual Networks (Azure VNet). These are logically isolated networks within the cloud provider’s infrastructure. Subnets within these VPCs/VNets further segment the network, analogous to how traditional VLANs segment a physical network.

Cloud Network Architecture (AWS/Azure)

@startuml
!theme mars

' Define all elements first
cloud "Internet" as INTERNET
rectangle "AWS Region (us-east-1)" as AWS_REGION {
    component "AWS VPC (10.0.0.0/16)" as AWS_VPC {
        rectangle "Public Subnet 1 (10.0.1.0/24)" as PUB_SUBNET_1
        rectangle "Public Subnet 2 (10.0.2.0/24)" as PUB_SUBNET_2
        rectangle "Private Subnet 1 (10.0.10.0/24)" as PRIV_SUBNET_1
        rectangle "Private Subnet 2 (10.0.11.0/24)" as PRIV_SUBNET_2

        node "Internet Gateway" as IGW
        node "NAT Gateway" as NAT_GW
        node "Route Tables" as RT
        node "Security Groups" as SG
    }
}

rectangle "Azure Region (East US 2)" as AZURE_REGION {
    component "Azure VNet (172.16.0.0/16)" as AZURE_VNET {
        rectangle "Frontend Subnet (172.16.1.0/24)" as FE_SUBNET
        rectangle "Backend Subnet (172.16.10.0/24)" as BE_SUBNET
        
        node "Public IP Gateway" as PIP_GW
        node "Network Security Groups" as NSG
        node "Route Tables (UDRs)" as UDR
    }
}

' Then connect them
INTERNET [label="> IGW
IGW"] AWS_VPC

AWS_VPC [label="> PUB_SUBNET_1
AWS_VPC"] PUB_SUBNET_2
AWS_VPC [label="> PRIV_SUBNET_1
AWS_VPC"] PRIV_SUBNET_2

PUB_SUBNET_1 [label="> RT
PUB_SUBNET_2"] RT
PRIV_SUBNET_1 [label="> RT
PRIV_SUBNET_2"] RT

PRIV_SUBNET_1 [label="> NAT_GW
NAT_GW"] IGW

INTERNET [label="> PIP_GW
PIP_GW"] AZURE_VNET

AZURE_VNET [label="> FE_SUBNET
AZURE_VNET"] BE_SUBNET

FE_SUBNET [label="> NSG
BE_SUBNET"] NSG
FE_SUBNET [label="> UDR
BE_SUBNET"] UDR

@enduml

8.5. Hybrid Cloud Networking with IaC

IaC is critical for managing the complexity of hybrid cloud environments, where on-premises networks need to seamlessly integrate with cloud networks. This often involves:

  • VPN/Direct Connect/ExpressRoute: Establishing secure, high-bandwidth connections between on-prem and cloud. Terraform can provision the cloud-side VPN gateways or direct connect interfaces.
  • IP Address Management (IPAM): Ensuring non-overlapping IP spaces and efficient allocation across both environments.
  • Routing: Configuring routes to ensure traffic flows correctly between on-prem VLANs and cloud subnets.
internet: Internet {
  shape: cloud
}

cloud_aws: AWS Cloud {
  shape: rectangle
  aws_vpc: "AWS VPC (10.0.0.0/16)" {
    shape: cylinder
    public_subnet: "Public Subnet (10.0.1.0/24)"
    private_subnet: "Private Subnet (10.0.10.0/24)"
  }
  aws_vpn_gw: "AWS VPN Gateway" { shape: hexagon }
}

on_prem: On-Premise Datacenter {
  shape: rectangle
  core_router: "Core Router"
  core_switch: "Core Switch" {
    vlan_10: "VLAN 10 (192.168.10.0/24)"
    vlan_20: "VLAN 20 (192.168.20.0/24)"
  }
  on_prem_vpn_gw: "On-Prem VPN Gateway" { shape: hexagon }
}

internet -> aws_vpn_gw
internet -> on_prem_vpn_gw

aws_vpn_gw <-> on_prem_vpn_gw: VPN Tunnel

aws_vpc.public_subnet <-> aws_vpn_gw
aws_vpc.private_subnet <-> aws_vpn_gw

core_router <-> core_switch
core_router <-> on_prem_vpn_gw

core_switch -> vlan_10
core_switch -> vlan_20

# Terraform manages:
#   - AWS VPC, Subnets, VPN Gateway
#   - Could potentially configure on-prem VPN Gateway (if API supported)
#   - Could trigger Ansible to configure on-prem VLANs
terraform: "Terraform/Ansible Control Plane" {
  shape: flow_ellipse
}

terraform -> cloud_aws: "Provisions Cloud Network"
terraform -> on_prem: "Automates On-Prem Configuration"

Configuration Examples (Terraform)

This section demonstrates how to use Terraform to provision cloud network resources and manage on-premises VLANs.

8.6. Cloud VLANs (AWS VPC and Subnets)

This example provisions an AWS VPC with multiple subnets, effectively segmenting your cloud network in a “VLAN-like” manner.

# main.tf for AWS VPC and Subnets

# Configure the AWS provider
provider "aws" {
  region = "us-east-1"
}

# Define a variable for the VPC CIDR block
variable "vpc_cidr_block" {
  description = "CIDR block for the VPC"
  type        = string
  default     = "10.0.0.0/16"
}

# Define a variable for subnet CIDR blocks
variable "public_subnet_cidrs" {
  description = "List of CIDR blocks for public subnets"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "private_subnet_cidrs" {
  description = "List of CIDR blocks for private subnets"
  type        = list(string)
  default     = ["10.0.10.0/24", "10.0.11.0/24"]
}

# Create a VPC
resource "aws_vpc" "main_vpc" {
  cidr_block           = var.vpc_cidr_block
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name        = "ProductionVPC"
    Environment = "Prod"
    ManagedBy   = "Terraform"
  }
}

# Create Internet Gateway
resource "aws_internet_gateway" "main_igw" {
  vpc_id = aws_vpc.main_vpc.id
  tags = {
    Name = "ProductionIGW"
  }
}

# Create public subnets
resource "aws_subnet" "public_subnets" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.main_vpc.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = data.aws_availability_zones.available.names[count.index] # Distribute across AZs
  map_public_ip_on_launch = true # Instances in public subnets get public IPs
  tags = {
    Name        = "PublicSubnet-${count.index + 1}"
    Environment = "Prod"
    Tier        = "Public"
  }
}

# Create private subnets
resource "aws_subnet" "private_subnets" {
  count             = length(var.private_subnet_cidrs)
  vpc_id            = aws_vpc.main_vpc.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name        = "PrivateSubnet-${count.index + 1}"
    Environment = "Prod"
    Tier        = "Private"
  }
}

# Data source to get available AZs
data "aws_availability_zones" "available" {
  state = "available"
}

# Create Route Tables for public subnets
resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.main_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main_igw.id
  }
  tags = {
    Name = "PublicRouteTable"
  }
}

# Associate public subnets with the public route table
resource "aws_route_table_association" "public_rt_associations" {
  count          = length(aws_subnet.public_subnets)
  subnet_id      = aws_subnet.public_subnets[count.index].id
  route_table_id = aws_route_table.public_rt.id
}

# Output VPC and Subnet IDs
output "vpc_id" {
  description = "The ID of the VPC"
  value       = aws_vpc.main_vpc.id
}

output "public_subnet_ids" {
  description = "List of IDs of the public subnets"
  value       = aws_subnet.public_subnets[*].id
}

output "private_subnet_ids" {
  description = "List of IDs of the private subnets"
  value       = aws_subnet.private_subnets[*].id
}

Verification Commands (AWS CLI):

aws ec2 describe-vpcs --filters "Name=tag:Name,Values=ProductionVPC" --query "Vpcs[*].VpcId" --output text
aws ec2 describe-subnets --filters "Name=tag:Name,Values=PublicSubnet-*" --query "Subnets[*].{ID:SubnetId,CIDR:CidrBlock,AZ:AvailabilityZone}" --output table
aws ec2 describe-subnets --filters "Name=tag:Name,Values=PrivateSubnet-*" --query "Subnets[*].{ID:SubnetId,CIDR:CidrBlock,AZ:AvailabilityZone}" --output table

Expected Output (example):

vpc-0abcdef1234567890

----------------------------------------------------
|               DescribeSubnets                    |
+--------------------------+-----------------+------+
|           AZ             |      CIDR       |  ID  |
+--------------------------+-----------------+------+
| us-east-1a               | 10.0.1.0/24     | subnet-0fedcba9876543210 |
| us-east-1b               | 10.0.2.0/24     | subnet-0123456789abcdef |
+--------------------------+-----------------+------+

----------------------------------------------------
|               DescribeSubnets                    |
+--------------------------+-----------------+------+
|           AZ             |      CIDR       |  ID  |
+--------------------------+-----------------+------+
| us-east-1a               | 10.0.10.0/24    | subnet-0987654321fedcba |
| us-east-1b               | 10.0.11.0/24    | subnet-0fedcba123456789 |
+--------------------------+-----------------+------+

8.7. On-Prem VLANs with Terraform (via Ansible Integration)

Direct Terraform providers for specific network device CLIs are less common for generic VLAN management compared to cloud providers. A robust approach for on-premises is to use Terraform to orchestrate higher-level infrastructure (e.g., VMs for network controllers, physical server deployments) and then invoke a configuration management tool like Ansible to configure granular device settings like VLANs.

Here, Terraform uses a local-exec provisioner to trigger an Ansible playbook.

1. Terraform Configuration (main.tf):

# main.tf for On-Prem VLAN Orchestration

# Configure the local provider (for local-exec)
provider "local" {}

# Define variables for VLANs
variable "vlans_to_create" {
  description = "A map of VLAN IDs to VLAN names to create on network devices."
  type = map(string)
  default = {
    "10" = "Users_VLAN",
    "20" = "Servers_VLAN",
    "30" = "Voice_VLAN"
  }
}

# Define network device inventory (could come from other Terraform resources or data sources)
variable "network_devices" {
  description = "List of network device IPs to configure"
  type        = list(string)
  default     = ["192.168.1.10", "192.168.1.11"] # Example device IPs
}

# Null resource to trigger Ansible playbook
resource "null_resource" "configure_vlans_on_devices" {
  # This makes Terraform re-run the provisioner if the vlans_to_create variable changes
  triggers = {
    vlans_config_hash = jsonencode(var.vlans_to_create)
    devices_hash      = jsonencode(var.network_devices)
  }

  provisioner "local-exec" {
    command = <<-EOT
      ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook \
        -i "${path.module}/inventory.ini" \
        "${path.module}/configure_vlans.yaml" \
        --extra-vars '{"target_devices": ${jsonencode(var.network_devices)}, "vlans_data": ${jsonencode(var.vlans_to_create)}}'
    EOT
    # Working directory is the module root where inventory.ini and configure_vlans.yaml reside
    working_dir = path.module
  }
}

# Output confirmation
output "on_prem_vlan_status" {
  description = "Status of on-prem VLAN configuration via Ansible"
  value       = "VLAN configuration playbook triggered for devices: ${join(", ", var.network_devices)}"
}

2. Ansible Inventory (inventory.ini):

This static inventory defines groups for different device types. You might dynamically generate this or fetch it from a source of truth.

# inventory.ini
[cisco_switches]
192.168.1.10
192.168.1.11

[all:vars]
ansible_user=admin
ansible_password=MySecurePassword!
ansible_network_os=ios

SECURITY WARNING: Storing plaintext passwords in inventory is highly discouraged for production. Use Ansible Vault or environment variables for sensitive data.

3. Ansible Playbook (configure_vlans.yaml):

This playbook will iterate through the vlans_data passed from Terraform and configure them on the target_devices.

# configure_vlans.yaml
---
- name: Configure VLANs on Network Devices
  hosts: all
  gather_facts: false
  connection: network_cli

  vars:
    ansible_network_os: "cisco.ios.ios" # Or juniper.junos, arista.eos based on devices
    ansible_connection: network_cli
    ansible_become: true
    ansible_become_method: enable # For privilege escalation

  tasks:
    - name: Ensure VLANs exist and have correct names
      ansible.netcommon.cli_config:
        config: |
          vlan 
          name 
        match: strict
        diff_against: running
      loop: ""
      loop_control:
        label: "VLAN "

    - name: Assign interfaces to VLANs (Example: Access Port)
      ansible.netcommon.cli_config:
        config: |
          interface GigabitEthernet0/
          switchport mode access
          switchport access vlan 
          no shutdown
        match: strict
        diff_against: running
      when: item.value.port is defined
      loop: ""
      loop_control:
        label: "Interface Gig0/ for VLAN "
      # NOTE: This is a simplified example. Real-world scenarios require dynamic port mapping.
      # For example: vlans_to_create: {"10": {"name": "Users_VLAN", "ports": ["1", "2"]}, ...}
      # You would then loop through ports for each VLAN.

    - name: Configure trunk interfaces (Example: Between switches)
      ansible.netcommon.cli_config:
        config: |
          interface GigabitEthernet0/24
          switchport trunk encapsulation dot1q
          switchport mode trunk
          switchport trunk allowed vlan 
          no shutdown
        match: strict
        diff_against: running
      when: inventory_hostname == '192.168.1.10' # Apply to specific device
      # WARNING: This assumes a fixed trunk port for simplicity. In production, use more dynamic logic.

    - name: Save configuration
      ansible.netcommon.cli_config:
        command: "write memory" # For Cisco IOS, adjust for other vendors
      changed_when: false # This command doesn't change configuration itself, just saves
      when: ansible_network_os == "cisco.ios.ios"

Verification Commands (Cisco IOS Example):

show vlan brief
show interfaces status
show interfaces trunk

Expected Output (Cisco IOS Example):

! (from show vlan brief)
VLAN Name                             Status    Ports
---- -------------------------------- --------- -------------------------------
1    default                          active    Gi0/1, Gi0/3-23
10   Users_VLAN                       active    Gi0/0
20   Servers_VLAN                     active    Gi0/4
30   Voice_VLAN                       active    
! (from show interfaces trunk)
Port        Mode             Encapsulation  Status        Native VLAN
Gi0/24      on               802.1q         trunking      1
Port        Vlans allowed on trunk
Gi0/24      10,20,30

Network Diagrams

8.8. Terraform IaC for Hybrid VLAN Deployment

This diagram illustrates the comprehensive flow of using Terraform to manage network segmentation across both cloud and on-premises environments, incorporating configuration management for physical devices.

@startuml
!theme cerulean

' Define all elements first
actor "Network Engineer" as Engineer
component "Terraform CLI" as TF_CLI
cloud "Terraform Cloud/Remote State" as TF_STATE {
    folder "State Files" as StateFiles
}

rectangle "Cloud Provider (AWS)" as AWS_CLOUD {
    component "AWS Provider API" as AWS_API
    rectangle "AWS VPC" as VPC {
        rectangle "Public Subnet (VLAN-like)" as PublicSubnet
        rectangle "Private Subnet (VLAN-like)" as PrivateSubnet
    }
}

rectangle "On-Premises Data Center" as ON_PREM {
    component "Ansible Control Host" as AnsibleHost
    node "Core Switch 1 (Cisco)" as SW1
    node "Access Switch 2 (Juniper)" as SW2
}

' Connections
Engineer --> TF_CLI : "Writes HCL"
TF_CLI <-> TF_STATE : "Manages State"
TF_CLI [label="> AWS_API : "Provision Cloud Resources"
AWS_API"] VPC : "Creates VPC/Subnets"
VPC [label="> PublicSubnet
VPC"] PrivateSubnet

TF_CLI --> AnsibleHost : "Triggers Ansible Playbook (via local-exec)"
AnsibleHost [label="> SW1 : "Configures VLANs (CLI over SSH)"
AnsibleHost"] SW2 : "Configures VLANs (CLI over SSH)"

SW1 <--> SW2 : "Trunk Links"

PrivateSubnet -[hidden]right-> AnsibleHost : "Logical segregation, but could connect via VPN"
@enduml

8.9. Cloud-Native VLAN Segmentation (AWS Example)

This nwdiag diagram visually represents a segmented AWS VPC with public and private subnets acting as logical VLANs.

nwdiag {
  network "Internet Gateway" {
    address = "0.0.0.0/0"
  }
  network "AWS VPC (10.0.0.0/16)" {
    address = "10.0.0.0/16"
    color = "#F0F8FF";

    internet_gateway [address = "igw-xxxxxxxxxxxx"];

    network "Public Subnet 1 (Web Tier)" {
      address = "10.0.1.0/24"
      color = "#E6F3F7";
      web_server_01 [address = "10.0.1.10"];
      web_server_02 [address = "10.0.1.11"];
      load_balancer [address = "10.0.1.5"];
      web_server_01 -- internet_gateway; # Direct access for public instances
    }

    network "Private Subnet 1 (App Tier)" {
      address = "10.0.10.0/24"
      color = "#FFF0F5";
      app_server_01 [address = "10.0.10.10"];
      app_server_02 [address = "10.0.10.11"];
    }

    network "Private Subnet 2 (DB Tier)" {
      address = "10.0.20.0/24"
      color = "#F5FFFA";
      db_server_01 [address = "10.0.20.10"];
      db_server_02 [address = "10.0.20.11"];
    }

    # Connections between logical segments within the VPC
    load_balancer -- app_server_01;
    app_server_01 -- db_server_01;
    # NAT Gateway for outbound internet from private subnets (not explicitly drawn as a device here, but implied)
    # Traffic from private subnets would route via NAT GW to Internet Gateway
  }
}

8.10. On-Premises Multi-VLAN Topology

This nwdiag diagram shows a simplified on-premises network with multiple VLANs configured across switches, managed by an IaC approach.

nwdiag {
  network "Management Network (VLAN 100)" {
    address = "192.168.100.0/24"
    color = "#F8F8FF";
    ansible_control [address = "192.168.100.50", description = "Ansible/Terraform Control"];
  }

  network "Core Switch (Cisco)" {
    address = "192.168.1.0/24" # Default management VLAN interface
    color = "#E0FFFF";
    core_sw [address = "192.168.100.10"];
    core_sw -- ansible_control; # Management connection

    network "User VLAN (VLAN 10)" {
      address = "192.168.10.0/24"
      color = "#F0FFFF";
      core_sw; # Core switch participates in this VLAN
    }
    network "Server VLAN (VLAN 20)" {
      address = "192.168.20.0/24"
      color = "#F5FFFA";
      core_sw; # Core switch participates in this VLAN
    }
    network "Voice VLAN (VLAN 30)" {
      address = "192.168.30.0/24"
      color = "#FFF0F5";
      core_sw; # Core switch participates in this VLAN
    }
  }

  network "Access Switch 1 (Juniper)" {
    address = "192.168.1.0/24" # Default management VLAN interface
    color = "#FFFAF0";
    access_sw_1 [address = "192.168.100.20"];
    access_sw_1 -- ansible_control; # Management connection

    network "User VLAN (VLAN 10)" {
      access_sw_1;
      client_pc_1 [address = "192.168.10.100"];
    }
    network "Voice VLAN (VLAN 30)" {
      access_sw_1;
      ip_phone_1 [address = "192.168.30.10"];
    }
  }

  network "Access Switch 2 (Arista)" {
    address = "192.168.1.0/24" # Default management VLAN interface
    color = "#F0FFF0";
    access_sw_2 [address = "192.168.100.30"];
    access_sw_2 -- ansible_control; # Management connection

    network "User VLAN (VLAN 10)" {
      access_sw_2;
      client_pc_2 [address = "192.168.10.101"];
    }
    network "Server VLAN (VLAN 20)" {
      access_sw_2;
      server_a [address = "192.168.20.20"];
    }
  }

  core_sw -- access_sw_1 [label = "Trunk (VLANs 10,20,30,100)"];
  core_sw -- access_sw_2 [label = "Trunk (VLANs 10,20,30,100)"];
}

Automation Examples

While Terraform itself is an automation tool for provisioning, it often integrates with other automation tools for configuration management or dynamic data generation.

8.11. Python for Dynamic Terraform Inputs

Python can be used to generate complex Terraform variable files (e.g., tfvars files) or dynamic inventories for tools like Ansible, based on external data sources (CMDB, spreadsheets, APIs).

# generate_vlans_tfvars.py
import json

def generate_vlan_config(num_vlans=5, start_vlan_id=100, prefix="APP_VLAN"):
    """
    Generates a dictionary suitable for Terraform's 'vlans_to_create' variable.
    """
    vlans_data = {}
    for i in range(num_vlans):
        vlan_id = str(start_vlan_id + i)
        vlan_name = f"{prefix}_{vlan_id}"
        vlans_data[vlan_id] = vlan_name
    return vlans_data

def write_tfvars_file(filename="terraform.tfvars", **kwargs):
    """
    Writes the generated data to a terraform.tfvars.json file.
    """
    with open(filename, 'w') as f:
        json.dump(kwargs, f, indent=2)
    print(f"Generated {filename} with dynamic VLAN data.")

if __name__ == "__main__":
    # Example usage: Generate 3 VLANs starting from ID 50
    dynamic_vlans = generate_vlan_config(num_vlans=3, start_vlan_id=50, prefix="MGMT_VLAN")
    
    # You could also pull other variables from a database or API
    network_devices = ["192.168.1.10", "192.168.1.11", "192.168.1.12"]

    # Combine all variables into a dictionary for the tfvars file
    tfvars_content = {
        "vlans_to_create": dynamic_vlans,
        "network_devices": network_devices,
        "vpc_cidr_block": "172.31.0.0/16" # Example for AWS VPC
    }

    write_tfvars_file("dynamic.tfvars.json", **tfvars_content)

# To use this in Terraform, you would run:
# python generate_vlans_tfvars.py
# terraform plan -var-file=dynamic.tfvars.json
# terraform apply -var-file=dynamic.tfvars.json

Security Considerations

Implementing IaC for VLANs introduces new security considerations alongside traditional network security.

8.12. IaC Security Best Practices

  • Secure Terraform State:
    • CRITICAL: Never store the terraform.tfstate file locally in production. Use a remote backend (AWS S3, Azure Blob Storage, Terraform Cloud/Enterprise) configured with encryption and versioning.
    • Control access to the state file using IAM policies (AWS), RBAC (Azure), or team-based access in Terraform Cloud.
  • Sensitive Data Management:
    • Avoid hardcoding API keys, passwords, or other sensitive information directly in HCL.
    • Use environment variables, HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault to securely inject credentials into Terraform runs.
  • Code Review and Version Control:
    • Store all Terraform configurations in a Git repository.
    • Implement pull request (PR) workflows requiring peer review before merging changes to production branches.
    • Use static code analysis tools (e.g., tflint, Checkov) to scan for security vulnerabilities or misconfigurations in HCL.
  • Principle of Least Privilege:
    • Grant Terraform (or the CI/CD pipeline executing it) only the minimum necessary permissions to manage the required resources.
    • Use specific IAM roles/service principals for different environments (dev, staging, prod).
  • Drift Detection:
    • Regularly run terraform plan in a read-only mode to detect any changes made to the infrastructure outside of Terraform (configuration drift).
    • Integrate drift detection into your CI/CD pipeline.

8.13. VLAN Security in an IaC Context

IaC, by enforcing desired state, helps mitigate many common VLAN security vulnerabilities:

  • VLAN Hopping (Switch Spoofing/Double Tagging):
    • Mitigation: Declaratively configure all switch ports as access for end devices and explicitly define trunk ports. Disable Dynamic Trunking Protocol (DTP) where not needed (e.g., switchport mode access, switchport nonegotiate). Ensure the native VLAN on trunks is an unused VLAN ID.
    • IaC Benefit: These configurations can be standardized and applied consistently across all devices, preventing manual misconfigurations.
  • Private VLANs (PVLANs):
    • Mitigation: Use PVLANs to isolate ports within the same VLAN at Layer 2, preventing direct communication between devices on “promiscuous” ports or within “isolated” communities.
    • IaC Benefit: Some network device Terraform providers (or Ansible modules) support PVLAN configuration, ensuring complex PVLAN setups are consistently applied.
  • Unused Ports:
    • Mitigation: All unused switch ports should be shut down and assigned to an unused “blackhole” VLAN.
    • IaC Benefit: Terraform/Ansible can enforce this policy across all ports not explicitly defined in the configuration.
  • Default VLAN (VLAN 1):
    • Mitigation: Do not use VLAN 1 for user or production traffic. Change the native VLAN on trunks from VLAN 1 to an unused VLAN.
    • IaC Benefit: Enforced by default configurations in your HCL or Ansible playbooks.
  • VLAN Access Control Lists (VACLs)/Network Security Groups (NSGs):
    • Mitigation: Implement L3/L4 filtering between VLANs/subnets to control traffic flow.
    • IaC Benefit: Terraform is excellent for managing cloud NSGs/Security Groups and can manage VACLs via network device providers.

Security Configuration Example (AWS Security Group with Terraform):

This example shows how Terraform enforces ingress/egress rules for a web server in a public subnet.

# Security Group for Web Servers

resource "aws_security_group" "web_sg" {
  name        = "web-server-sg"
  description = "Allow HTTP/HTTPS inbound traffic"
  vpc_id      = aws_vpc.main_vpc.id # Reference the VPC created earlier

  ingress {
    description      = "Allow HTTP from anywhere"
    from_port        = 80
    to_port          = 80
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  ingress {
    description      = "Allow HTTPS from anywhere"
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1" # All protocols
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "WebSG"
  }
}

Verification & Troubleshooting

8.14. Terraform Verification Commands

Terraform provides a robust set of commands for verifying configurations and inspecting state.

  • terraform init: Initializes a working directory containing Terraform configuration files. Downloads providers and modules.
  • terraform validate: Checks the configuration for syntax errors and internal consistency.
  • terraform plan: Generates an execution plan, showing what actions Terraform will take (create, modify, destroy) without actually performing them. CRITICAL for reviewing changes.
  • terraform apply: Executes the actions proposed in a plan (or a freshly generated one if no plan file is specified).
  • terraform show: Reads the current state file and outputs the managed infrastructure resources.
  • terraform state list: Lists all resources currently managed by the Terraform state.
  • terraform state show <resource_address>: Shows the attributes of a specific resource in the state file.
  • terraform refresh: Updates the state file by checking the real infrastructure’s current state. Useful for detecting drift.

Expected Output (terraform plan example):

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.main_vpc will be created
  + resource "aws_vpc" "main_vpc" {
      + arn                                  = (known after apply)
      + assign_generated_ipv6_cidr_block     = false
      + cidr_block                           = "10.0.0.0/16"
      + default_dhcp_options_id              = (known after apply)
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + enable_classiclink                     = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Environment" = "Prod"
          + "ManagedBy"   = "Terraform"
          + "Name"        = "ProductionVPC"
        }
      + tags_all                             = {
          + "Environment" = "Prod"
          + "ManagedBy"   = "Terraform"
          + "Name"        = "ProductionVPC"
        }
    }

  # aws_subnet.public_subnets[0] will be created
  + resource "aws_subnet" "public_subnets" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-east-1a"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.1.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = true
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Environment" = "Prod"
          + "Name"        = "PublicSubnet-1"
          + "Tier"        = "Public"
        }
      + tags_all                        = {
          + "Environment" = "Prod"
          + "Name"        = "PublicSubnet-1"
          + "Tier"        = "Public"
        }
      + vpc_id                          = (known after apply)
    }

  # ... (truncated for brevity) ...

Plan: 6 to add, 0 to change, 0 to destroy.

8.15. Troubleshooting Terraform & VLANs

Common Terraform Issues:

IssueDescriptionResolution Steps
Provider Not Found/ConfiguredTerraform cannot find or initialize the necessary provider plugin.1. Run terraform init. 2. Check provider block syntax. 3. Ensure AWS/Azure credentials are configured (environment variables, CLI config).
State File Lock/CorruptionMultiple users or processes trying to modify the state, or a corrupted state.1. Ensure remote state locking is configured (e.g., DynamoDB for AWS). 2. If locked, check for active terraform apply processes. 3. If corrupted, consider terraform state pull, manual editing (with extreme caution!), or rollback.
Configuration DriftInfrastructure resources manually changed outside Terraform.1. Run terraform refresh to update state. 2. Run terraform plan to see differences. 3. terraform apply to converge to desired state or manually update HCL.
Resource Not Found (ID)Referencing a resource ID that doesn’t exist or is incorrect.1. Verify resource exists in the cloud/on-prem. 2. Check data source filters. 3. Ensure correct id attribute is used from existing resources or outputs.
Syntax Errors (HCL)Typos, missing brackets, incorrect variable usage.1. Run terraform validate. 2. Carefully review Terraform logs for line numbers and error messages. 3. Use an IDE with HCL syntax highlighting and linting.

Common VLAN Configuration Issues (and IaC’s Role):

IssueDescriptionIaC Impact/Resolution
VLAN ID MismatchDifferent VLAN IDs or names on interconnected switches for the same logical segment.IaC (Terraform+Ansible) ensures consistent VLAN ID and name definitions across all targeted devices, eliminating manual typos. Verification: show vlan brief (Cisco), show vlans (Juniper).
Trunking IssuesIncorrect encapsulation (dot1q), allowed VLANs, or native VLAN mismatch on trunk ports.IaC enforces standardized trunk port configurations (mode, encapsulation, allowed VLANs, native VLAN). terraform plan reviews these. Verification: show interfaces trunk.
Access Port Assignment ErrorEnd-device ports assigned to the wrong VLAN or in trunk mode.IaC explicitly sets switchport mode access and switchport access vlan <ID> for end-device ports, reducing misconfiguration. Verification: show interfaces status, show interfaces switchport.
Routing Between VLANsLayer 3 connectivity missing or incorrect for inter-VLAN routing (SVI/IRB).While IaC can configure SVIs/IRBs, troubleshooting often involves checking IP addresses, routing protocols, and ACLs. Terraform provisions cloud routing policies and virtual routers for cloud subnets.
VLAN Pruning IssuesVLANs unnecessarily propagating over trunks.IaC can enforce VLAN pruning (e.g., switchport trunk allowed vlan remove <ID>) or configure cloud network segmentation to minimize unnecessary traffic propagation. Verification: show interfaces trunk.

Root Cause Analysis: When troubleshooting, combine Terraform’s desired state (terraform show) with actual device state (show commands). Any discrepancy indicates drift or a failed IaC application. For on-premises issues, check Ansible logs (ansible-playbook -v) for connection errors or failed commands.


Performance Optimization

While Terraform primarily focuses on provisioning and configuration consistency, it contributes to network performance indirectly by enabling optimal design and reducing misconfigurations.

8.16. Terraform for Efficient Network Design

  • Optimal Subnetting/VLAN Sizing: IaC encourages thoughtful IP address management and subnet sizing from the outset, preventing inefficient use of IP space or overly large broadcast domains.
  • VLAN Pruning: For on-premises networks, Terraform (via Ansible or direct provider) can ensure VLAN pruning is configured consistently, preventing unnecessary broadcast traffic from flowing across trunk links.
  • Cloud Network Segmentation: By deploying well-designed VPCs/VNets and subnets, Terraform ensures logical isolation, traffic flow optimization, and efficient application of security policies (Security Groups, NSGs).
  • Resource Sizing: Terraform allows you to parameterize and consistently deploy network resources (e.g., VPN gateway bandwidth, NAT gateway capacity) according to performance requirements.

8.17. Terraform Run Optimization

  • Terraform Workspaces: Use workspaces to manage multiple environments (dev, staging, prod) with the same configuration, isolating state files.
  • Modularization: Break down large configurations into smaller, reusable modules to improve readability, maintainability, and execution speed.
  • Targeting: For specific changes, use terraform apply -target=resource_type.name to only apply changes to a subset of resources (use with caution in production).
  • Parallelism: Terraform executes many operations in parallel by default, but you can control this with -parallelism for specific needs (e.g., reduce for API rate limits, increase for faster deployment).

8.18. Monitoring Recommendations

Integrate monitoring solutions (e.g., Prometheus, Grafana, cloud-native monitoring like CloudWatch/Azure Monitor) with your IaC workflow. While Terraform doesn’t monitor directly, it can provision monitoring agents, dashboards, and alerts, ensuring that performance metrics for VLANs (e.g., interface utilization, error rates on trunks) are collected and analyzed consistently.


Hands-On Lab

This lab will guide you through deploying a hybrid network setup using Terraform, comprising a cloud VPC/VNet and simulated on-premises VLANs.

8.19. Lab Topology

nwdiag {
  network "Internet" {
    shape = cloud;
  }

  network "AWS Cloud Environment" {
    address = "10.0.0.0/16"
    Internet; # Connection to Internet
    aws_igw [label="AWS IGW"];

    network "AWS Public Subnet (VLAN-like)" {
      address = "10.0.1.0/24"
      aws_web_ec2 [label="Web Server EC2"];
    }

    network "AWS Private Subnet (VLAN-like)" {
      address = "10.0.10.0/24"
      aws_app_ec2 [label="App Server EC2"];
    }

    aws_vpn_gw [label="AWS VPN Gateway"];
    aws_igw -- aws_vpn_gw; # VPN traffic passes via IGW
  }

  network "On-Premises Lab Network" {
    address = "192.168.0.0/16"
    on_prem_router [label="On-Prem VPN Router"];
    on_prem_switch [label="On-Prem Core Switch"];
    ansible_controller [label="Ansible Controller (VM)"];

    network "VLAN 10 (Users)" {
      address = "192.168.10.0/24"
      user_pc_1 [label="User PC"];
    }

    network "VLAN 20 (Servers)" {
      address = "192.168.20.0/24"
      lab_server_1 [label="Lab Server"];
    }

    ansible_controller -- on_prem_switch; # Management path
    on_prem_router -- on_prem_switch; # Routing path
    on_prem_switch -- user_pc_1;
    on_prem_switch -- lab_server_1;
    on_prem_router -- Internet [label="VPN Tunnel to AWS"]; # Simulated/conceptual
  }
}

8.20. Objectives

  1. Provision an AWS VPC with public and private subnets using Terraform.
  2. Deploy a simulated Ansible Control Host (e.g., a basic EC2 instance in AWS, or use your local machine).
  3. Use Terraform’s local-exec to trigger an Ansible playbook that “configures” (simulates) VLANs on your local machine (acting as an on-prem device).
  4. Verify the cloud resources and the simulated on-prem configuration.

8.21. Step-by-Step Configuration

Prerequisites:

  • AWS account with programmatic access configured (AWS CLI installed and configured).
  • Terraform installed.
  • Ansible installed (if running locally).
  • A basic understanding of network concepts.

Lab Setup (Files):

Create a directory named terraform_vlan_lab. Inside it, create:

  • main.tf (for AWS VPC)
  • on_prem_config.tf (for Ansible integration)
  • variables.tf
  • outputs.tf
  • inventory.ini
  • configure_vlans.yaml

variables.tf:

variable "aws_region" {
  description = "AWS region to deploy resources"
  type        = string
  default     = "us-east-1"
}

variable "vpc_cidr" {
  description = "CIDR block for the VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "public_subnet_cidr" {
  description = "CIDR block for the public subnet"
  type        = string
  default     = "10.0.1.0/24"
}

variable "private_subnet_cidr" {
  description = "CIDR block for the private subnet"
  type        = string
  default     = "10.0.10.0/24"
}

variable "on_prem_vlans" {
  description = "Map of VLAN ID to Name for on-prem devices"
  type        = map(string)
  default = {
    "10" = "Users",
    "20" = "Servers"
  }
}

variable "on_prem_device_ip" {
  description = "IP address of the simulated on-prem device (your local machine)"
  type        = string
  default     = "127.0.0.1" # Use localhost for simulation
}

outputs.tf:

output "aws_vpc_id" {
  description = "ID of the created AWS VPC"
  value       = aws_vpc.lab_vpc.id
}

output "aws_public_subnet_id" {
  description = "ID of the created public subnet"
  value       = aws_subnet.public_subnet.id
}

output "aws_private_subnet_id" {
  description = "ID of the created private subnet"
  value       = aws_subnet.private_subnet.id
}

output "ansible_vlan_config_status" {
  description = "Status of on-prem VLAN configuration via Ansible"
  value       = null_resource.configure_local_vlans.id
}

main.tf (AWS VPC and Subnets):

provider "aws" {
  region = var.aws_region
}

resource "aws_vpc" "lab_vpc" {
  cidr_block = var.vpc_cidr
  tags = {
    Name = "terraform-vlan-lab-vpc"
  }
}

resource "aws_internet_gateway" "lab_igw" {
  vpc_id = aws_vpc.lab_vpc.id
  tags = {
    Name = "terraform-vlan-lab-igw"
  }
}

resource "aws_subnet" "public_subnet" {
  vpc_id                  = aws_vpc.lab_vpc.id
  cidr_block              = var.public_subnet_cidr
  availability_zone       = data.aws_availability_zones.available.names[0]
  map_public_ip_on_launch = true
  tags = {
    Name = "public-subnet"
  }
}

resource "aws_subnet" "private_subnet" {
  vpc_id            = aws_vpc.lab_vpc.id
  cidr_block        = var.private_subnet_cidr
  availability_zone = data.aws_availability_zones.available.names[0]
  tags = {
    Name = "private-subnet"
  }
}

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.lab_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.lab_igw.id
  }
  tags = {
    Name = "public-route-table"
  }
}

resource "aws_route_table_association" "public_rt_assoc" {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_rt.id
}

on_prem_config.tf (Ansible Integration):

# This resource will trigger the Ansible playbook
resource "null_resource" "configure_local_vlans" {
  triggers = {
    # Re-run Ansible if VLAN definitions change
    vlans_hash = jsonencode(var.on_prem_vlans)
  }

  provisioner "local-exec" {
    command = <<-EOT
      ansible-playbook \
        -i "${path.module}/inventory.ini" \
        "${path.module}/configure_vlans.yaml" \
        --extra-vars '{"target_device_ip": "${var.on_prem_device_ip}", "vlans_to_create": ${jsonencode(var.on_prem_vlans)}}'
    EOT
    working_dir = path.module
  }
}

inventory.ini:

[local_device]
 ansible_connection=local

(Note: target_device_ip will be passed via --extra-vars)

configure_vlans.yaml:

---
- name: Simulate On-Prem VLAN Configuration
  hosts: local_device
  gather_facts: false

  tasks:
    - name: Display VLANs to be "configured"
      debug:
        msg: "Simulating creation of VLAN  () on "
      loop: ""
      loop_control:
        label: "VLAN "

    - name: Create dummy files for VLANs (simulation)
      ansible.builtin.copy:
        content: "This is a simulated configuration for VLAN  ()\n"
        dest: "/tmp/vlan_.cfg"
      loop: ""
      loop_control:
        label: "VLAN "

Steps:

  1. Initialize Terraform:
    terraform init
    
  2. Review the Plan:
    terraform plan
    
    Carefully examine the output to ensure Terraform plans to create the VPC, subnets, and trigger the Ansible playbook as expected.
  3. Apply the Configuration:
    terraform apply
    
    Type yes when prompted. Terraform will provision the AWS resources and then execute the Ansible playbook. You should see output from Ansible indicating the simulated VLAN creation.

8.22. Verification Steps

  1. Verify AWS Resources:

    aws ec2 describe-vpcs --filters "Name=tag:Name,Values=terraform-vlan-lab-vpc" --query "Vpcs[*].VpcId" --output text
    aws ec2 describe-subnets --filters "Name=tag:Name,Values=public-subnet" --query "Subnets[*].{ID:SubnetId,CIDR:CidrBlock}" --output table
    aws ec2 describe-subnets --filters "Name=tag:Name,Values=private-subnet" --query "Subnets[*].{ID:SubnetId,CIDR:CidrBlock}" --output table
    

    Confirm that the VPC and subnets have been created with the correct CIDR blocks.

  2. Verify Simulated On-Prem VLANs: Check your local /tmp directory for the dummy configuration files created by Ansible.

    ls /tmp/vlan_*.cfg
    cat /tmp/vlan_10.cfg
    cat /tmp/vlan_20.cfg
    

    This confirms the Ansible playbook was executed successfully.

8.23. Challenge Exercises

  1. Add more VLANs: Modify variables.tf to add a new VLAN (e.g., VLAN 30 for “Voice”). Rerun terraform plan and terraform apply.
  2. Add a Security Group: Create an aws_security_group resource in main.tf to allow SSH (port 22) into the public subnet. Modify the web server EC2 resource (if you add one later) to use this SG.
  3. Clean up: After completing the lab, destroy the resources to avoid incurring costs:
    terraform destroy
    
    Type yes when prompted.

Best Practices Checklist

[x] Configuration Best Practices: * [x] Use a consistent naming convention for all resources (cloud and on-prem). * [x] Organize Terraform code into logical modules (e.g., vpc, subnets, network_devices). * [x] Define variables for all configurable parameters. * [x] Leverage data sources to query existing infrastructure. * [x] Ensure terraform fmt is run on all HCL files for consistent formatting. * [x] Implement terraform validate as part of your CI/CD pipeline. * [x] For on-prem, use idempotency in configuration management tools like Ansible. * [x] Plan for IP address management (IPAM) for both cloud and on-prem.

[x] Security Hardening: * [x] Store Terraform state in a secure, remote, and versioned backend. * [x] Encrypt Terraform state at rest. * [x] Use secrets management solutions (Vault, AWS Secrets Manager, Azure Key Vault) for sensitive data. * [x] Apply the principle of least privilege to Terraform execution roles/users. * [x] Conduct regular code reviews and static analysis of IaC code. * [x] Implement explicit access and trunk port configurations for on-prem. * [x] Disable Dynamic Trunking Protocol (DTP) on unused or access ports. * [x] Set unused ports to an unused VLAN and shut them down. * [x] Change native VLAN on trunks to an unused VLAN (not VLAN 1). * [x] Implement network security groups (cloud) or VACLs (on-prem) for inter-VLAN filtering.

[x] Monitoring Setup: * [x] Terraform configurations include provisioning of monitoring agents and logging configurations for network resources. * [x] Alerting is configured for critical network events (e.g., high traffic on trunk, interface errors). * [x] Dashboards are created to visualize VLAN/subnet performance and connectivity.

[x] Documentation: * [x] All Terraform configurations are well-commented. * [x] Git commit messages clearly describe changes. * [x] Network diagrams (nwdiag, PlantUML, D2) are generated from code and kept up-to-date. * [x] README files for modules and root configurations describe their purpose and usage.

[x] Change Management: * [x] All infrastructure changes flow through a Git-based pull request (PR) workflow. * [x] terraform plan output is reviewed and approved before applying changes to production. * [x] Implement CI/CD pipelines for automated plan, validate, and apply stages. * [x] Enable state locking for remote backends to prevent concurrent modifications. * [x] Define rollback procedures (e.g., revert Git commit, terraform state rm, terraform import).


Terraform Documentation:

VLAN Standards:

Network Automation Tools:

Diagramming Tools:


What’s Next

This chapter has equipped you with the foundational knowledge and practical skills to manage VLANs and network segmentation using Terraform across cloud and on-premises environments. You’ve learned about IaC principles, Terraform’s core components, multi-vendor automation strategies, and critical security and troubleshooting considerations.

In the next chapter, we will delve into Chapter 9: Advanced VLAN Features: VXLAN, PVLANs, and EVPN, exploring more complex segmentation technologies that build upon traditional VLANs to meet the demands of modern data centers and highly scalable network architectures. We will examine how these advanced features are implemented and managed, often still leveraging IaC for consistent deployment.