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:
- Configure VLANs
- Add VLAN's and VLAN-mapping to P010 trunk
- 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 :)