Interfacing Ansible, NetBox, and production equipment can sometimes feel like a daunting task. Once you’ve successfully setup Ansible and have some basic interactions working( which this blog Getting Started with Network Automation: NetBox + Ansible from Eric Goebelbecker does a phenomenal job at discussing) you can look to start expanding into more complicated tasks or additional platforms.
In this blog, I’m going to walk through how we extend our Ansible usage into additional platforms. We’re going to use Ansible to assign an IP address designated for a specific interface directly to the interface. For the purposes of this lab I’ll be using virtual switches but these steps should work on physical switches as well.
To start, let’s define the platforms in use. For this lab I’ll have five devices. Cisco NX-OS, Cisco IOS, Arista vEOS, Juniper vQFX, and VyOS. Each of these 5 devices have been configured in NetBox to match their virtual counterpart.
With each of the five devices created, we can now begin leveraging Ansible to change the first interface on each device. We’ll be setting up five playbooks, one for each specific device type, and then having Ansible leverage the identified management IP to change the interface we’re targeting. For each of the five devices we’ll look at the playbook, the device configuration before, the Ansible execution, and the device configuration after.
Virtual NX-OS
Let’s start with our before config:
vNXOS# show run int eth2/1 interface Ethernet2/1 shutdown no switchport vNXOS#
And now we look at our corresponding Ansible playbook:
(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ cat 01_cisco--nx-os.yml --- - name: Set NX-OS IP address hosts: device_types_vnxos connection: network_cli become: true vars: ansible_user: admin ansible_password: admin ansible_become: yes ansible_become_password: admin ansible_network_os: cisco.nxos.nxos target_interface: Ethernet2/1 tasks: - name: Configure Layer3 Mode and Enable Port cisco.nxos.nxos_interfaces: config: - name: "{{ target_interface }}" mode: layer3 enabled: true loop: "{{ interfaces }}" when: item.name == target_interface - name: Configure IP address cisco.nxos.nxos_l3_interfaces: config: - name: "{{ target_interface }}" ipv4: - address: "{{ item.ip_addresses[0].address }}" loop: "{{ interfaces }}" when: item.name == target_interface (.venv) jvillarreal@dev-box:~/ansible/ip-blog$
We can see that in our “Configure Layer3 Mode and Enable Port” task that we’re targeting the interface named Ethernet2/1. We loop through all of our interfaces until we find that one, by name, and then configure it to be in Layer 3 mode and be enabled. Then under “Configure IP address” we take the first IP address out of NetBox for that interface and assign it to the same named interface.
Next, we execute the playbook:
(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 01_cisco--nx-os.yml PLAY [Set NX-OS IP address] ********************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************** ok: [IP Blog NXOS] TASK [Configure Layer3 Mode and Enable Port] ***************************************************************************************** changed: [IP Blog NXOS] => (item={'id': 11591, 'url': 'https://192.168.86.92:443/api/dcim/interfaces/11591/', 'display': 'Ethernet2/1' , 'device': {'id': 644, 'url': 'https://192.168.86.92:443/api/dcim/devices/644/', 'display': 'IP Blog NXOS', 'name': 'IP Blog NXOS'}, 'vdcs': [], 'module': None, 'name': 'Ethernet2/1', 'label': '', 'type': {'value': 'virtual', 'label': 'Virtual'}, 'enabled': True, 'parent': None, 'bridge': None, 'lag': None, 'mtu': None, 'mac_address': None, 'speed': None, 'duplex': None, 'wwn': None, 'mgmt_only': False, 'description': '', 'mode': None, 'rf_role': None, 'rf_channel': None, 'poe_mode': None, 'poe_type': None, 'rf_channel_frequency': None, 'rf_channel_width': None, 'tx_power': None, 'untagged_vlan': None, 'tagged_vlans': [], 'mark_connected': False, 'cable': None, 'cable_end': '', 'wireless_link': None, 'link_peers': [], 'link_peers_type': None, 'wireless_lans': [], 'vrf': None, 'l2vpn_termination': None, 'connected_endpoints': None, 'connected_endpoints_type': None, 'connected_endpoints_reachable': None, 'tags': [], 'custom_fields': {}, 'created': '2023-06-26T19:18:44.487337Z', 'last_updated': '2023-06-29T17:14:46.954713Z', 'count_ipaddresses': 1, 'count_fhrp_groups': 0, '_occupied': False, 'ip_addresses': [{'id': 197, 'url': 'https://192.168.86.92:443/api/ipam/ip-addresses/197/', 'display': '10.10.10.1/24', 'family': {'value': 4, 'label': 'IPv4'}, 'address': '10.10.10.1/24', 'vrf': None, 'tenant': None, 'status': {'value': 'active', 'label': 'Active'}, 'role': None, 'nat_inside': None, 'nat_outside': [], 'dns_name': '', 'description': '', 'comments': '', 'tags': [], 'custom_fields': {}, 'created': '2023-06-28T19:18:18.383547Z', 'last_updated': '2023-06-28T19:18:18.383561Z'}]}) TASK [Configure IP address] ********************************************************************************************************** changed: [IP Blog NXOS] => (item={'id': 11591, 'url': 'https://192.168.86.92:443/api/dcim/interfaces/11591/', 'display': 'Ethernet2/1' , 'device': {'id': 644, 'url': 'https://192.168.86.92:443/api/dcim/devices/644/', 'display': 'IP Blog NXOS', 'name': 'IP Blog NXOS'}, 'vdcs': [], 'module': None, 'name': 'Ethernet2/1', 'label': '', 'type': {'value': 'virtual', 'label': 'Virtual'}, 'enabled': True, 'parent': None, 'bridge': None, 'lag': None, 'mtu': None, 'mac_address': None, 'speed': None, 'duplex': None, 'wwn': None, 'mgmt_only': False, 'description': '', 'mode': None, 'rf_role': None, 'rf_channel': None, 'poe_mode': None, 'poe_type': None, 'rf_channel_frequency': None, 'rf_channel_width': None, 'tx_power': None, 'untagged_vlan': None, 'tagged_vlans': [], 'mark_connected': False, 'cable': None, 'cable_end': '', 'wireless_link': None, 'link_peers': [], 'link_peers_type': None, 'wireless_lans': [], 'vrf': None, 'l2vpn_termination': None, 'connected_endpoints': None, 'connected_endpoints_type': None, 'connected_endpoints_reachable': None, 'tags': [], 'custom_fields': {}, 'created': '2023-06-26T19:18:44.487337Z', 'last_updated': '2023-06-29T17:14:46.954713Z', 'count_ipaddresses': 1, 'count_fhrp_groups': 0, '_occupied': False, 'ip_addresses': [{'id': 197, 'url': 'https://192.168.86.92:443/api/ipam/ip-addresses/197/', 'display': '10.10.10.1/24', 'family': {'value': 4, 'label': 'IPv4'}, 'address': '10.10.10.1/24', 'vrf': None, 'tenant': None, 'status': {'value': 'active', 'label': 'Active'}, 'role': None, 'nat_inside': None, 'nat_outside': [], 'dns_name': '', 'description': '', 'comments': '', 'tags': [], 'custom_fields': {}, 'created': '2023-06-28T19:18:18.383547Z', 'last_updated': '2023-06-28T19:18:18.383561Z'}]}) PLAY RECAP *************************************************************************************************************************** IP Blog NXOS : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 (.venv) jvillarreal@dev-box:~/ansible/ip-blog$
We can see the playbook recap has identified two changes. Let’s check on the device.
vNXOS# show run int eth2/1 interface Ethernet2/1 no switchport ip address 10.10.10.1/24 no shutdown vNXOS#
And we can see that our port is now a Layer 3 interface with the designated IP. Going forward I’ll be truncating output of the Ansible playbooks.
Virtual IOS
Same as before. We’ll start with our before config:
vIOS#show run int gi 0/0 interface GigabitEthernet0/0 no ip address shutdown duplex auto speed auto end vIOS#
Then examine our playbook:
--- - name: Set IOS IP address hosts: device_types_vios connection: network_cli become: true vars: ansible_user: cisco ansible_password: cisco ansible_become: yes ansible_become_password: admin ansible_network_os: cisco.ios.ios target_interface: GigabitEthernet0/0 tasks: - name: Configure Enable Port cisco.ios.ios_interfaces: config: - name: "{{ target_interface }}" enabled: true loop: "{{ interfaces }}" when: item.name == target_interface - name: Configure IP address cisco.ios.ios_l3_interfaces: config: - name: "{{ target_interface }}" ipv4: - address: "{{ item.ip_addresses[0].address }}" loop: "{{ interfaces }}" when: item.name == target_interface
Now we execute the playbook:
(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 02_cisco--ios.yml PLAY [Set IOS IP address] ************************************************************************************************************ TASK [Gathering Facts] *************************************************************************************************************** ok: [IP Blog vIOS] TASK [Configure Enable Port] ********************************************************************************************************* changed: [IP Blog vIOS] => {{ Truncated Output }} TASK [Configure IP address] ********************************************************************************************************** changed: [IP Blog vIOS] => {{ Truncated Output }} PLAY RECAP *************************************************************************************************************************** IP Blog vIOS : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 (.venv) jvillarreal@dev-box:~/ansible/ip-blog$
And now the switch output:
vIOS#show run int gi 0/0 interface GigabitEthernet0/0 ip address 10.10.10.2 255.255.255.0 duplex auto speed auto end vIOS#
Virtual EOS
Our very terse before config:
vEOS(config)#show run int e1 interface Ethernet1
And next, the playbook:
--- - name: Set vEOS IP address hosts: device_types_veos connection: network_cli become: true vars: ansible_user: admin ansible_password: admin ansible_become: yes ansible_become_password: admin ansible_network_os: arista.eos.eos target_interface: Ethernet1 tasks: - name: Configure Layer3 Mode and Enable Port arista.eos.eos_interfaces: config: - name: "{{ target_interface }}" mode: layer3 enabled: true loop: "{{ interfaces }}" when: item.name == target_interface - name: Configure IP address arista.eos.eos_l3_interfaces: config: - name: "{{ target_interface }}" ipv4: - address: "{{ item.ip_addresses[0].address }}" loop: "{{ interfaces }}" when: item.name == target_interface
Running the playbook:
(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 03_arista--veos.yml PLAY [Set vEOS IP address] *********************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************** ok: [IP Blog vEOS] TASK [Configure Layer3 Mode and Enable Port] ***************************************************************************************** changed: [IP Blog vEOS] => {{ Truncated Output }} TASK [Configure IP address] ********************************************************************************************************** changed: [IP Blog vEOS] => {{ Truncated Output }} PLAY RECAP *************************************************************************************************************************** IP Blog vEOS : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 (.venv) jvillarreal@dev-box:~/ansible/ip-blog$
And the updated config, again very terse:
vEOS#show run int e1 interface Ethernet1 no switchport ip address 10.10.10.3/24 vEOS#
VYoS
Our before config:
vyos@vyos# show interface ethernet eth0 hw-id 50:00:00:03:00:00 [edit]
Our playbook:
--- - name: Set vVyOS IP address hosts: device_types_vvyos connection: network_cli become: true vars: ansible_user: vyos ansible_password: vyos ansible_become: yes ansible_become_password: vyos ansible_network_os: vyos.vyos.vyos target_interface: eth0 tasks: - name: Configure Enable Port vyos.vyos.vyos_interfaces: config: - name: "{{ target_interface }}" enabled: true loop: "{{ interfaces }}" when: item.name == target_interface - name: Configure IP address vyos.vyos.vyos_l3_interfaces: config: - name: "{{ target_interface }}" ipv4: - address: "{{ item.ip_addresses[0].address }}" loop: "{{ interfaces }}" when: item.name == target_interface
Running our playbook:
(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 04_vyow--vvyos.yml PLAY [Set vVyOS IP address] ********************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************** ok: [IP Blog vVyOS] TASK [Configure Enable Port] ********************************************************************************************************* ok: [IP Blog vVyOS] => {{ Truncated Output }} TASK [Configure IP address] ********************************************************************************************************** changed: [IP Blog vVyOS] => {{ Truncated Output }} PLAY RECAP *************************************************************************************************************************** IP Blog vVyOS : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Verifying the config
vyos@vyos# show interface ethernet eth0 address 10.10.10.4/24 hw-id 50:00:00:03:00:00 [edit] vyos@vyos#
Virtual JUN-OS
And last, but certainly not least, our starting Juniper config:
root@vqfx-re# show interfaces xe-0/0/0 unit 0 { family inet { dhcp; } } {master:0}[edit] root@vqfx-re#
Our playbook:
--- - name: Set vQFX IP address hosts: device_types_vqfx connection: ansible.netcommon.netconf become: true vars: ansible_user: root ansible_password: Juniper ansible_network_os: junipernetworks.junos.junos target_interface: xe-0/0/0 tasks: - name: Configure IP address junipernetworks.junos.junos_l3_interfaces: config: - name: "{{ target_interface }}" ipv4: - address: "{{ item.ip_addresses[0].address }}" state: replaced loop: "{{ interfaces }}" when: item.name == target_interface
Next up, executing our playbook:
(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 05_juniper--vqfx.yml [WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details PLAY [Set vQFX IP address] *********************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************** ok: [IP Blog vQFX] TASK [Configure IP address] ********************************************************************************************************** changed: [IP Blog vQFX] => {{ Truncated Output }} PLAY RECAP *************************************************************************************************************************** IP Blog vQFX : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 (.venv) jvillarreal@dev-box:~/ansible/ip-blog$
And finally, checking the results:
root@vqfx-re# show interfaces xe-0/0/0 unit 0 { family inet { address 10.10.10.5/24; } } {master:0}[edit] root@vqfx-re#
Wrapping It Up
Once you get the hang of how Ansible works, it becomes very easy to iterate and make specific playbooks for different platforms. The majority of the time I spent troubleshooting through this document were really based around making sure I’m issuing the right commands for the right platforms. For example, vNX-OS is a switch and vIOS is a router. I ended up spending a good amount of time trying to understand why the “no switchport” commands were taking on vNX-OS and not on vIOS before it dawned on me. Oh, that’s right. Routers don’t have a switchport command because they aren’t switches!
In summary: Know your platform!
A note about “ok” in Ansible
As you look through my output, you’re going to find “ok” counts that exceed the number of changes we’re making. Each of the playbooks runs a “Gathering Facts” task, which contributes to one of our ok’s. Each Ansible module requires the usage of the “name” variable as well. Even though we aren’t making a change to the name of the interfaces, that contributes to our ok count as well. Subsequent runs of these playbooks would result in our ok count increasing by the number of successful changes.
Get more tutorials and resources by joining the NetBox community on Slack and signing up for our NetBox Community mailing list.
Ansible Modules Used:
- NetBox – https://docs.ansible.com/ansible/latest/collections/netbox/netbox/index.html
- Cisco NX-OS – https://docs.ansible.com/ansible/latest/collections/cisco/nxos/index.html
- Cisco IOS – https://docs.ansible.com/ansible/latest/collections/cisco/ios/index.html
- Arista EOS – https://docs.ansible.com/ansible/latest/collections/arista/eos/index.html
- VYoS – https://docs.ansible.com/ansible/latest/collections/vyos/vyos/index.html
- Juniper JUNOS – https://docs.ansible.com/ansible/latest/collections/junipernetworks/junos/index.html