+++
title = "Chapter 6: Network Automation with Ansible: VLAN Provisioning"
topic = "networking"
date = 2026-01-24
draft = false
weight = 6
description = "This chapter provides a comprehensive guide to automating VLAN provisioning using Ansible. Learn core concepts, multi-vendor configurations (Cisco, Juniper, Arista), security best practices, and hands-on lab exercises for efficient network management."
slug = "ansible-vlan-provisioning"
keywords = ["Ansible", "VLAN automation", "network automation", "VLAN provisioning", "Cisco", "Juniper", "Arista", "802.1Q", "NetDevOps", "infrastructure as code"]
tags = ["Network Automation", "Ansible", "VLAN", "Configuration Management", "NetDevOps"]
categories = ["Networking", "Automation"]
+++

## Introduction

In modern enterprise networks, Virtual Local Area Networks (VLANs) are fundamental for segmenting traffic, enhancing security, and optimizing network performance. However, the manual configuration of VLANs across dozens or hundreds of switches is a tedious, error-prone, and time-consuming process. This chapter addresses these challenges by introducing network automation with Ansible for streamlined VLAN provisioning.

This chapter will guide you through the technical concepts of VLANs and Ansible, provide multi-vendor configuration examples, detail security considerations, offer robust verification and troubleshooting strategies, and outline performance optimization techniques. By the end of this chapter, you will be able to design, implement, and automate VLAN provisioning workflows across diverse network infrastructures using Ansible.

## Technical Concepts

### VLAN Fundamentals and the IEEE 802.1Q Standard

VLANs logically segment a single physical network into multiple broadcast domains. This segmentation improves security by isolating different types of traffic (e.g., corporate, guest, voice, IoT) and enhances network efficiency by reducing the size of broadcast domains. The core standard governing VLAN tagging is **IEEE 802.1Q-2022**, which defines the mechanism for inserting a 4-byte tag into an Ethernet frame header to identify the VLAN to which the frame belongs.

Key 802.1Q concepts include:
*   **VLAN ID (VID):** A 12-bit field in the 802.1Q tag, allowing for 4094 unique VLANs (1-4094).
*   **Tagged Frames:** Ethernet frames carrying an 802.1Q tag, typically used on trunk links between switches or to devices that understand VLAN tags (e.g., routers, servers with virtual NICs).
*   **Untagged Frames:** Ethernet frames without an 802.1Q tag, typically used on access ports connected to end devices that are not VLAN-aware.
*   **Native VLAN:** On a trunk link, untagged frames are associated with the native VLAN. It is a security best practice to change the native VLAN from the default (VLAN 1) to an unused VLAN ID.
*   **Trunk Port:** A switch port configured to carry traffic for multiple VLANs (tagged frames).
*   **Access Port:** A switch port configured to carry traffic for a single VLAN (untagged frames).

For advanced scenarios, **IEEE 802.1ad (QinQ)** extends 802.1Q by allowing multiple VLAN tags (stacked VLANs) within a single Ethernet frame, often used by service providers to carry customer VLANs across their network without conflicting with their own VLAN IDs. While essential for certain designs, this chapter focuses on standard 802.1Q provisioning.

#### Logical Network Segmentation with VLANs

```nwdiag
nwdiag {
  network corporate_network {
    address = "192.168.10.0/24"
    description = "VLAN 10: Corporate"
    color = "#E0FFFF"

    Router [address = "192.168.10.1"];
  }

  network guest_network {
    address = "192.168.20.0/24"
    description = "VLAN 20: Guest Wi-Fi"
    color = "#F0FFF0"

    Router [address = "192.168.20.1"];
  }

  network voice_network {
    address = "192.168.30.0/24"
    description = "VLAN 30: Voice"
    color = "#FFFACD"

    Router [address = "192.168.30.1"];
  }

  network management_network {
    address = "192.168.99.0/24"
    description = "VLAN 99: Management"
    color = "#FFE4E1"

    Router [address = "192.168.99.1"];
  }

  network core_trunk {
    description = "Trunk Link"
    color = "#D3D3D3"
    style = dotted
  }

  Router;
  Core_Switch;
  Access_Switch_1;
  Access_Switch_2;

  corporate_network -- Access_Switch_1 [label="Port 10", style=dashed];
  guest_network -- Access_Switch_1 [label="Port 20", style=dashed];
  voice_network -- Access_Switch_1 [label="Port 30", style=dashed];

  corporate_network -- Access_Switch_2 [label="Port 10", style=dashed];
  guest_network -- Access_Switch_2 [label="Port 20", style=dashed];
  voice_network -- Access_Switch_2 [label="Port 30", style=dashed];

  Router -- Core_Switch;
  Core_Switch -- Access_Switch_1;
  Core_Switch -- Access_Switch_2;
}

Ansible Architecture for Network Automation

Ansible is an agentless automation engine that uses SSH to connect to network devices and execute commands. Its declarative nature allows engineers to define the desired state of their network, and Ansible handles the steps required to reach that state.

  • Control Node: The machine where Ansible is installed and from which playbooks are run.
  • Managed Nodes: The network devices (switches, routers) that Ansible configures.
  • Inventory: A list of managed nodes, often grouped for logical organization (e.g., ‘core_switches’, ‘access_switches’, ‘cisco’, ‘juniper’). Credentials and variables can be defined here.
  • Playbooks: YAML files that describe a series of automation tasks to be executed on managed nodes. Playbooks use modules to interact with devices.
  • Modules: Pre-built units of code that abstract complex tasks. Ansible provides specific network modules (e.g., cisco.ios.vlan, juniper.junos.vlan, arista.eos.vlan) designed for various vendors.
  • Idempotency: A core Ansible principle. Running a playbook multiple times will yield the same result. If the desired state is already met, Ansible will report no changes. This is crucial for consistent network configurations.

Ansible Network Automation Architecture

@startuml
!theme mars

' Define ALL elements first
component "Ansible Control Node" as ControlNode
cloud "Network Devices" as NetworkDevices
database "Inventory (hosts.ini)" as Inventory
file "Playbooks (.yml)" as Playbooks
artifact "Network Modules (e.g., ios_vlan)" as Modules
rectangle "Device OS (e.g., Cisco IOS XE)" as DeviceOS
actor "Network Engineer" as Engineer

' Then connect them
Engineer -- ControlNode : "Runs Playbooks"
ControlNode -- Inventory : "Reads Hosts/Groups"
ControlNode [label="Playbooks : "Executes Tasks"
Playbooks"] Modules : "Uses specific modules"
Modules <--> NetworkDevices : "SSH/NETCONF/eAPI"
NetworkDevices -- DeviceOS : "Interacts with OS"

ControlNode -[hidden]down-> NetworkDevices
NetworkDevices -[hidden]down-> DeviceOS
@enduml

Control Plane vs. Data Plane Interaction

When automating VLAN provisioning, Ansible primarily interacts with the control plane of a network device. It sends configuration commands (via SSH/CLI or API/NETCONF) to the device’s operating system (e.g., Cisco IOS XE, Juniper Junos, Arista EOS). These commands modify the device’s configuration, which in turn dictates how the data plane (the forwarding hardware) handles Ethernet frames. For instance, creating a VLAN and assigning it to a port through Ansible commands updates the switch’s internal forwarding tables (MAC address table, VLAN database), thereby influencing how actual data traffic is processed.

State Machines and Workflows for VLAN Provisioning

Ansible’s strength lies in its declarative model, effectively acting as a state machine. Instead of specifying a series of imperative commands (“create VLAN 10”, “assign interface Gi1/0/1 to VLAN 10”), you declare the desired final state (“VLAN 10 exists”, “Interface Gi1/0/1 is an access port in VLAN 10”). Ansible compares this desired state with the current state of the device and executes only the necessary commands to reconcile any differences.

A typical VLAN provisioning workflow with Ansible involves:

  1. Define Desired State: Create variables or data structures (YAML dictionaries/lists) outlining the VLANs, their IDs, names, and associated interfaces.
  2. Playbook Execution: Run an Ansible playbook that uses network-specific modules. These modules handle the vendor-specific CLI or API interactions.
  3. Idempotent Application: Ansible checks the current device configuration against the desired state.
    • If a VLAN/interface configuration matches the desired state, no action is taken.
    • If a VLAN is missing or incorrect, Ansible applies the necessary commands.
    • If a VLAN needs to be removed, Ansible issues the deletion commands (if state: absent is used).
  4. Verification: Post-provisioning tasks to confirm the new VLANs and port assignments are active and correct.

VLAN Provisioning Workflow with Ansible

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

    A [label="1. Define Desired VLAN State\n(YAML Data)"];
    B [label="2. Prepare Ansible Playbook\n(Modules, Variables, Templates)"];
    C [label="3. Execute Playbook\n(ansible-playbook command)"];
    D [label="4. Connect to Network Device\n(SSH/NETCONF/eAPI)"];
    E [label="5. Retrieve Current Config\n(Device State)"];
    F [label="6. Compare Desired vs. Current State\n(Idempotency Check)"];
    G [label="7. Apply Necessary Changes\n(Configuration Commands)"];
    H [label="8. Device Configuration Updated\n(Control Plane)"];
    I [label="9. Data Plane Activated\n(Traffic Forwarding)"];
    J [label="10. Verify Configuration\n(Post-Check Tasks)"];

    A -> B [label="Input"];
    B -> C [label="Run"];
    C -> D [label="Connect"];
    D -> E [label="Read"];
    E -> F [label="Input"];
    A -> F [label="Input"];
    F -> G [label="If Different"];
    G -> H [label="Configure"];
    H -> I [label="Activate"];
    I -> J [label="Confirm"];
    G -> C [label="Report No Change", style=dashed, color="#A9A9A9"];
}

Configuration Examples

This section provides multi-vendor configuration examples for VLAN provisioning tasks that Ansible will automate.

Scenario: Create VLAN 10 (Corporate), VLAN 20 (Guest), and VLAN 30 (Voice). Assign VLAN 10 to interface GigabitEthernet0/1, VLAN 20 to interface GigabitEthernet0/2, and configure GigabitEthernet0/24 as a trunk port allowing VLANs 10, 20, and 30. Set native VLAN to 999.

Cisco IOS XE

! Configure VLANs
vlan 10
 name Corporate
vlan 20
 name Guest
vlan 30
 name Voice
vlan 999
 name Native_Unused

! Configure Access Port (VLAN 10)
interface GigabitEthernet0/1
 description Access Port for Corporate
 switchport mode access
 switchport access vlan 10
 no shutdown

! Configure Access Port (VLAN 20)
interface GigabitEthernet0/2
 description Access Port for Guest
 switchport mode access
 switchport access vlan 20
 no shutdown

! Configure Trunk Port (allowing VLANs 10, 20, 30, with native VLAN 999)
interface GigabitEthernet0/24
 description Trunk to Core Switch
 switchport mode trunk
 switchport trunk allowed vlan 10,20,30
 switchport trunk native vlan 999
 no shutdown

! --- Verification Commands ---
show vlan brief
show interfaces GigabitEthernet0/1 switchport
show interfaces GigabitEthernet0/2 switchport
show interfaces GigabitEthernet0/24 trunk

Expected Cisco Output (Partial):

VLAN Name                             Status    Ports
---- -------------------------------- --------- -------------------------------
10   Corporate                        active    Gi0/1
20   Guest                            active    Gi0/2
30   Voice                            active
999  Native_Unused                    active
...

Name: Gi0/1
Switchport: Enabled
Administrative Mode: static access
Operational Mode: static access
Access Mode VLAN: 10 (Corporate)
...

Name: Gi0/24
Switchport: Enabled
Administrative Mode: trunk
Operational Mode: trunk
Negotiation of Trunking: Off
Access Mode VLAN: 1 (default)
Trunking Native Mode VLAN: 999 (Native_Unused)
Trunking VLANs Enabled: 10,20,30
...

Juniper JunOS

# Configure VLANs
set vlans Corporate vlan-id 10
set vlans Guest vlan-id 20
set vlans Voice vlan-id 30
set vlans Native_Unused vlan-id 999

# Configure Access Port (VLAN 10)
set interfaces ge-0/0/1 unit 0 family ethernet-switching interface-mode access
set interfaces ge-0/0/1 unit 0 family ethernet-switching vlan members Corporate
set interfaces ge-0/0/1 disable no-disable

# Configure Access Port (VLAN 20)
set interfaces ge-0/0/2 unit 0 family ethernet-switching interface-mode access
set interfaces ge-0/0/2 unit 0 family ethernet-switching vlan members Guest
set interfaces ge-0/0/2 disable no-disable

# Configure Trunk Port (allowing VLANs 10, 20, 30, with native VLAN 999)
set interfaces ge-0/0/24 unit 0 family ethernet-switching interface-mode trunk
set interfaces ge-0/0/24 unit 0 family ethernet-switching vlan members Corporate
set interfaces ge-0/0/24 unit 0 family ethernet-switching vlan members Guest
set interfaces ge-0/0/24 unit 0 family ethernet-switching vlan members Voice
set interfaces ge-0/0/24 unit 0 family ethernet-switching native-vlan-id 999
set interfaces ge-0/0/24 disable no-disable

commit and-quit

# --- Verification Commands ---
show vlans
show interfaces ge-0/0/1 terse
show interfaces ge-0/0/2 terse
show interfaces ge-0/0/24 terse

Expected Juniper Output (Partial):

VLAN                Tag       Type   Interfaces
Corporate           10        ivl
Guest               20        ivl
Native_Unused       999       ivl
Voice               30        ivl
...

Interface               Admin Link Proto    Local                 Remote
ge-0/0/1.0              up    up   eth-switch
ge-0/0/2.0              up    up   eth-switch
ge-0/0/24.0             up    up   eth-switch
...

Arista EOS

! Configure VLANs
vlan 10
 name Corporate
vlan 20
 name Guest
vlan 30
 name Voice
vlan 999
 name Native_Unused

! Configure Access Port (VLAN 10)
interface Ethernet1/1
 description Access Port for Corporate
 switchport mode access
 switchport access vlan 10
 no shutdown

! Configure Access Port (VLAN 20)
interface Ethernet1/2
 description Access Port for Guest
 switchport mode access
 switchport access vlan 20
 no shutdown

! Configure Trunk Port (allowing VLANs 10, 20, 30, with native VLAN 999)
interface Ethernet1/24
 description Trunk to Core Switch
 switchport mode trunk
 switchport trunk allowed vlan 10,20,30
 switchport trunk native vlan 999
 no shutdown

! --- Verification Commands ---
show vlan
show interfaces Ethernet1/1 switchport
show interfaces Ethernet1/2 switchport
show interfaces Ethernet1/24 trunk

Expected Arista Output (Partial):

VLAN  Name                             Status    Ports
----- -------------------------------- --------- -------------------------------
10    Corporate                        active    Et1/1
20    Guest                            active    Et1/2
30    Voice                            active
999   Native_Unused                    active
...

Name: Et1/1
Switchport: Enabled
Administrative Mode: access
Operational Mode: access
Access Mode VLAN: 10 (Corporate)
...

Name: Et1/24
Switchport: Enabled
Administrative Mode: trunk
Operational Mode: trunk
Negotiation of Trunking: Off
Access Mode VLAN: 1 (default)
Trunking Native Mode VLAN: 999 (Native_Unused)
Trunking VLANs Enabled: 10,20,30
...

Automation Examples

This section demonstrates how to automate the above VLAN provisioning tasks using Ansible playbooks. We will leverage Ansible’s network-specific modules for an idempotent and declarative approach.

First, define your inventory (hosts.ini) and device variables (group_vars/all.yml or host_vars).

hosts.ini:

[cisco_devices]
cisco_switch_1 ansible_host=10.0.0.10

[juniper_devices]
juniper_switch_1 ansible_host=10.0.0.11

[arista_devices]
arista_switch_1 ansible_host=10.0.0.12

[network_devices:children]
cisco_devices
juniper_devices
arista_devices

group_vars/all.yml:

---
ansible_network_os: "{{ 'cisco.ios.ios' if inventory_hostname in groups['cisco_devices'] else \
                        ('juniper.junos.junos' if inventory_hostname in groups['juniper_devices'] else \
                         ('arista.eos.eos' if inventory_hostname in groups['arista_devices'] else '')) }}"
ansible_user: your_ansible_user
ansible_password: "" # Secure password via environment variable
ansible_become: yes # For privilege escalation
ansible_become_method: enable # Method for privilege escalation

vlan_data.yml (Data for VLANs and interfaces):

---
desired_vlans:
  - id: 10
    name: Corporate
  - id: 20
    name: Guest
  - id: 30
    name: Voice
  - id: 999
    name: Native_Unused

cisco_interfaces:
  - name: GigabitEthernet0/1
    description: "Corporate Access Port"
    mode: access
    access_vlan: 10
  - name: GigabitEthernet0/2
    description: "Guest Access Port"
    mode: access
    access_vlan: 20
  - name: GigabitEthernet0/24
    description: "Trunk to Core"
    mode: trunk
    trunk_allowed_vlans: [10, 20, 30]
    trunk_native_vlan: 999

juniper_interfaces:
  - name: ge-0/0/1
    description: "Corporate Access Port"
    mode: access
    access_vlan: 10
  - name: ge-0/0/2
    description: "Guest Access Port"
    mode: access
    access_vlan: 20
  - name: ge-0/0/24
    description: "Trunk to Core"
    mode: trunk
    trunk_allowed_vlans: [10, 20, 30]
    trunk_native_vlan: 999

arista_interfaces:
  - name: Ethernet1/1
    description: "Corporate Access Port"
    mode: access
    access_vlan: 10
  - name: Ethernet1/2
    description: "Guest Access Port"
    mode: access
    access_vlan: 20
  - name: Ethernet1/24
    description: "Trunk to Core"
    mode: trunk
    trunk_allowed_vlans: [10, 20, 30]
    trunk_native_vlan: 999

Ansible Playbook for Multi-Vendor VLAN Provisioning

playbooks/provision_vlans.yml:

---
- name: Provision VLANs and Interfaces on Network Devices
  hosts: network_devices
  gather_facts: false # Network devices often don't support gather_facts

  vars_files:
    - ../vlan_data.yml

  tasks:
    - name: Ensure VLANs exist (Cisco IOS XE)
      when: ansible_network_os == 'cisco.ios.ios'
      cisco.ios.vlan:
        vlan_id: ""
        name: ""
        state: present
      loop: ""

    - name: Ensure VLANs exist (Juniper Junos)
      when: ansible_network_os == 'juniper.junos.junos'
      juniper.junos.vlan:
        vlan_id: ""
        name: ""
        state: present
      loop: ""

    - name: Ensure VLANs exist (Arista EOS)
      when: ansible_network_os == 'arista.eos.eos'
      arista.eos.vlan:
        vlan_id: ""
        name: ""
        state: present
      loop: ""

    - name: Configure Access/Trunk Interfaces (Cisco IOS XE)
      when: ansible_network_os == 'cisco.ios.ios'
      cisco.ios.interfaces:
        config:
          - name: ""
            description: ""
            enabled: true
            switchport:
              mode: ""
              access:
                vlan: ""
              trunk:
                native_vlan: ""
                allowed_vlans: ""
        state: merged
      loop: ""

    - name: Configure Access/Trunk Interfaces (Juniper Junos)
      when: ansible_network_os == 'juniper.junos.junos'
      juniper.junos.interfaces:
        config:
          - name: ""
            description: ""
            unit:
              - name: 0
                family:
                  ethernet_switching:
                    interface_mode: ""
                    vlan:
                      members: ""
                    native_vlan_id: ""
        state: merged
      loop: ""

    - name: Configure Access/Trunk Interfaces (Arista EOS)
      when: ansible_network_os == 'arista.eos.eos'
      arista.eos.interfaces:
        config:
          - name: ""
            description: ""
            enabled: true
            switchport:
              mode: ""
              access:
                vlan: ""
              trunk:
                native_vlan: ""
                allowed_vlans: ""
        state: merged
      loop: ""

    - name: Save configuration (Cisco IOS XE)
      when: ansible_network_os == 'cisco.ios.ios'
      cisco.ios.config:
        save_when: modified

    - name: Save configuration (Juniper Junos)
      when: ansible_network_os == 'juniper.junos.junos'
      juniper.junos.config:
        commit: yes
        diff: yes
        comment: "Ansible: VLAN and interface provisioning"

    - name: Save configuration (Arista EOS)
      when: ansible_network_os == 'arista.eos.eos'
      arista.eos.config:
        save_when: modified

Explanation:

  • hosts: network_devices: The playbook targets all devices in the network_devices group from hosts.ini.
  • gather_facts: false: Recommended for network devices as collecting facts can be slow or unsupported by some hardware/OS versions.
  • vars_files: Imports vlan_data.yml to define desired VLANs and interface configurations.
  • when: ansible_network_os == ...: This condition ensures that tasks are only executed on devices of the specific vendor. ansible_network_os is a variable set based on your inventory.
  • VLAN Modules: cisco.ios.vlan, juniper.junos.vlan, arista.eos.vlan are used to create or ensure the presence of VLANs by their ID and name. state: present makes this task idempotent.
  • Interface Modules: cisco.ios.interfaces, juniper.junos.interfaces, arista.eos.interfaces are used to configure the switch ports. These modules provide declarative options for switchport mode, access vlan, trunk allowed vlans, and native vlan.
  • loop: Iterates over the desired_vlans and interface lists, applying the same logic to each item.
  • save_when: modified / commit: yes: Ensures that changes are committed and saved to the device’s persistent configuration only if changes were made.

To execute this playbook, run:

export ANSIBLE_NET_PASSWORD="your_secure_password"
ansible-playbook -i hosts.ini playbooks/provision_vlans.yml -vvv

Security Considerations

VLANs enhance network security by segmenting traffic, but they are not foolproof. Improper configuration can lead to vulnerabilities like VLAN hopping. Automation tools like Ansible, if not secured, can also become attack vectors.

Attack Vectors and Mitigation Strategies

  1. VLAN Hopping (Switch Spoofing):

    • Attack: An attacker configures their device to emulate a trunking switch (e.g., using DTP - Dynamic Trunking Protocol) and then sends specially crafted frames to gain access to VLANs beyond their intended access VLAN.
    • Mitigation:
      • Disable DTP: Manually configure all switch ports as either switchport mode access or switchport mode trunk. Never use switchport mode dynamic auto or dynamic desirable on production ports.
      • Untrusted ports to access mode: By default, all end-user ports should be configured as access ports for a specific VLAN.
      • Disable unused ports: Shut down ports that are not in use to prevent unauthorized connections.
      • Ansible Automation: Automate the explicit configuration of port modes to enforce these policies consistently.
  2. VLAN Hopping (Double Tagging):

    • Attack: An attacker sends a frame with two 802.1Q tags. When the first switch processes the outer tag, it strips it and forwards the frame. The next switch then sees the inner tag and forwards the frame to a VLAN the attacker should not have access to, typically exploiting the native VLAN.
    • Mitigation:
      • Change Native VLAN: Set the native VLAN on all trunk ports to an unused VLAN ID (e.g., 999 as used in examples). Ensure this unused VLAN is not routed anywhere.
      • Explicitly tag Native VLAN: Some devices allow tagging of the native VLAN on trunks. Enable this if supported.
      • Ansible Automation: Use Ansible to consistently configure native VLANs across all trunk ports according to your security policy.
  3. Default VLAN 1:

    • Vulnerability: VLAN 1 is often the default native VLAN and is usually configured on all switch ports by default. This makes it a common target for attacks.
    • Mitigation:
      • Do not use VLAN 1 for user data: Reserve VLAN 1 only for management (if absolutely necessary, but preferably use a dedicated management VLAN) or ideally, remove it from all operational interfaces.
      • Change native VLAN: As above, set the native VLAN to an unused ID.
  4. Misconfigurations with Automation:

    • Risk: An incorrectly written Ansible playbook could accidentally assign sensitive VLANs to insecure ports or create loops.
    • Mitigation:
      • Idempotency: Ansible’s idempotent nature helps prevent unintended configuration drift but doesn’t protect against logically flawed desired states.
      • Testing: Thoroughly test playbooks in lab environments before deploying to production.
      • Code Review: Implement peer review for all automation scripts.
      • Version Control: Store playbooks in Git with proper change management.
      • Least Privilege: Configure Ansible user accounts on network devices with the minimum necessary privileges.
      • Ansible Vault: Use Ansible Vault to encrypt sensitive data like passwords and API keys.

Security Best Practices Checklist

  • [ ] Disable DTP: Explicitly configure switchport mode access or switchport mode trunk on all interfaces.
  • [ ] Change Native VLAN: Set switchport trunk native vlan <unused_vlan_id> on all trunk links.
  • [ ] Restrict Allowed VLANs: Use switchport trunk allowed vlan <list_of_vlans> to explicitly allow only necessary VLANs on trunk ports. Avoid switchport trunk allowed vlan all.
  • [ ] Disable Unused Ports: shutdown interfaces that are not actively used.
  • [ ] Port Security: Implement port security (MAC address limits, sticky MAC) where appropriate to prevent unauthorized devices.
  • [ ] Control Plane Policing (CoPP): Protect the switch’s CPU from malicious or excessive traffic destined for the control plane.
  • [ ] Ansible Vault: Encrypt all sensitive credentials and data within your Ansible project.
  • [ ] Role-Based Access Control (RBAC): Implement RBAC for who can run playbooks and access device credentials.
  • [ ] Logging and Auditing: Ensure comprehensive logging on devices and integrate with SIEM for auditing automation actions.

Security Configuration Examples

The Ansible playbooks demonstrated above implicitly enforce many of these best practices by explicitly defining port modes, allowed VLANs, and native VLANs. For example, using switchport trunk allowed vlan 10,20,30 explicitly restricts the VLANs on the trunk.

To explicitly shut down unused ports, you could add another task:

    - name: Shut down unused interfaces (Cisco IOS XE example)
      when: ansible_network_os == 'cisco.ios.ios'
      cisco.ios.interfaces:
        config:
          - name: GigabitEthernet0/3 # Example unused port
            enabled: false
            # Optionally set to access mode to prevent trunk negotiation
            switchport:
              mode: access
              access:
                vlan: 999 # Assign to an unused VLAN
        state: merged

Security Warning: Never hardcode passwords directly in your playbooks or inventory files. Always use Ansible Vault or environment variables for sensitive data.

Verification & Troubleshooting

After automating VLAN provisioning, robust verification is essential to ensure the network is operating as expected. Troubleshooting skills are critical when issues arise.

Verification Commands and Expected Output

These commands, used in the manual configuration section, are equally vital for post-automation verification.

Cisco IOS XE

# Verify VLANs created
show vlan brief

# Verify interface configuration for access port
show interfaces GigabitEthernet0/1 switchport

# Verify interface configuration for trunk port
show interfaces GigabitEthernet0/24 trunk

Expected Output Snippets:

  • show vlan brief: Should list VLANs 10, 20, 30, 999 with their names and associated active ports.
  • show interfaces GigabitEthernet0/1 switchport: Should show Operational Mode: static access and Access Mode VLAN: 10.
  • show interfaces GigabitEthernet0/24 trunk: Should show Operational Mode: trunk, Trunking Native Mode VLAN: 999, and Trunking VLANs Enabled: 10,20,30.

Juniper JunOS

# Verify VLANs created
show vlans

# Verify interface configuration for access port
show interfaces ge-0/0/1 extensive | match "Ethernet-switching|VLAN"

# Verify interface configuration for trunk port
show interfaces ge-0/0/24 extensive | match "Ethernet-switching|VLAN"

Expected Output Snippets:

  • show vlans: Should list Corporate, Guest, Voice, Native_Unused with correct Tag IDs.
  • show interfaces ge-0/0/1 extensive | match "Ethernet-switching|VLAN": Should show Interface mode: Access, Access VLAN: Corporate.
  • show interfaces ge-0/0/24 extensive | match "Ethernet-switching|VLAN": Should show Interface mode: Trunk, Native VLAN ID: 999, VLAN membership: Corporate, Guest, Voice.

Arista EOS

# Verify VLANs created
show vlan

# Verify interface configuration for access port
show interfaces Ethernet1/1 switchport

# Verify interface configuration for trunk port
show interfaces Ethernet1/24 trunk

Expected Output Snippets:

  • show vlan: Should list VLANs 10, 20, 30, 999 with their names and associated active ports.
  • show interfaces Ethernet1/1 switchport: Should show Operational Mode: access and Access Mode VLAN: 10.
  • show interfaces Ethernet1/24 trunk: Should show Operational Mode: trunk, Trunking Native Mode VLAN: 999, and Trunking VLANs Enabled: 10,20,30.

Troubleshooting Common Issues

IssueDescriptionResolution Steps
VLAN MismatchA device connected to an access port is in a different VLAN than the port is configured for.1. Verify access vlan on the switch port. 2. Verify device’s IP configuration matches the correct VLAN subnet. 3. Ensure the VLAN exists and is active.
Native VLAN MismatchNative VLANs on connected trunk ports do not match.1. Check switchport trunk native vlan on both ends of the trunk. 2. Ensure they are identical. 3. Re-run Ansible to correct. This is a common STP issue source.
Incorrect Port ModeAn interface intended for an end device is configured as a trunk, or vice-versa.1. Verify switchport mode (Cisco/Arista) or interface-mode (Juniper) on the port. 2. Correct to access or trunk as needed. 3. Disable DTP if enabled.
VLAN Not Allowed on TrunkA VLAN exists on one switch but is not permitted on the trunk link to another.1. Check switchport trunk allowed vlan (Cisco/Arista) or vlan members (Juniper) on both ends of the trunk. 2. Add the missing VLAN ID to the allowed list.
Physical ConnectivityCable issues, interface errors, port shut down.1. Check interface status (show ip interface brief, show interfaces status). 2. Verify cable connections. 3. Ensure no shutdown is applied.
Ansible Playbook ErrorsSyntax errors, incorrect module parameters, credential issues, network unreachable.1. Run Ansible with -vvv for verbose output. 2. Check YAML syntax with ansible-playbook --syntax-check. 3. Verify ansible_host, ansible_user, ansible_password in inventory. 4. Ping the device from the control node.
No Connectivity to Default GatewayVLANs configured correctly, but devices cannot reach outside their subnet.1. Verify the SVI/VLAN interface IP address on the Layer 3 switch/router for that VLAN. 2. Check routing table (show ip route). 3. Ensure the VLAN is allowed on the trunk to the L3 device. 4. Check firewall rules if applicable.

Ansible-Specific Debugging

  • Verbose Output: Use ansible-playbook -vvv <playbook.yml> for detailed command execution logs and responses from devices.
  • Check Mode: Run with ansible-playbook --check <playbook.yml> to preview changes without applying them.
  • Diff Mode: Use ansible-playbook --diff <playbook.yml> to see the exact configuration difference Ansible intends to apply.
  • debug module: Insert - debug: var=<variable_name> tasks in your playbook to inspect variable values during execution.

Performance Optimization

VLANs inherently offer performance benefits by reducing broadcast domains. However, proper design and configuration are crucial for optimal network performance.

  1. VLAN Pruning:

    • Concept: Prevents unnecessary VLAN traffic from being broadcast across trunk links where it’s not needed. If VLAN 10 exists on Switch A but not Switch B, and Switch A is trunked to Switch B, there’s no need to send VLAN 10 traffic over that trunk.
    • Optimization: Explicitly specify switchport trunk allowed vlan on trunk ports to only permit VLANs that are active on both sides of the link or needed further downstream.
    • Ansible Role: An Ansible playbook can automate the process of querying active VLANs and dynamically configuring allowed vlans on trunk ports.
  2. Optimal VLAN Sizing:

    • Concept: Avoid excessively large VLANs. While VLANs reduce broadcast domains, a VLAN with thousands of devices can still experience high broadcast traffic, impacting performance.
    • Optimization: Segment large user groups into smaller VLANs based on department, location, or function.
    • Ansible Role: Your vlan_data.yml and playbooks should reflect a thoughtful, segmented VLAN design.
  3. Spanning Tree Protocol (STP) Optimization:

    • Concept: VLANs interact with STP (or RSTP, MSTP). Each VLAN has its own STP instance in PVST+ or a subset in MSTP. Proper STP configuration prevents loops and ensures fast convergence.
    • Optimization:
      • PortFast: Enable PortFast on all access ports connected to end devices to quickly bring them into forwarding state.
      • BPDU Guard/Filter: Protect access ports from receiving BPDUs, indicating a potential rogue switch.
      • Root Bridge Placement: Designate primary and secondary root bridges for each VLAN.
    • Ansible Role: Ansible can automate these STP configurations globally and per-interface.
  4. Monitoring and Capacity Planning:

    • Concept: Regularly monitor network performance metrics (bandwidth utilization, CPU load, error rates) to identify bottlenecks.
    • Optimization: Use network monitoring tools (e.g., Prometheus, Grafana, SolarWinds, PRTG) to track VLAN-specific traffic.
    • Ansible Role: While Ansible doesn’t monitor, it can configure SNMP traps or NetFlow/IPFIX exporters to facilitate monitoring. Use gathered data for future VLAN and subnet capacity planning.

Hands-On Lab

This lab will guide you through automating the provisioning of VLANs and interface configurations on a simulated or lab network environment using Ansible.

Lab Topology

The lab will use two switches, with a trunk link between them, and two end-devices connected to access ports on Switch1.

nwdiag {
  network corporate_vlan {
    address = "192.168.10.0/24"
    description = "VLAN 10: Corporate"
    color = "#E0FFFF"
  }
  network guest_vlan {
    address = "192.168.20.0/24"
    description = "VLAN 20: Guest"
    color = "#F0FFF0"
  }
  network unused_native {
    address = "192.168.99.0/24"
    description = "VLAN 99: Native Unused"
    color = "#FFE4E1"
  }

  network trunk_link {
    description = "Trunk Link"
    color = "#D3D3D3"
    style = dotted
  }

  Switch1 [address = "10.0.0.10"];
  Switch2 [address = "10.0.0.11"];
  HostA [address = "192.168.10.5"];
  HostB [address = "192.168.20.5"];

  corporate_vlan -- HostA;
  guest_vlan -- HostB;

  HostA -- Switch1 [label="Access (VLAN 10)"];
  HostB -- Switch1 [label="Access (VLAN 20)"];
  Switch1 -- Switch2 [label="Trunk"];
}

Objectives

  1. Set up an Ansible control node and inventory.
  2. Define desired VLANs (10, 20, 99) and interface configurations.
  3. Create an Ansible playbook to provision VLANs and configure access/trunk ports.
  4. Execute the playbook and verify configurations on both switches.
  5. Test inter-VLAN connectivity (if a Layer 3 device is available).

Step-by-Step Configuration

Prerequisites:

  • An Ansible control node (e.g., Linux VM).
  • Two network devices (e.g., Cisco IOS XE, Juniper Junos, or Arista EOS) accessible via SSH, with basic connectivity and SSH user credentials.
  • Initial clean configuration on switches, or at least no conflicting VLANs/interface configs.

1. Create Project Directory:

mkdir ansible_vlan_lab
cd ansible_vlan_lab

2. Create hosts.ini: Assume Switch1 is cisco_switch_1 and Switch2 is juniper_switch_1. Adjust ansible_host IPs.

# hosts.ini
[cisco_devices]
cisco_switch_1 ansible_host=192.168.1.10

[juniper_devices]
juniper_switch_1 ansible_host=192.168.1.11

[network_devices:children]
cisco_devices
juniper_devices

3. Create group_vars/all.yml:

# group_vars/all.yml
---
ansible_network_os: "{{ 'cisco.ios.ios' if inventory_hostname in groups['cisco_devices'] else \
                        ('juniper.junos.junos' if inventory_hostname in groups['juniper_devices'] else '') }}"
ansible_user: your_ansible_user
ansible_password: ""
ansible_become: yes
ansible_become_method: enable

Important: Set the ANSIBLE_NET_PASSWORD environment variable before running the playbook: export ANSIBLE_NET_PASSWORD="your_switch_password"

4. Create vlan_data.yml: Define specific interface names for your lab switches (e.g., GigabitEthernet0/1 for Cisco, ge-0/0/1 for Juniper).

# vlan_data.yml
---
desired_vlans:
  - id: 10
    name: Corporate
  - id: 20
    name: Guest
  - id: 99
    name: Native_Unused

cisco_switch1_interfaces:
  - name: GigabitEthernet0/1 # For HostA
    description: "Access Port for HostA"
    mode: access
    access_vlan: 10
  - name: GigabitEthernet0/2 # For HostB
    description: "Access Port for HostB"
    mode: access
    access_vlan: 20
  - name: GigabitEthernet0/24 # Trunk to Switch2
    description: "Trunk to Juniper Switch2"
    mode: trunk
    trunk_allowed_vlans: [10, 20] # Only need these two for host traffic
    trunk_native_vlan: 99

juniper_switch1_interfaces:
  - name: ge-0/0/24 # Trunk to Switch1
    description: "Trunk to Cisco Switch1"
    mode: trunk
    trunk_allowed_vlans: [10, 20]
    trunk_native_vlan: 99

5. Create playbooks/provision_lab_vlans.yml:

# playbooks/provision_lab_vlans.yml
---
- name: Provision Lab VLANs and Interfaces
  hosts: network_devices
  gather_facts: false

  vars_files:
    - ../vlan_data.yml

  tasks:
    - name: Ensure VLANs exist (Cisco IOS XE)
      when: ansible_network_os == 'cisco.ios.ios'
      cisco.ios.vlan:
        vlan_id: ""
        name: ""
        state: present
      loop: ""

    - name: Ensure VLANs exist (Juniper Junos)
      when: ansible_network_os == 'juniper.junos.junos'
      juniper.junos.vlan:
        vlan_id: ""
        name: ""
        state: present
      loop: ""

    - name: Configure Interfaces on Cisco Switch1
      when: inventory_hostname == 'cisco_switch_1'
      cisco.ios.interfaces:
        config:
          - name: ""
            description: ""
            enabled: true
            switchport:
              mode: ""
              access:
                vlan: ""
              trunk:
                native_vlan: ""
                allowed_vlans: ""
        state: merged
      loop: ""

    - name: Configure Interfaces on Juniper Switch2
      when: inventory_hostname == 'juniper_switch_1'
      juniper.junos.interfaces:
        config:
          - name: ""
            description: ""
            unit:
              - name: 0
                family:
                  ethernet_switching:
                    interface_mode: ""
                    vlan:
                      members: "" # Juniper module expects comma-separated or list for members
                    native_vlan_id: ""
        state: merged
      loop: ""

    - name: Save configuration (Cisco IOS XE)
      when: inventory_hostname == 'cisco_switch_1'
      cisco.ios.config:
        save_when: modified

    - name: Save configuration (Juniper Junos)
      when: inventory_hostname == 'juniper_switch_1'
      juniper.junos.config:
        commit: yes
        diff: yes
        comment: "Ansible: Lab VLAN provisioning"

6. Execute the Playbook:

ansible-playbook -i hosts.ini playbooks/provision_lab_vlans.yml -vvv

Verification Steps

After the playbook runs, log into each switch and execute the appropriate verification commands (show vlan brief, show interfaces trunk, show vlans etc.) to confirm VLANs are created and interfaces are correctly configured.

Connectivity Test:

  • Assign HostA an IP address from VLAN 10 (e.g., 192.168.10.5/24).
  • Assign HostB an IP address from VLAN 20 (e.g., 192.168.20.5/24).
  • If a Layer 3 device is present, verify HostA can ping the default gateway for VLAN 10, and HostB for VLAN 20.
  • HostA should NOT be able to communicate directly with HostB without a Layer 3 device routing between VLANs.

Challenge Exercises

  1. Remove a VLAN: Modify vlan_data.yml to remove VLAN 20 and update the playbook to use state: absent for that VLAN, then re-run. Observe how Ansible handles deletion.
  2. Add a New VLAN: Add a new VLAN (e.g., VLAN 40: “IoT”) to vlan_data.yml and provision an existing unused port (e.g., GigabitEthernet0/3 on Switch1) as an access port for VLAN 40. Update the trunk links to allow VLAN 40.
  3. Refactor with Roles: Organize the playbook into Ansible roles for better reusability and maintainability (e.g., a vlans role and an interfaces role).

Best Practices Checklist

Adhering to these best practices will ensure robust, secure, and maintainable VLAN automation with Ansible:

  • [ ] Standardized Naming/Numbering: Establish a consistent VLAN ID and naming scheme across your organization (e.g., VLAN 10 for Corporate, 20 for Guest, 999 for Native Unused). Stick to larger numbers for flexibility (e.g., 10, 20, 30 instead of 1, 2, 3).
  • [ ] Source of Truth (SoT): Centralize VLAN definitions in a SoT (e.g., vlan_data.yml, a CMDB, or NetBox). Your Ansible playbooks should pull from this SoT.
  • [ ] Idempotency: Design playbooks to be idempotent, ensuring repeated runs yield the same configuration without error. Ansible network modules are generally idempotent by design.
  • [ ] Version Control: Store all Ansible playbooks, inventory, and variable files in a version control system (e.g., Git).
  • [ ] Ansible Vault: Encrypt all sensitive data (passwords, API keys) using Ansible Vault.
  • [ ] Modular Playbooks and Roles: Break down complex automation tasks into smaller, reusable roles and modules for better organization and maintainability.
  • [ ] Pre- and Post-Checks: Implement tasks in your playbooks to perform “show” commands before and after configuration changes to capture the state and verify success.
  • [ ] Error Handling: Include fail tasks or block/rescue sections to handle expected errors gracefully.
  • [ ] Documentation: Clearly document your Ansible project structure, playbooks, variables, and the intended purpose of each.
  • [ ] Continuous Integration/Continuous Deployment (CI/CD): Integrate your network automation workflows into a CI/CD pipeline for automated testing and deployment.
  • [ ] Native VLAN Security: Always change the native VLAN from VLAN 1 to an unused, unrouted VLAN on trunk ports.
  • [ ] Disable Unused Ports: Automate the shutdown of unused switch ports.
  • [ ] Explicit Configuration: Avoid relying on dynamic protocols like DTP; explicitly configure access and trunk modes.

What’s Next

This chapter provided a foundational understanding of automating VLAN provisioning with Ansible. You’ve learned about VLAN fundamentals, Ansible’s architecture, multi-vendor configuration, critical security considerations, and effective troubleshooting strategies. The hands-on lab provided practical experience in building and executing an Ansible playbook for VLAN management.

In the next chapter, we will delve deeper into Chapter 7: Advanced Network Automation with Ansible: Dynamic Inventory and CI/CD. This will cover using dynamic inventory sources (e.g., NetBox, cloud providers) to automatically populate your Ansible inventory, integrate playbooks into CI/CD pipelines for automated testing and deployment, and explore event-driven automation techniques to further enhance your network operations. You’ll gain skills to build more scalable, resilient, and fully automated network infrastructures.