Introduction

In the rapidly evolving landscape of network infrastructure, traditional manual configuration of VLANs is prone to errors, inconsistency, and slow deployment cycles. As networks scale and business demands accelerate, a more robust, auditable, and automated approach becomes indispensable. This chapter introduces the GitOps workflow for VLAN configuration management, a paradigm that brings the best practices of modern software development to network operations.

GitOps, at its core, leverages Git as the single source of truth for declarative infrastructure and application configurations. For VLANs, this means defining desired VLAN states in version-controlled files, with automated processes ensuring that the actual network state continuously converges with the state declared in Git.

What this chapter covers:

  • Core GitOps Principles: Understanding the foundational concepts that enable declarative, version-controlled network configuration.
  • VLAN Configuration as Code (IaC): Representing VLANs in structured, human-readable data formats.
  • CI/CD Pipeline Integration: How continuous integration and continuous deployment principles apply to network changes.
  • Multi-Vendor Automation: Practical examples using tools like Ansible and Terraform for Cisco, Juniper, and Arista devices.
  • Advanced VLAN Concepts: Incorporating IEEE 802.1Q and 802.1ad standards within an automated framework.
  • Security, Verification, and Troubleshooting: Ensuring robust, secure, and resilient VLAN deployments.
  • Performance Optimization: Strategies to fine-tune VLAN performance in large-scale environments.

Why it’s important:

Implementing a GitOps workflow for VLANs dramatically enhances operational efficiency, reduces human error, provides an immutable audit trail, simplifies disaster recovery, and ensures consistency across diverse network estates. This approach is critical for modern enterprises aiming for agility, reliability, and security in their network operations.

What you’ll be able to do after reading this chapter:

  • Design and implement a GitOps-driven pipeline for VLAN configuration.
  • Create declarative VLAN configurations for multi-vendor environments.
  • Automate VLAN deployment, modification, and deletion using Ansible and Terraform.
  • Apply advanced security best practices for VLANs in an automated fashion.
  • Effectively verify and troubleshoot VLAN issues within a GitOps framework.

Technical Concepts

GitOps Principles for Network Configuration

GitOps extends the DevOps philosophy by using Git as the declarative single source of truth for both infrastructure and applications. For network engineering, this means:

  1. Declarative Configuration: All VLAN configurations (IDs, names, port assignments, IP interfaces) are described in a declarative manner, specifying what the desired state is, not how to achieve it.
  2. Version Control (Git): Git acts as the central repository for all network configurations. Every change, review, and approval is tracked, providing an immutable audit log. This enables easy rollbacks to previous states.
  3. Pull Requests (PRs): Network changes are proposed via PRs, triggering reviews and automated checks (CI). This fosters collaboration and prevents unauthorized or erroneous configurations from being deployed.
  4. Automated Synchronization: A Continuous Delivery (CD) process automatically applies approved configuration changes from Git to the network devices. This can be a “push” model (CI/CD pipeline pushes changes) or a “pull” model (an agent on the network, or a controller, pulls changes from Git).
  5. Continuous Reconciliation: The system continuously monitors the actual state of the network and compares it to the desired state in Git. Any drift is detected and automatically rectified to maintain the desired configuration.

Benefits of GitOps for VLANs:

  • Increased Reliability: Version control and PRs reduce human error.
  • Faster MTTR (Mean Time To Recovery): Easy rollback to any known good state.
  • Enhanced Security: All changes are reviewed, audited, and approved.
  • Improved Compliance: An immutable audit trail of all configurations.
  • Scalability: Automate repetitive tasks across hundreds or thousands of devices.

VLAN Configuration as Code (IaC)

Infrastructure as Code (IaC) is fundamental to GitOps. For VLANs, this involves representing the desired state of VLANs and their assignments using structured data formats like YAML or JSON. This declarative approach allows automation tools to interpret the desired state and apply it to various network devices.

# Example: VLANs definition in a YAML file
---
vlans:
  - id: 10
    name: "PRODUCTION_SERVERS"
    description: "VLAN for critical production servers"
    ip_interface: "10.10.10.1/24"
    devices:
      - name: "core_switch_1"
        ports:
          - "GigabitEthernet1/0/1"
          - "GigabitEthernet1/0/2"
        trunk_ports:
          - "GigabitEthernet1/0/24"
      - name: "access_switch_2"
        ports:
          - "GigabitEthernet0/1"
  - id: 20
    name: "DEVELOPMENT_LAB"
    description: "VLAN for development environment"
    ip_interface: "10.20.20.1/24"
    devices:
      - name: "core_switch_1"
        ports: []
        trunk_ports:
          - "GigabitEthernet1/0/24"
      - name: "access_switch_1"
        ports:
          - "GigabitEthernet0/5"

CI/CD Pipeline for Network Automation

A CI/CD pipeline automates the lifecycle of network changes, from code commit to deployment.

  1. Commit: A network engineer pushes a change (e.g., a new VLAN definition) to a Git repository.
  2. CI (Continuous Integration):
    • Linting/Syntax Check: Ensures the configuration files adhere to proper YAML/JSON syntax.
    • Validation: Custom scripts or tools validate the proposed VLAN changes against business rules (e.g., VLAN ID ranges, naming conventions, preventing overlaps).
    • Pre-Flight Checks: (Optional) Simulate changes against device configurations or network state using tools like Batfish or network testing frameworks.
  3. PR Review & Approval: The proposed changes are reviewed by peers.
  4. CD (Continuous Deployment):
    • Deployment: Upon approval and merge to the main branch, automation tools (Ansible, Terraform) execute, applying the new VLAN configurations to the network devices. This might involve dry runs, phased rollouts, and pre/post-checks.
    • Verification: Automated tests confirm the deployed VLANs are operational and meet requirements (e.g., ping tests, show command parsing).
    • Rollback: If verification fails or issues are detected, the pipeline can automatically revert to the previous known-good configuration.

Protocol Specifications: IEEE 802.1Q and 802.1ad (QinQ)

GitOps manages the desired state of VLANs, which are defined by these IEEE standards.

  • IEEE 802.1Q (VLAN Tagging): This standard defines the architecture for Virtual Local Area Networks and specifies the VLAN tag format that is inserted into an Ethernet frame. The tag includes a 12-bit VLAN Identifier (VID), allowing for 4094 unique VLANs (0 and 4095 are reserved).
    • RFC Reference: While 802.1Q is an IEEE standard, its principles are widely implemented and understood in network RFCs related to Layer 2 and Layer 3 operations. The latest version is IEEE 802.1Q-2022.
  • IEEE 802.1ad (QinQ / Provider Bridging): An amendment to 802.1Q, also known as Q-in-Q, allows for the insertion of multiple 802.1Q tags into a single Ethernet frame. This is crucial for service providers to transport customer VLANs while maintaining their own network’s segmentation, effectively providing “VLANs within VLANs.” The outer tag is often called the “Service VLAN ID” (S-VID) and the inner the “Customer VLAN ID” (C-VID).

GitOps Workflow Architecture Diagram

@startuml
!theme mars

' Define all elements first
actor "Network Engineer" as NE
rectangle "Git Repository (Single Source of Truth)" as Git
cloud "CI/CD Pipeline" as CICD
component "Ansible Automation Engine" as Ansible
component "Terraform Provisioner" as Terraform
node "Cisco Switch" as CiscoSW
node "Juniper Router" as JuniperRT
node "Arista Switch" as AristaSW
rectangle "Monitoring & Alerting" as Monitor

' Then connect them
NE -- Git : 1. Commit/Push Config Changes
Git --> CICD : 2. Webhook/Poll (New Changes)

CICD -- Git : 3. CI: Linting, Validation (Pull Config)
CICD --> Ansible : 4. CD: Trigger Ansible Playbook
CICD --> Terraform : 4. CD: Trigger Terraform Apply

Ansible --> CiscoSW : 5. Apply VLAN Config
Ansible --> JuniperRT
Ansible --> AristaSW

Terraform --> CiscoSW : 5. Provision Network Resources
Terraform --> JuniperRT
Terraform --> AristaSW

CiscoSW --> Monitor : 6. Telemetry/Logs
JuniperRT --> Monitor
AristaSW --> Monitor

Monitor -- NE : 7. Alerts on Drift/Issues

@enduml

802.1Q VLAN Tag Packet Structure

When an Ethernet frame is tagged with 802.1Q, a 4-byte tag header is inserted between the Source Address and the EtherType/Length fields.

packetdiag {
  colwidth = 32
  0-47: Destination MAC Address
  48-95: Source MAC Address
  96-111: EtherType (0x8100 for 802.1Q)
  112-114: Priority Code Point (PCP) (3 bits)
  115: Drop Eligible Indicator (DEI) (1 bit)
  116-127: VLAN Identifier (VID) (12 bits)
  128-143: Length/EtherType of original frame
  144-X: Original Ethernet Payload
  X-Y: Frame Check Sequence (FCS)
}

Configuration Examples

This section provides example configurations for creating VLANs and assigning them to ports on Cisco, Juniper, and Arista devices. These are the target configurations that an automation tool would push.

Scenario: Creating VLAN 10 (PRODUCTION_SERVERS) and VLAN 20 (DEVELOPMENT_LAB)

  • VLAN 10: PRODUCTION_SERVERS, IP 10.10.10.1/24 (on L3 switch/router)
  • VLAN 20: DEVELOPMENT_LAB, IP 10.20.20.1/24 (on L3 switch/router)
  • Access Port: GigabitEthernet1/0/1 for VLAN 10, GigabitEthernet1/0/5 for VLAN 20
  • Trunk Port: GigabitEthernet1/0/24 (allowing VLANs 10, 20)

Cisco IOS XE Configuration

! Configure VLANs
vlan 10
 name PRODUCTION_SERVERS
!
vlan 20
 name DEVELOPMENT_LAB
!
! Configure Layer 3 VLAN interfaces (SVI)
interface Vlan10
 description IP Interface for PRODUCTION_SERVERS VLAN
 ip address 10.10.10.1 255.255.255.0
 no shutdown
!
interface Vlan20
 description IP Interface for DEVELOPMENT_LAB VLAN
 ip address 10.20.20.1 255.255.255.0
 no shutdown
!
! Configure an access port for VLAN 10
interface GigabitEthernet1/0/1
 description Access port for Production Server
 switchport mode access
 switchport access vlan 10
 switchport nonegotiate
 spanning-tree portfast
!
! Configure an access port for VLAN 20
interface GigabitEthernet1/0/5
 description Access port for Development Lab PC
 switchport mode access
 switchport access vlan 20
 switchport nonegotiate
 spanning-tree portfast
!
! Configure a trunk port allowing VLANs 10 and 20
! WARNING: Ensure native VLAN is secure and not VLAN 1.
! Best practice: Set an unused VLAN as native or explicitly remove native VLAN from trunk.
interface GigabitEthernet1/0/24
 description Trunk to other Core Switch
 switchport mode trunk
 switchport trunk allowed vlan 10,20
 switchport trunk native vlan 999 ! Assign an unused VLAN as native
 switchport nonegotiate
!

Juniper Junos OS Configuration

# Configure VLANs
set vlans PRODUCTION_SERVERS vlan-id 10
set vlans DEVELOPMENT_LAB vlan-id 20
#
# Configure Layer 3 VLAN interfaces (IRB - Integrated Routing and Bridging)
set interfaces irb unit 10 family inet address 10.10.10.1/24
set interfaces irb unit 20 family inet address 10.20.20.1/24
#
# Bind IRB interfaces to VLANs
set vlans PRODUCTION_SERVERS l3-interface irb.10
set vlans DEVELOPMENT_LAB l3-interface irb.20
#
# Configure an access port for 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 PRODUCTION_SERVERS
#
# Configure an access port for VLAN 20
set interfaces ge-0/0/5 unit 0 family ethernet-switching interface-mode access
set interfaces ge-0/0/5 unit 0 family ethernet-switching vlan members DEVELOPMENT_LAB
#
# Configure a trunk port allowing VLANs 10 and 20
# WARNING: Ensure native VLAN is secure and not VLAN 1.
# Best practice: Assign an unused VLAN as native or disable native VLAN if possible.
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 [ PRODUCTION_SERVERS DEVELOPMENT_LAB ]
set interfaces ge-0/0/24 unit 0 family ethernet-switching native-vlan-id 999
#

Arista EOS Configuration

! Configure VLANs
vlan 10
 name PRODUCTION_SERVERS
!
vlan 20
 name DEVELOPMENT_LAB
!
! Configure Layer 3 VLAN interfaces (SVI)
interface Vlan10
 description IP Interface for PRODUCTION_SERVERS VLAN
 ip address 10.10.10.1/24
 no shutdown
!
interface Vlan20
 description IP Interface for DEVELOPMENT_LAB VLAN
 ip address 10.20.20.1/24
 no shutdown
!
! Configure an access port for VLAN 10
interface Ethernet1/1
 description Access port for Production Server
 switchport mode access
 switchport access vlan 10
 switchport host
!
! Configure an access port for VLAN 20
interface Ethernet1/5
 description Access port for Development Lab PC
 switchport mode access
 switchport access vlan 20
 switchport host
!
! Configure a trunk port allowing VLANs 10 and 20
! WARNING: Ensure native VLAN is secure and not VLAN 1.
! Best practice: Set an unused VLAN as native or explicitly remove native VLAN from trunk.
interface Ethernet1/24
 description Trunk to other Core Switch
 switchport mode trunk
 switchport trunk allowed vlan 10,20
 switchport trunk native vlan 999
!

Network Diagrams

Multi-Vendor Core & Access Layer Topology

This diagram illustrates a common enterprise setup with core and access layer switches, demonstrating where VLANs are typically configured and how they span the network.

nwdiag {
  network core_network {
    address = "10.0.0.0/16"
    core_switch_cisco [address = "10.0.0.1", shape = cisco];
    core_switch_juniper [address = "10.0.0.2", shape = juniper];
  }

  network access_network_1 {
    address = "10.10.10.0/24"
    access_switch_arista [address = "10.10.10.254", shape = arista];
    access_switch_arista -- core_switch_cisco [label = "Trunk (VLAN 10, 20)"];
  }

  network access_network_2 {
    address = "10.20.20.0/24"
    access_switch_cisco [address = "10.20.20.254", shape = cisco];
    access_switch_cisco -- core_switch_juniper [label = "Trunk (VLAN 10, 20)"];
  }

  group {
    label = "VLAN 10: Production Servers";
    color = "#CCFFCC";
    server1 [address = "10.10.10.10"];
    server2 [address = "10.10.10.11"];
    server1 -- access_switch_arista [label = "Access Port (VLAN 10)"];
    server2 -- access_switch_arista [label = "Access Port (VLAN 10)"];
  }

  group {
    label = "VLAN 20: Development Lab";
    color = "#FFCCCC";
    dev_pc1 [address = "10.20.20.10"];
    dev_pc2 [address = "10.20.20.11"];
    dev_pc1 -- access_switch_cisco [label = "Access Port (VLAN 20)"];
    dev_pc2 -- access_switch_cisco [label = "Access Port (VLAN 20)"];
  }
}

GitOps Configuration Flow

This diagram visualizes the end-to-end GitOps flow for managing VLAN configurations.

# GitOps Configuration Flow
direction: right

engineer: Network Engineer
git_repo: Git Repository {
  label: "Network Config (YAML)"
  shape: cylinder
}
ci_cd_pipeline: CI/CD Pipeline {
  validate: Validation & Linting
  deploy: Deployment
}
ansible_engine: Ansible Engine
devices: Network Devices {
  cisco_sw: Cisco Switch
  juniper_rt: Juniper Router
  arista_sw: Arista Switch
}
monitor: Monitoring & Telemetry {
  shape: cloud
}

engineer -> git_repo: 1. Push Config Changes (PR)
git_repo -> ci_cd_pipeline.validate: 2. Trigger CI
ci_cd_pipeline.validate -> ci_cd_pipeline.deploy: 3. CI Pass (Approved Merge)
ci_cd_pipeline.deploy -> ansible_engine: 4. Trigger Playbook
ansible_engine -> cisco_sw: 5. Apply Config
ansible_engine -> juniper_rt
ansible_engine -> arista_sw

devices -> monitor: 6. Telemetry & Logs
monitor -> engineer: 7. Alerts (Drift, Issues)

cisco_sw.label: "VLANs Configured"
juniper_rt.label: "VLANs Configured"
arista_sw.label: "VLANs Configured"

engineer.style.font-color: black

Automation Examples

The power of GitOps for VLAN management comes from automation. Here, we demonstrate how Ansible and Terraform can be used.

Ansible Playbook for Multi-Vendor VLAN Configuration

This Ansible playbook reads VLAN data from the group_vars/all/vlans.yml file and applies it to Cisco, Juniper, and Arista devices using their respective network modules.

inventory.ini:

[cisco_devices]
cisco_core_switch_1 ansible_host=192.168.1.11

[juniper_devices]
juniper_core_router_1 ansible_host=192.168.1.12

[arista_devices]
arista_access_switch_1 ansible_host=192.168.1.13

[network_devices:children]
cisco_devices
juniper_devices
arista_devices

[all:vars]
ansible_user=admin
ansible_password=Cisco123!
ansible_network_os_cisco_devices=ios
ansible_network_os_juniper_devices=junos
ansible_network_os_arista_devices=eos
ansible_become=yes
ansible_become_method=enable
ansible_connection=network_cli

group_vars/all/vlans.yml:

---
# Define desired VLAN states across the network
# This file serves as the source of truth for VLANs
network_vlans:
  - id: 10
    name: PRODUCTION_SERVERS
    description: VLAN for critical production servers
    ip_address: 10.10.10.1/24
    devices:
      cisco_core_switch_1:
        access_ports: ['GigabitEthernet1/0/1', 'GigabitEthernet1/0/2']
        trunk_ports: ['GigabitEthernet1/0/24']
        trunk_allowed_vlan: '10,20'
        trunk_native_vlan: 999
      arista_access_switch_1:
        access_ports: ['Ethernet1/1']
        trunk_ports: ['Ethernet1/24']
        trunk_allowed_vlan: '10,20'
        trunk_native_vlan: 999
      juniper_core_router_1:
        access_ports: ['ge-0/0/1']
        trunk_ports: ['ge-0/0/24']
        trunk_allowed_vlan: '10,20' # Juniper uses members [VLAN_NAME]
        trunk_native_vlan: 999
  - id: 20
    name: DEVELOPMENT_LAB
    description: VLAN for development environment
    ip_address: 10.20.20.1/24
    devices:
      cisco_core_switch_1:
        access_ports: ['GigabitEthernet1/0/5']
        trunk_ports: ['GigabitEthernet1/0/24']
        trunk_allowed_vlan: '10,20'
        trunk_native_vlan: 999
      arista_access_switch_1:
        access_ports: ['Ethernet1/5']
        trunk_ports: ['Ethernet1/24']
        trunk_allowed_vlan: '10,20'
        trunk_native_vlan: 999
      juniper_core_router_1:
        access_ports: ['ge-0/0/5']
        trunk_ports: ['ge-0/0/24']
        trunk_allowed_vlan: '10,20' # Juniper uses members [VLAN_NAME]
        trunk_native_vlan: 999

configure_vlans.yml playbook:

---
- name: Apply VLAN Configuration using GitOps Workflow
  hosts: network_devices
  connection: network_cli
  gather_facts: false
  
  tasks:
    - name: Ensure VLANs exist (Cisco, Arista, Juniper)
      tags: configure_vlans
      block:
        - name: Configure VLAN on Cisco IOS/IOS-XE devices
          ansible.netcommon.cli_config:
            config: |
              vlan 
               name 
            save_when: modified
          when:
            - ansible_network_os == 'ios'
            - inventory_hostname in item.devices | dict2items | map(attribute='key') | list
          loop: ""
          loop_control:
            label: "Cisco VLAN "

        - name: Configure VLAN on Juniper Junos devices
          juniper.device.vlans:
            config:
              - name: ""
                vlan_id: ""
            state: merged
          when:
            - ansible_network_os == 'junos'
            - inventory_hostname in item.devices | dict2items | map(attribute='key') | list
          loop: ""
          loop_control:
            label: "Juniper VLAN "

        - name: Configure VLAN on Arista EOS devices
          arista.eos.vlan:
            vlan_id: ""
            name: ""
            state: present
          when:
            - ansible_network_os == 'eos'
            - inventory_hostname in item.devices | dict2items | map(attribute='key') | list
          loop: ""
          loop_control:
            label: "Arista VLAN "

    - name: Configure VLAN Interfaces (SVI/IRB) and Port Assignments
      tags: configure_interfaces
      block:
        - name: Configure VLAN Interfaces and ports on Cisco IOS/IOS-XE
          ansible.netcommon.cli_config:
            config: |
              {% for vlan in network_vlans %}
                {% if inventory_hostname in vlan.devices %}
                  interface Vlan
                   description IP Interface for  VLAN
                   ip address  
                   no shutdown
                  {% for port in vlan.devices[inventory_hostname].access_ports %}
                    interface 
                     description Access port for 
                     switchport mode access
                     switchport access vlan 
                     switchport nonegotiate
                     spanning-tree portfast
                  {% endfor %}
                  {% for port in vlan.devices[inventory_hostname].trunk_ports %}
                    interface 
                     description Trunk to other switch
                     switchport mode trunk
                     switchport trunk allowed vlan 
                     switchport trunk native vlan 
                     switchport nonegotiate
                  {% endfor %}
                {% endif %}
              {% endfor %}
            save_when: modified
          when: ansible_network_os == 'ios'

        - name: Configure VLAN Interfaces and ports on Juniper Junos
          juniper.device.config:
            lines:
              {% for vlan in network_vlans %}
                {% if inventory_hostname in vlan.devices %}
                  - "set interfaces irb unit  family inet address "
                  - "set vlans  l3-interface irb."
                  {% for port in vlan.devices[inventory_hostname].access_ports %}
                    - "set interfaces  unit 0 family ethernet-switching interface-mode access"
                    - "set interfaces  unit 0 family ethernet-switching vlan members "
                  {% endfor %}
                  {% for port in vlan.devices[inventory_hostname].trunk_ports %}
                    - "set interfaces  unit 0 family ethernet-switching interface-mode trunk"
                    - "set interfaces  unit 0 family ethernet-switching vlan members [ {% for member_vlan_id in vlan.devices[inventory_hostname].trunk_allowed_vlan.split(',') %}  {% endfor %} ]"
                    - "set interfaces  unit 0 family ethernet-switching native-vlan-id "
                  {% endfor %}
                {% endif %}
              {% endfor %}
            config_mode: exclusive
            comment: "Ansible: Configure VLAN Interfaces and ports"
          when: ansible_network_os == 'junos'

        - name: Configure VLAN Interfaces and ports on Arista EOS
          ansible.netcommon.cli_config:
            config: |
              {% for vlan in network_vlans %}
                {% if inventory_hostname in vlan.devices %}
                  interface Vlan
                   description IP Interface for  VLAN
                   ip address 
                   no shutdown
                  {% for port in vlan.devices[inventory_hostname].access_ports %}
                    interface 
                     description Access port for 
                     switchport mode access
                     switchport access vlan 
                     switchport host
                  {% endfor %}
                  {% for port in vlan.devices[inventory_hostname].trunk_ports %}
                    interface 
                     description Trunk to other switch
                     switchport mode trunk
                     switchport trunk allowed vlan 
                     switchport trunk native vlan 
                  {% endfor %}
                {% endif %}
              {% endfor %}
            save_when: modified
          when: ansible_network_os == 'eos'

    - name: Run verification commands
      ansible.builtin.include_tasks: verification_tasks.yml
      tags: verify_config

verification_tasks.yml (included in the main playbook):

- name: Verify VLANs on Cisco IOS/IOS-XE
  ansible.netcommon.cli_command:
    command: "show vlan brief"
  register: cisco_vlan_output
  when: ansible_network_os == 'ios'

- name: Display Cisco VLANs
  ansible.builtin.debug:
    msg: ""
  when: ansible_network_os == 'ios'

- name: Verify VLANs on Juniper Junos
  ansible.netcommon.cli_command:
    command: "show vlans"
  register: juniper_vlan_output
  when: ansible_network_os == 'junos'

- name: Display Juniper VLANs
  ansible.builtin.debug:
    msg: ""
  when: ansible_network_os == 'junos'

- name: Verify VLANs on Arista EOS
  ansible.netcommon.cli_command:
    command: "show vlan"
  register: arista_vlan_output
  when: ansible_network_os == 'eos'

- name: Display Arista VLANs
  ansible.builtin.debug:
    msg: ""
  when: ansible_network_os == 'eos'

Terraform for Network Infrastructure Provisioning

While Ansible excels at configuration management on existing devices, Terraform is ideal for provisioning the underlying network infrastructure itself, especially in cloud or SDN/ACI environments. Terraform can define VPCs, subnets, and even virtual network appliances, which then form the foundation for VLAN-like segmentation or where Ansible would then configure VLANs on physical devices.

Example: Terraform for AWS VPC and Subnet Provisioning (conceptual VLAN segmentation)

# main.tf for AWS VPC provisioning
provider "aws" {
  region = "us-east-1"
}

resource "aws_vpc" "prod_vpc" {
  cidr_block = "10.10.0.0/16"
  enable_dns_hostnames = true
  tags = {
    Name = "Production-VPC"
    ManagedBy = "Terraform-GitOps"
  }
}

resource "aws_subnet" "prod_subnet_servers" {
  vpc_id            = aws_vpc.prod_vpc.id
  cidr_block        = "10.10.10.0/24" # Corresponds to VLAN 10 functionally
  availability_zone = "us-east-1a"
  tags = {
    Name = "Production-Servers-Subnet"
    VLAN_ID_Concept = "10" # Conceptual mapping for documentation
  }
}

resource "aws_subnet" "prod_subnet_dev" {
  vpc_id            = aws_vpc.prod_vpc.id
  cidr_block        = "10.10.20.0/24" # Corresponds to VLAN 20 functionally
  availability_zone = "us-east-1a"
  tags = {
    Name = "Development-Lab-Subnet"
    VLAN_ID_Concept = "20" # Conceptual mapping for documentation
  }
}

output "prod_vpc_id" {
  value = aws_vpc.prod_vpc.id
  description = "The ID of the production VPC"
}

output "prod_subnet_servers_id" {
  value = aws_subnet.prod_subnet_servers.id
  description = "The ID of the production servers subnet"
}

This Terraform example provisions cloud resources that functionally replicate the isolation provided by VLANs. Ansible could then connect to instances within these subnets and perform further configuration.

Python for State Synchronization (Netmiko)

Python scripts, often using libraries like Netmiko, NAPALM, or Nornir, are crucial for custom automation tasks, fetching device state, or as components within larger CI/CD pipelines.

Example: Python script to fetch VLAN configuration (Netmiko)

import os
from netmiko import ConnectHandler

# Device details (ideally from a secure config management system or environment variables)
cisco_device = {
    "device_type": "cisco_ios",
    "host": os.getenv("CISCO_HOST"),
    "username": os.getenv("CISCO_USER"),
    "password": os.getenv("CISCO_PASSWORD"),
    "secret": os.getenv("CISCO_ENABLE_SECRET"),
}

juniper_device = {
    "device_type": "juniper_junos",
    "host": os.getenv("JUNIPER_HOST"),
    "username": os.getenv("JUNIPER_USER"),
    "password": os.getenv("JUNIPER_PASSWORD"),
}

def get_vlan_config(device):
    """Connects to a device and retrieves VLAN configuration."""
    print(f"Connecting to {device['host']}...")
    try:
        with ConnectHandler(**device) as net_connect:
            if device['device_type'] == 'cisco_ios':
                output = net_connect.send_command("show vlan brief", use_textfsm=True)
            elif device['device_type'] == 'juniper_junos':
                output = net_connect.send_command("show vlans", use_textfsm=True)
            elif device['device_type'] == 'arista_eos': # Assuming similar command to Cisco
                output = net_connect.send_command("show vlan", use_textfsm=True)
            else:
                print(f"Unsupported device type: {device['device_type']}")
                return None
            print(f"VLAN configuration from {device['host']}:\n{output}")
            return output
    except Exception as e:
        print(f"Error connecting to {device['host']}: {e}")
        return None

if __name__ == "__main__":
    # Ensure environment variables are set for actual execution
    # For testing, you might hardcode values or use a .env file
    # Example:
    # export CISCO_HOST='192.168.1.11'
    # export CISCO_USER='admin'
    # export CISCO_PASSWORD='password'
    # export CISCO_ENABLE_SECRET='enable_password'

    print("Fetching Cisco VLANs:")
    cisco_vlans = get_vlan_config(cisco_device)

    print("\nFetching Juniper VLANs:")
    juniper_vlans = get_vlan_config(juniper_device)

    # You can add Arista device details and call get_vlan_config as well

Security Considerations

VLANs are a foundational element for network segmentation and security, but they are not inherently secure. A GitOps approach helps by enforcing review and auditability, but specific security measures must be part of the declarative configuration.

Attack Vectors

  1. VLAN Hopping:
    • Switch Spoofing (DTP): An attacker sends Dynamic Trunking Protocol (DTP) messages to a switch, tricking it into forming a trunk link. Once a trunk, the attacker can access all VLANs allowed on that trunk.
    • Double Tagging (802.1Q Tunneling): An attacker encapsulates a frame with two 802.1Q tags. If the first tag matches the native VLAN of the trunk, the switch strips it, exposing the second tag which then allows the frame to enter a targeted VLAN.
  2. MAC Flooding: Overwhelming the switch’s MAC address table, forcing it into a hub-like state where it floods all traffic, making it visible to attackers.
  3. DHCP Snooping Bypass: Exploiting DHCP to gain unauthorized IP addresses or launch denial-of-service attacks.
  4. ARP Spoofing: Intercepting traffic by falsely associating MAC addresses with IP addresses.

Mitigation Strategies

  1. Disable DTP: Manually configure trunk links (e.g., switchport mode trunk on Cisco, interface-mode trunk on Juniper/Arista) and explicitly disable DTP (switchport nonegotiate on Cisco).
  2. Change Native VLAN: Assign an unused, dedicated VLAN ID (e.g., VLAN 999) as the native VLAN on all trunks, and ensure no devices are on this VLAN. Better yet, configure trunks to not have a native VLAN if the platform supports it and it’s compatible with connected devices.
  3. Remove Unused VLANs from Trunks: Implement VLAN pruning (e.g., switchport trunk allowed vlan <list> on Cisco) to restrict which VLANs are propagated over trunk links.
  4. Port Security: Limit the number of MAC addresses allowed on an access port. Dynamically learn MACs or statically configure them. (e.g., switchport port-security on Cisco).
  5. Disable Unused Ports: Shut down all unused physical switch ports (shutdown command).
  6. Storm Control: Prevent broadcast, multicast, and unicast storms on interfaces.
  7. DHCP Snooping: Enable DHCP snooping to validate DHCP messages and prevent rogue DHCP servers.
  8. Dynamic ARP Inspection (DAI): Mitigate ARP spoofing attacks by validating ARP packets.
  9. IP Source Guard: Prevent IP spoofing by filtering traffic based on source IP and MAC addresses.
  10. Private VLANs (PVLANs): Isolate ports within the same VLAN, preventing communication between devices on specific ports even if they are in the same subnet. This provides micro-segmentation at Layer 2.

Security Configuration Examples (Cisco IOS XE)

! Mitigation: Disable DTP, change native VLAN, prune allowed VLANs
interface GigabitEthernet1/0/24
 description Secure Trunk
 switchport mode trunk
 switchport nonegotiate
 switchport trunk allowed vlan 10,20,30,40 ! Only explicitly allowed
 switchport trunk native vlan 999        ! Unused VLAN for native

! Mitigation: Port Security on Access Ports
interface GigabitEthernet1/0/1
 description Secure Access Port
 switchport mode access
 switchport access vlan 10
 switchport port-security
 switchport port-security maximum 2     ! Allow max 2 MAC addresses
 switchport port-security violation restrict ! Drop packets, send SNMP trap
 switchport port-security mac-address sticky ! Learn MACs dynamically
!
! Mitigation: Disable unused ports
interface GigabitEthernet1/0/10
 description Unused port
 shutdown
!
! Mitigation: DHCP Snooping (globally and per VLAN)
ip dhcp snooping
ip dhcp snooping vlan 10,20
ip dhcp snooping database flash:dhcp_snooping_db
!
! Mitigation: Trust DHCP Snooping on Uplinks (Trunks)
interface GigabitEthernet1/0/24
 ip dhcp snooping trust
!
! Mitigation: Dynamic ARP Inspection (globally and per VLAN)
ip arp inspection vlan 10,20
!
! Mitigation: Trust DAI on Uplinks (Trunks)
interface GigabitEthernet1/0/24
 ip arp inspection trust
!
! Mitigation: IP Source Guard (on access ports)
interface GigabitEthernet1/0/1
 ip verify source port-security
!

Security Warning: Improper configuration of VLANs, especially native VLANs on trunks, DTP settings, and port security, can lead to network vulnerabilities. Always validate changes thoroughly in a lab environment before deploying to production. GitOps helps by making such validations part of the automated workflow.

Verification & Troubleshooting

Even with GitOps, issues can arise from incorrect declarative state, automation tool failures, or unexpected device behavior. A robust verification and troubleshooting strategy is essential.

Verification Commands

These commands confirm the operational status of VLANs and their interfaces.

Cisco IOS XE:

show vlan brief
show interface status
show interface trunk
show interface GigabitEthernet1/0/1 switchport
show ip interface brief
show run | section vlan|interface

Juniper Junos OS:

show vlans
show interfaces terse | grep ethernet-switching
show interfaces ge-0/0/1
show interfaces descriptions
show interfaces irb

Arista EOS:

show vlan
show interfaces status
show interfaces trunk
show interfaces Ethernet1/1 switchport
show ip interface brief
show running-config | section vlan|interface

Expected Output (Example for Cisco show vlan brief)

VLAN Name                             Status    Ports
---- -------------------------------- --------- -------------------------------
1    default                          active    Gi1/0/10, Gi1/0/11, Gi1/0/12
10   PRODUCTION_SERVERS               active    Gi1/0/1, Gi1/0/2, Gi1/0/24
20   DEVELOPMENT_LAB                  active    Gi1/0/5, Gi1/0/24
999  UNUSED_NATIVE_VLAN               active
  • Annotation: This output confirms that VLANs 10, 20, and 999 are active and correctly associated with their respective access and trunk ports.

Troubleshooting Common Issues

IssueProbable CauseDebug Commands / Verification StepsResolution Steps
VLAN ID MismatchInconsistent VLAN ID configured across devices.show vlan brief, show interfaces trunk (Cisco)Ensure VLAN IDs are uniform across all devices in the declarative config.
Trunk MisconfigurationMode mismatch (access/trunk), allowed VLANs.show interfaces trunk (all vendors)Correct trunk mode, ensure allowed VLANs list is correct, check native VLAN.
Native VLAN MismatchNative VLAN differs on connected trunk ports.show interfaces trunk (Cisco/Arista), show interfaces (Juniper)Standardize native VLAN across the trunk, ideally to an unused VLAN.
Access Port Incorrect VLANPort assigned to wrong VLAN or configured as trunk.show interfaces <port> switchport (Cisco/Arista)Set port mode to access and assign correct access VLAN.
Layer 3 SVI/IRB IssuesIP address mismatch, no shutdown missing, no IP.show ip interface brief (Cisco/Arista), show interfaces irb (Juniper)Configure correct IP, ensure interface is no shutdown, verify VLAN exists.
Spanning Tree Protocol (STP) IssuesRoot bridge election, blocking due to loop.show spanning-tree active (Cisco/Arista)Verify STP configuration per VLAN, ensure root bridge is as intended.
VLAN Hopping Attack (post-mortem)DTP enabled, default native VLAN.show interfaces trunk, show cdp neighbors detail (Cisco)Implement all VLAN security mitigations (disable DTP, secure native VLAN).

Root Cause Analysis in GitOps: When an issue is identified, the first step is to verify the desired state in Git. If the desired state is correct, but the actual state is not, the issue lies in the automation pipeline or the device itself. If the desired state in Git is incorrect, then the issue lies in the design or the PR approval process, requiring a new PR to fix the declarative configuration.

Performance Optimization

Optimizing VLAN performance is critical for large enterprise networks to ensure efficient traffic flow and resource utilization.

  1. VLAN Pruning: Prevents broadcast, unknown unicast, and multicast traffic for a specific VLAN from being sent across trunk links where that VLAN has no active members.
    • Cisco (VTP Pruning): vtp mode transparent, vtp pruning. Or manually switchport trunk allowed vlan.
    • Juniper: Configure no-pruning or specify allowed VLANs on trunks.
    • Arista: switchport trunk allowed vlan
    • Benefit: Reduces unnecessary traffic, saves bandwidth on trunk links, and improves overall network efficiency.
  2. Spanning Tree Protocol (STP) Optimization:
    • Rapid PVST+ (RPVST+) / MSTP: Ensure fast convergence and stability across VLANs. RPVST+ runs a separate STP instance for each VLAN, while MSTP groups VLANs into instances to reduce overhead.
    • PortFast/Edge Port: Configure access ports connected to end devices with PortFast (Cisco) or edge port (Juniper/Arista) to bypass the listening/learning states, allowing immediate connectivity and preventing unnecessary TCNs.
    • BPDU Guard/Root Guard: Protect the STP topology by preventing unauthorized devices from becoming root bridges or injecting BPDUs.
  3. Jumbo Frames: If applications require large packets (e.g., storage networks, data center interconnects), configure jumbo frames (MTU > 1500 bytes) consistently across all devices and VLANs in the path.
  4. Broadcast Domain Sizing: Keep VLANs appropriately sized to minimize broadcast traffic. Too large VLANs can impact performance and security.
  5. Quality of Service (QoS): Implement QoS policies to prioritize critical VLAN traffic (e.g., voice, video) over less sensitive traffic. VLAN tags (PCP bits in 802.1Q) can be used for marking.

Network Diagram with VLAN Pruning

This diagram illustrates how VLAN pruning can be used to optimize trunk links by preventing specific VLAN traffic from propagating where it’s not needed.

@startuml
!theme mars

' Define elements
cloud "Internet/MPLS" as CLOUD
node "Core Router" as R1
node "Core Switch 1" as CS1
node "Core Switch 2" as CS2
node "Access Switch A" as ASA
node "Access Switch B" as ASB

rectangle "VLAN 10 (Sales)" as VLAN10_A
rectangle "VLAN 20 (Marketing)" as VLAN20_A
rectangle "VLAN 30 (Engineering)" as VLAN30_A

rectangle "VLAN 10 (Sales)" as VLAN10_B
rectangle "VLAN 20 (Marketing)" as VLAN20_B
rectangle "VLAN 40 (Guest)" as VLAN40_B

' Connections
CLOUD -- R1
R1 -- CS1 : "Trunk to Core"

CS1 -- CS2 : "Core Interconnect (Trunk - ALL)"
CS1 -- ASA : "Trunk (VLAN 10,20,30 - Pruned)"
CS2 -- ASB : "Trunk (VLAN 10,20,40 - Pruned)"

ASA -- VLAN10_A : "Access"
ASA -- VLAN20_A : "Access"
ASA -- VLAN30_A : "Access"

ASB -- VLAN10_B : "Access"
ASB -- VLAN20_B : "Access"
ASB -- VLAN40_B : "Access"

' Emphasize pruning
note right of CS1
  Trunk to ASA:
  VLANs 10,20,30
  (VLAN 40 pruned)
end note

note right of CS2
  Trunk to ASB:
  VLANs 10,20,40
  (VLAN 30 pruned)
end note

@enduml

Hands-On Lab: Implementing GitOps for a New VLAN

This lab simulates adding a new VLAN using a GitOps workflow.

Lab Topology

nwdiag {
  network management_network {
    address = "192.168.1.0/24"
    lab_server [address = "192.168.1.100", shape = server, description = "Git/Ansible Host"];
    core_switch_1 [address = "192.168.1.11", shape = cisco];
    access_switch_1 [address = "192.168.1.13", shape = arista];
  }

  network vlan_10_prod {
    address = "10.10.10.0/24"
    prod_server_a [address = "10.10.10.5"];
  }

  network vlan_20_dev {
    address = "10.20.20.0/24"
    dev_pc_a [address = "10.20.20.5"];
  }

  group {
    label = "VLAN 10 Devices";
    color = "#CCFFCC";
    prod_server_a -- access_switch_1 [label = "E1/1 (Access VLAN 10)"];
  }

  group {
    label = "VLAN 20 Devices";
    color = "#FFCCCC";
    dev_pc_a -- access_switch_1 [label = "E1/5 (Access VLAN 20)"];
  }

  core_switch_1 -- access_switch_1 [label = "Gi1/0/24 - E1/24 (Trunk ALL)"];
}

Objectives

  1. Add a new VLAN (VLAN 30, GUEST_WIFI) to the declarative configuration in Git.
  2. Modify the Ansible playbook to include this new VLAN.
  3. Simulate a Git Pull Request process.
  4. Execute the Ansible playbook to apply the changes to core_switch_1 (Cisco) and access_switch_1 (Arista).
  5. Verify the new VLAN’s presence on both switches.

Step-by-Step Configuration

Prerequisites:

  • A Linux host (lab_server) with Git and Ansible installed.
  • Access to core_switch_1 (Cisco IOS-XE) and access_switch_1 (Arista EOS) with SSH credentials.
  • Initial inventory.ini and group_vars/all/vlans.yml (as per Automation Examples section).
  • Clone the Git repository containing your Ansible files on lab_server.

Step 1: Create a new Git Branch

On lab_server, navigate to your Git repository and create a new branch for the change:

cd /path/to/your/repo
git checkout -b feature/add-guest-wifi-vlan

Step 2: Update the group_vars/all/vlans.yml file

Edit group_vars/all/vlans.yml to add the new VLAN 30:

# ... (existing content) ...
  - id: 30
    name: GUEST_WIFI
    description: VLAN for Guest Wireless Access
    ip_address: 10.30.30.1/24
    devices:
      cisco_core_switch_1:
        access_ports: [] # No direct access ports on core for this VLAN
        trunk_ports: ['GigabitEthernet1/0/24']
        trunk_allowed_vlan: '10,20,30' # Update allowed VLANs
        trunk_native_vlan: 999
      arista_access_switch_1:
        access_ports: ['Ethernet1/10'] # Assuming E1/10 is for guest AP
        trunk_ports: ['Ethernet1/24']
        trunk_allowed_vlan: '10,20,30' # Update allowed VLANs
        trunk_native_vlan: 999

Step 3: Commit and Push the Changes

git add group_vars/all/vlans.yml
git commit -m "feat: Add VLAN 30 for Guest WiFi"
git push origin feature/add-guest-wifi-vlan

Step 4: Simulate CI/CD (Manual Trigger for Lab)

Normally, a Git webhook would trigger your CI/CD pipeline. For this lab, we’ll manually run the Ansible playbook from the lab_server.

# Before running, ensure your playbook (configure_vlans.yml) is up-to-date
# You would typically merge this feature branch into main after review,
# and the CI/CD pipeline would trigger.
# For this lab, we assume the feature branch is merged to main.
git checkout main
git pull origin main # Ensure you have the latest (merged) changes

ansible-playbook -i inventory.ini configure_vlans.yml --limit core_switch_1,access_switch_1

Step 5: Verification

After the playbook completes, log into the devices and verify the new VLAN.

On Cisco core_switch_1:

ssh admin@192.168.1.11
show vlan brief
show interface GigabitEthernet1/0/24 switchport
show ip interface brief

Expected Output for show vlan brief: VLAN 30 should be listed. Expected Output for show interface GigabitEthernet1/0/24 switchport: VLAN 30 should be in the “Trunking VLANs Enabled” list. Expected Output for show ip interface brief: Vlan30 should have 10.30.30.1 assigned.

On Arista access_switch_1:

ssh admin@192.168.1.13
show vlan
show interface Ethernet1/24 switchport
show interface Ethernet1/10 switchport
show ip interface brief

Expected Output for show vlan: VLAN 30 should be listed. Expected Output for show interface Ethernet1/24 switchport: VLAN 30 should be in the “Allowed VLans on Trunk” list. Expected Output for show interface Ethernet1/10 switchport: Access Mode VLAN: 30. Expected Output for show ip interface brief: Vlan30 should have 10.30.30.1 assigned.

Challenge Exercises

  1. Remove a VLAN: Create a new branch, remove VLAN 20 from vlans.yml, commit, push, and run the playbook to observe its removal.
  2. Add Port Security: Update vlans.yml and the Ansible playbook to apply switchport port-security maximum 1 to a specific access port for VLAN 10 on the Cisco switch.
  3. Implement VLAN Pruning: Modify the trunk configuration in vlans.yml to only allow VLAN 10 and 30 on the trunk between core_switch_1 and access_switch_1, ensuring VLAN 20 traffic is explicitly pruned.

Best Practices Checklist

Implementing a GitOps workflow for VLAN configuration requires adherence to best practices to maximize its benefits.

  • Declarative Configuration: All VLAN configurations are defined declaratively in version control.
  • Single Source of Truth: Git is the sole source of all desired VLAN states.
  • Granular Data Separation: Separate VLAN definitions from device-specific assignments where possible, using Jinja2 templating or similar.
  • Peer Review for Changes: All changes to VLAN configurations go through a Pull Request review process.
  • Automated Validation (CI): Linting, syntax checks, and logical validation of configuration files are automated.
  • Automated Deployment (CD): Approved changes are automatically deployed to network devices.
  • Rollback Capability: The system supports quick and reliable rollbacks to previous configurations.
  • Secure Access to Git: Protect your Git repository with strong authentication and authorization.
  • Secure Automation Credentials: Store sensitive credentials (API keys, passwords) in a secure vault (e.g., Ansible Vault, HashiCorp Vault).
  • Idempotent Automation: Automation scripts should be idempotent, meaning running them multiple times produces the same result without unintended side effects.
  • Comprehensive Logging: Log all automation activities, including changes applied, timestamp, and user.
  • Monitoring for Drift: Implement monitoring to detect configuration drift between the Git repository and the actual device state.
  • Dedicated Native VLAN: Use an unused, dedicated VLAN ID for native VLAN on all trunks.
  • Disable DTP/Negotiation: Manually configure trunk and access modes, disabling DTP.
  • VLAN Pruning: Restrict VLANs on trunks to only those required.
  • Port Security: Implement port security measures on access ports.
  • Regular Audits: Periodically audit the Git repository and live configurations for compliance.
  • Documentation: Keep clear documentation of the GitOps workflow, repository structure, and configuration standards.
  • Environment Segregation: Use separate Git branches or repositories for different environments (dev, staging, production) with stricter approval processes for production.

What’s Next

This chapter has provided a comprehensive dive into establishing a robust GitOps workflow for managing VLAN configurations. We’ve explored the theoretical underpinnings, practical multi-vendor automation examples, and critical security and performance considerations.

You’ve learned to:

  • Frame VLAN management within a GitOps paradigm.
  • Translate VLAN requirements into declarative configuration code.
  • Leverage Ansible and Terraform for automated, multi-vendor VLAN provisioning and configuration.
  • Identify and mitigate common VLAN security vulnerabilities.
  • Optimize VLAN performance for enterprise-scale networks.

In the next chapter, we will expand on network segmentation by exploring VXLAN (Virtual Extensible LAN). While VLANs are limited to 4094 IDs and operate primarily within a single Layer 2 domain, VXLAN offers significant scalability (16 million segments) and the ability to extend Layer 2 networks over a Layer 3 underlay, which is crucial for modern data centers and cloud-native architectures. You’ll discover how GitOps principles can also be applied to VXLAN deployments, further enhancing network agility and automation.