Automating Network VLAN Deployments with Ansible

Automating Network VLAN Deployments with Ansible

Rolling out new VLANs across a network can be time consuming. The Ansible playbook documented in this post automates the configuration and deployment for specified VLANs to a group of switches.

In our case, we use private-vlans in the 5xx range for our customers, and map these to 15xx vlans in the core network. This mapping is configured on our upstream trunk ports.

The finished playbook thus completes these tasks in this order:

  1. Configure VLANs
  2. Add VLAN's and VLAN-mapping to P010 trunk
  3. Send confirmation message to Slack channel

VLAN Configuration

To avoid manually typing out the configuration for the VLANs, I render this configuration through Ansible. First I must define how my VLAN configuration should look in my Jinja2 template:

vlans.j2

{% for item in vlans %}
  vlan {{ item.vlan }}
    name {{ item.name_1 }}
     private-vlan primary
     private-vlan association 1{{ item.vlan }}
     exit
  vlan 1{{ item.vlan }}
    name {{ item.name_2 }}
     private-vlan isolated
     exit
{% endfor %}

And I can then define the VLAN properties in JSON:

vlans.json

{
   "vlans":[
   {   "vlan": "501", "name_1": "CUST1-VRF", "name_2": "CUST1-CE" },
   {   "vlan": "502", "name_1": "CUST2-VRF", "name_2": "CUST2-CE" },

   ...

   {   "vlan": "548", "name_1": "CUST48-VRF", "name_2": "CUST48-CE" },
   {   "vlan": "549", "name_1": "CUST49-VRF", "name_2": "CUST49-CE" },
   {   "vlan": "550", "name_1": "CUST50-VRF", "name_2": "CUST50-CE" }
   ]
}

This is a really nice way of doing it, as it allows me to render all of the Cisco configuration automatically, while giving me control to explicitly name my VLANs. The CUSTX naming scheme above is fabricated, and we would actually name our VLANs properly in production, however if your requirement does not necessitate uniquely naming VLANs then you can omit the JSON altogether and iterate over a pythonic range in Jinja2:

vlans.j2 (alternative)

{% for vlan in range(500,551) %}
  vlan {{ vlan }}
    name CUST{{ vlan }}-VRF
     private-vlan primary
     private-vlan association 1{{ vlan }}
     exit
  vlan 1{{ item.vlan }}
    name CUST{{ item.name_2 }}-CE
     private-vlan isolated
     exit
{% endfor %}

This is also a neat way of doing it.

Finally, the below playbook task will render and apply the VLAN configuration:

Task

  - name: Render Jinja2 VLAN configuration for VLANs 500-550, 1500-1550
    ios_config:
      backup: yes
      src: vlans.j2
      match: none

I have set the match parameter to none, instructing ansible to not check the running config prior to executing the rendered config. Of the 4 possible options for the match parameter, this seems to be the only option that allows the task to complete successfully when one of the rendered VLANs already exist on the target equipment.


Trunk Configuration

Mapping and trunking the VLANs on our Port-Channel10 interface can be done statically like the below:

Task

  - name: Deploy VLANs to P010
    ios_config:
      lines:
        - switchport private-vlan trunk allowed vlan add 500-550
        - switchport private-vlan mapping trunk 500 1500
        - switchport private-vlan mapping trunk 501 1501
        - switchport private-vlan mapping trunk 502 1502

        ...

        - switchport private-vlan mapping trunk 548 1548
        - switchport private-vlan mapping trunk 549 1549
        - switchport private-vlan mapping trunk 550 1550
      parents: interface Port-channel10
      match: exact
      authorize: yes

Alternatively, we can use Jinja2 to render this in a similar fashion to our VLAN configuration:

trunk.j2

{% for vlan in range(500,551) %}
 switchport private-vlan trunk allowed vlan add {{ vlan }}
 switchport private-vlan mapping trunk {{ vlan }} 1{{ vlan }}
{% endfor %}

We will need to add the parents paremeter to our task to ensure this is deployed under the interface configuration:

Task (alternative)

  - name: Render Jinja2 VLAN configuration for trunk interface
    ios_config:
      backup: yes
      src: trunk.j2
      parents: interface Port-Channel10
      match: none

Last of all, I like to include a Slack confirmation at the end of my playbooks. A slightly tweaked example from the Ansible Documentation will do:

  - name: Send notification message via Slack
    slack:
      token: PRIVATETOKEN
      msg: 'New VLANs have been successfully deployed to DC Switches'
      color: '#ff007f'
      channel: '#bots'
      username: 'Ansible Bot'
      parse: 'none'
    delegate_to: localhost

And that's it! Our complete playbook will look like this:

The Playbook

- hosts: dc_switches
  gather_facts: no
  connection: network_cli
  become: yes
  become_method: enable
  vars_files:
    - vlans.json
  
  tasks:
  - name: Render Jinja2 VLAN configuration for VLANs 500-550, 1500-1550
    ios_config:
      backup: yes
      src: vlans.j2
      match: none
      
  - name: Deploy VLANs to P010
    ios_config:
      lines:
        - switchport private-vlan trunk allowed vlan add 500-550
        - switchport private-vlan mapping trunk 500 1500
        - switchport private-vlan mapping trunk 501 1501
        - switchport private-vlan mapping trunk 502 1502

        ...

        - switchport private-vlan mapping trunk 548 1548
        - switchport private-vlan mapping trunk 549 1549
        - switchport private-vlan mapping trunk 550 1550
      parents: interface Port-channel10
      match: exact
      authorize: yes
      
  - name: Send notification message via Slack
    slack:
      token: PRIVATETOKEN
      msg: 'New VLANs have been successfully deployed to DC Switches'
      color: '#ff007f'
      channel: '#bots'
      username: 'Ansible Bot'
      parse: 'none'
    delegate_to: localhost
      

Thanks for reading :)

Related Article