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.

Ansible_Topology

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.