+++
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:
- Define Desired State: Create variables or data structures (YAML dictionaries/lists) outlining the VLANs, their IDs, names, and associated interfaces.
- Playbook Execution: Run an Ansible playbook that uses network-specific modules. These modules handle the vendor-specific CLI or API interactions.
- 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: absentis used).
- 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 thenetwork_devicesgroup fromhosts.ini.gather_facts: false: Recommended for network devices as collecting facts can be slow or unsupported by some hardware/OS versions.vars_files: Importsvlan_data.ymlto 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_osis a variable set based on your inventory.- VLAN Modules:
cisco.ios.vlan,juniper.junos.vlan,arista.eos.vlanare used to create or ensure the presence of VLANs by their ID and name.state: presentmakes this task idempotent. - Interface Modules:
cisco.ios.interfaces,juniper.junos.interfaces,arista.eos.interfacesare used to configure the switch ports. These modules provide declarative options forswitchport mode,access vlan,trunk allowed vlans, andnative vlan. loop: Iterates over thedesired_vlansand 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
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 accessorswitchport mode trunk. Never useswitchport mode dynamic autoordynamic desirableon 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.
- Disable DTP: Manually configure all switch ports as either
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.
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.
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 accessorswitchport mode trunkon 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. Avoidswitchport trunk allowed vlan all. - [ ] Disable Unused Ports:
shutdowninterfaces 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 showOperational Mode: static accessandAccess Mode VLAN: 10.show interfaces GigabitEthernet0/24 trunk: Should showOperational Mode: trunk,Trunking Native Mode VLAN: 999, andTrunking 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 listCorporate,Guest,Voice,Native_Unusedwith correctTagIDs.show interfaces ge-0/0/1 extensive | match "Ethernet-switching|VLAN": Should showInterface mode: Access,Access VLAN: Corporate.show interfaces ge-0/0/24 extensive | match "Ethernet-switching|VLAN": Should showInterface 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 showOperational Mode: accessandAccess Mode VLAN: 10.show interfaces Ethernet1/24 trunk: Should showOperational Mode: trunk,Trunking Native Mode VLAN: 999, andTrunking VLANs Enabled: 10,20,30.
Troubleshooting Common Issues
| Issue | Description | Resolution Steps |
|---|---|---|
| VLAN Mismatch | A 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 Mismatch | Native 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 Mode | An 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 Trunk | A 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 Connectivity | Cable 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 Errors | Syntax 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 Gateway | VLANs 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. debugmodule: 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.
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 vlanon 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 vlanson trunk ports.
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.ymland playbooks should reflect a thoughtful, segmented VLAN design.
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.
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
- Set up an Ansible control node and inventory.
- Define desired VLANs (10, 20, 99) and interface configurations.
- Create an Ansible playbook to provision VLANs and configure access/trunk ports.
- Execute the playbook and verify configurations on both switches.
- 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
HostAan IP address from VLAN 10 (e.g., 192.168.10.5/24). - Assign
HostBan IP address from VLAN 20 (e.g., 192.168.20.5/24). - If a Layer 3 device is present, verify
HostAcan ping the default gateway for VLAN 10, andHostBfor VLAN 20. HostAshould NOT be able to communicate directly withHostBwithout a Layer 3 device routing between VLANs.
Challenge Exercises
- Remove a VLAN: Modify
vlan_data.ymlto remove VLAN 20 and update the playbook to usestate: absentfor that VLAN, then re-run. Observe how Ansible handles deletion. - Add a New VLAN: Add a new VLAN (e.g., VLAN 40: “IoT”) to
vlan_data.ymland provision an existing unused port (e.g.,GigabitEthernet0/3on Switch1) as an access port for VLAN 40. Update the trunk links to allow VLAN 40. - Refactor with Roles: Organize the playbook into Ansible roles for better reusability and maintainability (e.g., a
vlansrole and aninterfacesrole).
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
failtasks orblock/rescuesections 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.
Reference Links
- IEEE 802.1Q-2022 Standard: The latest specification for VLANs. standards.ieee.org
- Ansible Network Documentation: Comprehensive guide to Ansible network automation. docs.ansible.com
- Cisco IOS Modules for Ansible: docs.ansible.com/ansible/latest/collections/cisco/ios/index.html
- Juniper Junos Modules for Ansible: docs.ansible.com/ansible/latest/collections/juniper/junos/index.html
- Arista EOS Modules for Ansible: docs.ansible.com/ansible/latest/collections/arista/eos/index.html
- VLAN Best Practices (Cisco): cisco.com/c/en/us/support/docs/smb/routers/cisco-rv-series-small-business-routers/1778-tz-VLAN-Best-Practices-and-Security-Tips-for-Cisco-Business-Routers.html
- VLAN Hopping Attacks & Mitigation: imperva.com/learn/availability/vlan-hopping/
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.