I have noticed lately that when other network engineers talk about automation they always bring Ansible into the conversation. I tend to prefer using Python with Napalm or Netmiko to write my network automation scripts. However, it was actually attempting to fix/maintain someone elses Ansible script that started me on my path of learning Python. I recently decided to try and rewrite some of my scripts using Ansible to see if maybe it is time to start using Ansible again. I figured walking through a basic playbook would make a good blog post so here it goes.
Topology
We have two switches directly connected to each other. One is running vEOS the the other is running NX-OS. The only configuration on the switches is the management interface/vrf so that we connect to them using their respective APIs.
Inventory and configuration files
We have a basic ansible.cfg
file in our main folder.
[defaults]
inventory = hosts-net
host_key_checking = False
retry_files_enabled = False
Which points to the our hosts-net
inventory file.
[eos]
sw1 ansible_host=192.0.2.254
[nxos]
sw2 ansible_host=192.0.2.253
The eos and nxos groups will be used to target the correct devices when we run our playbook.
Host and group vars
Next we are going to create the group_vars
and host_vars
folders.
The group_vars
folder will have two Yaml files one for each NOS.
.
├── anisble.cfg
├── group_vars
│ ├── eos.yml
│ └── nxos.yml
├── hosts-net
└── host_vars
├── sw1.yml
└── sw2.yml
eos.yml
---
ansible_connection: httpapi
ansible_network_os: eos
ansible_user: admin
ansible_ssh_pass: admin
ansible_become: yes
ansible_become_method: enable
nxos.yml
---
ansible_connection: httpapi
ansible_network_os: nxos
ansible_user: admin
ansible_ssh_pass: admin
If these were production switches I would use RSA keys or ansible-vault
if I was forced to save passwords, but since these are VMs on my laptop the password in clear text is fine.
The host-vars
folder will have a Yaml file for each switch.
sw1.yml
---
interfaces:
- name: Ethernet1
ipv4: 192.168.12.1/24
enabled: True
state: up
switchport: False
- name: Loopback0
ipv4: 1.1.1.1/32
enabled: True
state: up
sw2.yml
---
interfaces:
- name: Ethernet2/1
ipv4: 192.168.12.2/24
mode: layer3
admin_state: up
- name: Loopback0
ipv4: 2.2.2.2/32
mode: layer3
admin_state: up
The interface parameters use different keys bases on the ansible modules that will access them.
The playbook
The next thing we will setup is the playbook that runs it all. It will be called interface-playbook.yml
. We are going to add multiple plays. The first one will backup the current configuration of the switches.
interface-playbook.yml
---
- hosts: eos
gather_facts: no
tasks:
- name: Backup eos device
eos_config:
backup: yes
register: "{{ inventory_hostname }}"
- hosts: nxos
gather_facts: no
tasks:
- name: Backup nxos device
nxos_config:
backup: yes
register: "{{ inventory_hostname }}"
We can run the play book with the ansible-playbook
command
ansible-playbook interface-playbook.yml
Output:
PLAY [eos] ********************************************************************************************************************************************************************************************************************************************************************
TASK [Backup eos device] ******************************************************************************************************************************************************************************************************************************************************
ok: [sw1]
PLAY [nxos] *******************************************************************************************************************************************************************************************************************************************************************
TASK [Backup nxos device] *****************************************************************************************************************************************************************************************************************************************************
ok: [sw2]
PLAY RECAP ********************************************************************************************************************************************************************************************************************************************************************
sw1 : ok=1 changed=0 unreachable=0 failed=0
sw2 : ok=1 changed=0 unreachable=0 failed=0
The backup module created a folder called backup and inside are the backups of our running-configs.
.
├── ansible.cfg
├── backup
│ ├── sw1_config.2019-01-21@12:18:58
│ └── sw2_config.2019-01-21@12:18:59
├── group_vars
│ ├── eos.yml
│ └── nxos.yml
├── hosts-net
├── host_vars
│ ├── sw1.yml
│ └── sw2.yml
└── interface-playbook.yml
Now that we have our backups it’s time to write plays that manipulate the interfaces. Starting with eos devices.
- hosts: eos
gather_facts: no
tasks:
--snip--
- name: Setup eos interfaces
eos_interface:
name: "{{ item['name'] }}"
enabled: "{{ item['enabled'] }}"
state: "{{ item['state'] }}"
loop: "{{ interfaces }}"
- name: Set no switchport eos
eos_config:
lines: no switchport
parents: interface {{ item['name'] }}
loop: "{{ interfaces }}"
when:
- item['switchport'] is defined
- item['switchport'] == False
- name: Setup layer3 eos
eos_l3_interface:
name: "{{ item['name'] }}"
ipv4: "{{ item['ipv4'] }}"
loop: "{{ interfaces }}"
We are going to loop through the interfaces in the files in the hostname.yml files. This way we can add interfaces as we need to. We can also add parameters to remove interface settings if needed.
The second play in the list is there because as far as I can tell setting no switchport
is not supported in any of the Ansible eos modules. The when statement is similar to an if statement in most programming languages.
The third play is pretty straight forward. It sets the IP information. If we started adding layer two ports to either switch, we would need a when statement or something similar to skip the layer3 task for those ports. For now I am trying to keep it as simple as possible and only write code when needed.
The nx-os portion is the same as the eos, the no switchport
play is not needed because it is built into the nxos module.
- hosts: nxos
gather_facts: no
--snip--
- name: Setup nxos interfaces
nxos_interface:
name: "{{ item['name'] }}"
admin_state: "{{ item['admin_state'] }}"
mode: "{{ item['mode'] }}"
loop: "{{ interfaces }}"
- name: Setup layer3 nxos
nxos_l3_interface:
name: "{{ item['name'] }}"
ipv4: "{{ item['ipv4'] }}"
loop: "{{ interfaces }}"
The last thing we are going to add to the playbook is a play for each NOS to write to memory. By default the ansible does not perform copy running-config startup-config
.
- hosts: eos
gather_facts: no
tasks:
--snip--
- name: Save eos configuration
eos_config:
save_when: modified
- hosts: nxos
gather_facts: no
tasks:
--snip--
- name: Save nxos configuration
nxos_config:
save_when: modified
Running the playbook produces the following results
PLAY [eos] ********************************************************************************************************************************************************************************************************************************************************************
TASK [Backup eos device] ******************************************************************************************************************************************************************************************************************************************************
ok: [sw1]
TASK [Setup eos interfaces] ***************************************************************************************************************************************************************************************************************************************************
ok: [sw1] => (item={'name': 'Ethernet1', 'ipv4': '192.168.12.1/24', 'enabled': True, 'state': 'up', 'switchport': False})
changed: [sw1] => (item={'name': 'Loopback0', 'ipv4': '1.1.1.1/32', 'enabled': True, 'state': 'up'})
TASK [Set no switchport eos] **************************************************************************************************************************************************************************************************************************************************
changed: [sw1] => (item={'name': 'Ethernet1', 'ipv4': '192.168.12.1/24', 'enabled': True, 'state': 'up', 'switchport': False})
skipping: [sw1] => (item={'name': 'Loopback0', 'ipv4': '1.1.1.1/32', 'enabled': True, 'state': 'up'})
TASK [Setup layer3 eos] *******************************************************************************************************************************************************************************************************************************************************
changed: [sw1] => (item={'name': 'Ethernet1', 'ipv4': '192.168.12.1/24', 'enabled': True, 'state': 'up', 'switchport': False})
changed: [sw1] => (item={'name': 'Loopback0', 'ipv4': '1.1.1.1/32', 'enabled': True, 'state': 'up'})
TASK [Save eos configuration] *************************************************************************************************************************************************************************************************************************************************
changed: [sw1]
PLAY [nxos] *******************************************************************************************************************************************************************************************************************************************************************
TASK [Backup nxos device] *****************************************************************************************************************************************************************************************************************************************************
ok: [sw2]
TASK [Setup nxos interfaces] **************************************************************************************************************************************************************************************************************************************************
changed: [sw2] => (item={'name': 'Ethernet2/1', 'ipv4': '192.168.12.2/24', 'mode': 'layer3', 'admin_state': 'up'})
changed: [sw2] => (item={'name': 'Loopback0', 'ipv4': '2.2.2.2/32', 'mode': 'layer3', 'admin_state': 'up'})
TASK [Setup layer3 nxos] ******************************************************************************************************************************************************************************************************************************************************
changed: [sw2] => (item={'name': 'Ethernet2/1', 'ipv4': '192.168.12.2/24', 'mode': 'layer3', 'admin_state': 'up'})
changed: [sw2] => (item={'name': 'Loopback0', 'ipv4': '2.2.2.2/32', 'mode': 'layer3', 'admin_state': 'up'})
TASK [Save nxos configuration] ************************************************************************************************************************************************************************************************************************************************
changed: [sw2]
PLAY RECAP ********************************************************************************************************************************************************************************************************************************************************************
sw1 : ok=5 changed=5 unreachable=0 failed=0
sw2 : ok=4 changed=3 unreachable=0 failed=0
The configuration was backed up, the interfaces were created and enabled where needed. The IP address information was added and finally the new configuration was saved.
SW1#show running-config interfaces ethernet 1
interface Ethernet1
no switchport
ip address 192.168.12.1/24
SW1#show running-config interfaces loopback 0
interface Loopback0
ip address 1.1.1.1/32
SW1#ping 192.168.12.2
PING 192.168.12.2 (192.168.12.2) 72(100) bytes of data.
80 bytes from 192.168.12.2: icmp_seq=1 ttl=255 time=19.6 ms
80 bytes from 192.168.12.2: icmp_seq=2 ttl=255 time=6.95 ms
80 bytes from 192.168.12.2: icmp_seq=3 ttl=255 time=5.66 ms
80 bytes from 192.168.12.2: icmp_seq=4 ttl=255 time=8.23 ms
80 bytes from 192.168.12.2: icmp_seq=5 ttl=255 time=5.74 ms
--- 192.168.12.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 70ms
rtt min/avg/max/mdev = 5.666/9.254/19.670/5.292 ms, ipg/ewma 17.506/14.274 ms
SW2# sh running-config interface ethernet 2/1
interface Ethernet2/1
no switchport
mac-address 0000.0000.002f
ip address 192.168.12.2/24
no shutdown
SW2# sh running-config interface loopback 0
interface loopback0
ip address 2.2.2.2/32
SW2# ping 192.168.12.2
PING 192.168.12.2 (192.168.12.2): 56 data bytes
64 bytes from 192.168.12.2: icmp_seq=0 ttl=255 time=0.393 ms
64 bytes from 192.168.12.2: icmp_seq=1 ttl=255 time=0.202 ms
64 bytes from 192.168.12.2: icmp_seq=2 ttl=255 time=0.157 ms
64 bytes from 192.168.12.2: icmp_seq=3 ttl=255 time=0.127 ms
64 bytes from 192.168.12.2: icmp_seq=4 ttl=255 time=0.128 ms
--- 192.168.12.2 ping statistics ---
5 packets transmitted, 5 packets received, 0.00% packet loss
round-trip min/avg/max = 0.127/0.201/0.393 ms
I can see why Ansible is popular for managing network gear as it lets you get started quickly. If I were writing this script in python, it would have taken me a bit longer but, I wouldn’t have to performed workarounds like I had to for the no switchport
section.
The full code can be found here, I also included the base configs for the two switches before they were modified by ansible.