Today we are going to learn how to use Ansible to configure BGP on Cisco IOS devices. There are several parts to this so get ready.
We will be using 3 routers in this example. Each router will have 2 active links each, one link connected to its direct neighbor. Here is a diagram of what the setup is inside the Cisco Modeling Labs (CML) environment.
Each router has a bridge network connection to my home network, not a NAT network connection. This is so that my laptop, which has Ansible installed on it, can run the necessary playbook to configure the routers in the lab. Each router already has the gigabitEthernet0/0 interface setup with an IP in the range of the bridge network. SSH is already setup on each router as well so that my laptop can remotely login via SSH.
First off you need to create a virtual environment to install the Ansible and Napalm packages. Ansible as already noted will be used to configure the routers, while Napalm will be used to verify the configuration of the routers. Run the following commands to install the virtual environment, Ansible and Napalm:
python -m venv venv
pip install ansible
pip install ansible-pylibssh
pip install napalm
We also need to clone the github repo that contains napalm-validate in order to run the validation step.
Create a directory in your home directory called ansible_library. Then change to that directory and clone the napalm-ansible repo:
mkdir ~/ansible_library
cd ~/ansible_library
git clone https://github.com/napalm-automation/napalm-ansible.git
Now within your home directory create a new directory to store the Ansible files needed to configure the 3 routers. I called mine ~/dev/ansible-ciscso-bgp-config. You can call it anything you want. Navigate to the directory and create an ansible.cfg file with the following content:
[defaults]
host_key_checking = False
gather_facts = False
library = ~/ansible_library/
inventory = inventory.yml
log_path=./EXAMPLE_OUTPUT
callbacks_enabled=ansible.posix.profile_tasks
[inventory]
enable_plugins = yaml,ini
inventory = inventory.yml
In this file we tell it to skip the host key check, since this is a lab environment, don't do this in production. We tell it to not gather facts from the device using python, we only want to gather facts based on the ios module. The library path is specified to where the napalm-ansible repo was stored, to run the validations.
The inventory file is specified from the default of hosts.ini. The log path is set for logging to an external file. The callbacks are enabled and specified to show how long the each task took to run. The inventory section is their tell it to use the yaml and ini plugins.
Next make a directory called host_vars. In that directory we are going to add three host files with their values needed to configure the respective routers.
host_vars/R1
ip1_name: gigabitEthernet0/2
ip_1: 10.10.13.1
ip_1_description: R1 gig2 -> R3 gig2
ip2_name: gigabitEthernet0/4
ip_2: 10.10.12.1
ip_2_description: R1 gig4 -> R2 gig4
name: R1
mask: 24
source_ip: 192.168.0.221
router_id: 1.1.1.1
asn: 65511
neighbor_1_ip: 10.10.12.2
neighbor_1_remote_as: 65512
neighbor_2_ip: 10.10.13.3
neighbor_2_remote_as: 65513
host_vars/R2
ip1_name: gigabitEthernet0/3
ip_1: 10.10.23.2
ip_1_description: R2 gig3 -> R3 gig3
ip2_name: gigabitEthernet0/4
ip_2: 10.10.12.2
ip_2_description: R2 gig4 -> R1 gig4
name: R2
mask: 24
source_ip: 192.168.0.222
router_id: 2.2.2.2
asn: 65512
neighbor_1_ip: 10.10.12.1
neighbor_1_remote_as: 65511
neighbor_2_ip: 10.10.23.3
neighbor_2_remote_as: 65513
host_vars/R3
ip1_name: gigabitEthernet0/2
ip_1: 10.10.13.3
ip_1_description: R3 gig2 -> R1 gig2
ip2_name: gigabitEthernet0/3
ip_2: 10.10.23.3
ip_2_description: R3 gig3 -> R2 gig3
name: R3
mask: 24
router_id: 3.3.3.3
source_ip: 192.168.0.223
asn: 65513
neighbor_1_ip: 10.10.13.1
neighbor_1_remote_as: 65511
neighbor_2_ip: 10.10.23.2
neighbor_2_remote_as: 65512
Now that we have that in place we need to create a group_vars directory and add in the variables that each host shares to a file called ios.yml. This will be things like the connection parameters, password and username.
group_vars/ios.yml
ansible_connection: network_cli
ansible_network_os: cisco.ios.ios
ansible_user: cisco
ansible_password: cisco
It is time to create the playbook file, the one that actually does the configuration and the validation of the configuration. You can call this file anything you like, I have called it configure.yml.
configure.yml
---
- name: Network Configurator
hosts: all
collections:
- napalm_validate
vars_files:
- group_vars/ios.yml
tasks:
- name: Configure Interfaces
cisco.ios.ios_interfaces:
config:
- name: "{{ ip1_name }}"
description: "{{ ip_1_description }}"
enabled: true
- name: "{{ ip2_name }}"
description: "{{ ip_2_description }}"
enabled: true
state: merged
- name: Configure Layer 3 Interfaces
cisco.ios.ios_l3_interfaces:
config:
- name: "{{ ip1_name }}"
ipv4:
- address: "{{ ip_1 }}/{{ mask }}"
- name: "{{ ip2_name }}"
ipv4:
- address: "{{ ip_2 }}/{{ mask }}"
state: merged
- name: Validate Layer 3 Interfaces
cisco.ios.ios_ping:
dest: "{{ ip_1 }}"
source: "{{ source_ip }}"
- name: Configure BGP on all hosts
cisco.ios.ios_bgp_global:
config:
as_number: "{{ asn }}"
neighbor:
- address: "{{ neighbor_1_ip }}"
remote_as: "{{ neighbor_1_remote_as }}"
- address: "{{ neighbor_2_ip }}"
remote_as: "{{ neighbor_2_remote_as }}"
bgp:
router_id:
address: "{{ router_id }}"
state: merged
- name: Validations on all hosts
napalm_validate:
hostname: "{{ name }}"
username: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validation_file: validate_{{ name }}.yml
dev_os: 'ios'
register: result
- name: Print validation results
debug: msg="{{ result }}"
Here we are configuring the state of the interfaces that will be putting IP addresses on. Next we configure the IP addresses on the interfaces. Then we validate that the layer3 interface is up. Then we configure BGP on the routers with their respective neighbors. Finally we run the validations on all routers using the validation files that we will discuss below.
Now create three validation files, one for each host, to validate that the configuration is setup properly.
validate_R1.yml
---
- get_facts:
hostname: R1
- get_bgp_neighbors:
global:
router_id: 1.1.1.1
peers:
10.10.12.2:
is_enabled: true
10.10.13.3:
is_enabled: true
- ping:
_name: ping_10.10.13.1
_kwargs:
destination: 10.10.13.1
source: 192.168.0.221
success:
packet_loss: 0
_mode: strict
- ping:
_name: ping_10.10.12.1
_kwargs:
destination: 10.10.12.1
source: 192.168.0.221
success:
packet_loss: 0
_mode: strict
validate_R2.yml
---
- get_facts:
hostname: R2
- get_bgp_neighbors:
global:
router_id: 2.2.2.2
peers:
10.10.12.1:
is_enabled: true
10.10.23.3:
is_enabled: true
- ping:
_name: ping_10.10.23.2
_kwargs:
destination: 10.10.23.2
source: 192.168.0.222
success:
packet_loss: 0
_mode: strict
- ping:
_name: ping_10.10.12.2
_kwargs:
destination: 10.10.12.2
source: 192.168.0.222
success:
packet_loss: 0
_mode: strict
validate_R3.yml
---
- get_facts:
hostname: R3
- get_bgp_neighbors:
global:
router_id: 3.3.3.3
peers:
10.10.13.1:
is_enabled: true
10.10.23.2:
is_enabled: true
- ping:
_name: ping_10.10.13.3
_kwargs:
destination: 10.10.13.3
source: 192.168.0.223
success:
packet_loss: 0
_mode: strict
- ping:
_name: ping_10.10.23.3
_kwargs:
destination: 10.10.23.3
source: 192.168.0.223
success:
packet_loss: 0
_mode: strict
All three of these files validate that the hostname is correct, that the BGP settings are properly configured with neighbor adjacencies formed, and that we are able to ping the interfaces configured.
With all this in place we can now run the playbook to configure and validate at the same time. Let's do it!
Run the following command:
ansible-playbook configure.yml
Here is the output of that command:
~/dev/ansible-cisco-ios-bgp-config$ ansible-playbook configure.yml
[WARNING]: errors were encountered during the plugin load for napalm_validate: ['napalm_validate.napalm_validate is not
a valid collection reference']
[WARNING]: errors were encountered during the plugin load for debug: ['napalm_validate.debug is not a valid collection
reference']
PLAY [Network Configurator] ********************************************************************************************
[WARNING]: errors were encountered during the plugin load for gather_facts: ['napalm_validate.gather_facts is not a
valid collection reference']
TASK [Gathering Facts] *************************************************************************************************
Wednesday 13 September 2023 10:46:18 -0500 (0:00:00.006) 0:00:00.006 ***
ok: [R3]
ok: [R2]
ok: [R1]
[WARNING]: errors were encountered during the plugin load for meta: ['napalm_validate.meta is not a valid collection
reference']
TASK [Configure Interfaces] ********************************************************************************************
Wednesday 13 September 2023 10:46:20 -0500 (0:00:02.623) 0:00:02.630 ***
changed: [R2]
changed: [R1]
changed: [R3]
TASK [Configure Layer 3 Interfaces] ************************************************************************************
Wednesday 13 September 2023 10:46:23 -0500 (0:00:02.279) 0:00:04.910 ***
changed: [R3]
changed: [R1]
changed: [R2]
TASK [Validate Layer 3 Interfaces] *************************************************************************************
Wednesday 13 September 2023 10:46:25 -0500 (0:00:01.999) 0:00:06.909 ***
ok: [R1]
ok: [R2]
ok: [R3]
TASK [Configure BGP on all hosts] **************************************************************************************
Wednesday 13 September 2023 10:46:25 -0500 (0:00:00.672) 0:00:07.582 ***
changed: [R2]
changed: [R3]
changed: [R1]
TASK [Validations on all hosts] ****************************************************************************************
Wednesday 13 September 2023 10:46:27 -0500 (0:00:01.718) 0:00:09.300 ***
[WARNING]: errors were encountered during the plugin load for napalm: ['napalm_validate.napalm is not a valid
collection reference']
[WARNING]: errors were encountered during the plugin load for napalm: ['napalm_validate.napalm is not a valid
collection reference']
[WARNING]: errors were encountered during the plugin load for napalm: ['napalm_validate.napalm is not a valid
collection reference']
ok: [R2]
ok: [R1]
ok: [R3]
TASK [Print validation results] ****************************************************************************************
Wednesday 13 September 2023 10:46:30 -0500 (0:00:03.552) 0:00:12.853 ***
ok: [R1] => {
"msg": {
"changed": false,
"compliance_report": {
"complies": true,
"get_bgp_neighbors": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"global": {
"complies": true,
"nested": true
}
}
},
"get_facts": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"hostname": {
"complies": true,
"nested": false
}
}
},
"ping_10.10.12.1": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"success": {
"complies": true,
"nested": true
}
}
},
"ping_10.10.13.1": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"success": {
"complies": true,
"nested": true
}
}
},
"skipped": []
},
"failed": false
}
}
ok: [R2] => {
"msg": {
"changed": false,
"compliance_report": {
"complies": true,
"get_bgp_neighbors": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"global": {
"complies": true,
"nested": true
}
}
},
"get_facts": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"hostname": {
"complies": true,
"nested": false
}
}
},
"ping_10.10.12.2": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"success": {
"complies": true,
"nested": true
}
}
},
"ping_10.10.23.2": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"success": {
"complies": true,
"nested": true
}
}
},
"skipped": []
},
"failed": false
}
}
ok: [R3] => {
"msg": {
"changed": false,
"compliance_report": {
"complies": true,
"get_bgp_neighbors": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"global": {
"complies": true,
"nested": true
}
}
},
"get_facts": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"hostname": {
"complies": true,
"nested": false
}
}
},
"ping_10.10.13.3": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"success": {
"complies": true,
"nested": true
}
}
},
"ping_10.10.23.3": {
"complies": true,
"extra": [],
"missing": [],
"present": {
"success": {
"complies": true,
"nested": true
}
}
},
"skipped": []
},
"failed": false
}
}
PLAY RECAP *************************************************************************************************************
R1 : ok=7 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
R2 : ok=7 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
R3 : ok=7 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Wednesday 13 September 2023 10:46:31 -0500 (0:00:00.523) 0:00:13.376 ***
===============================================================================
Validations on all hosts ---------------------------------------------------------------------------------------- 3.55s
Gathering Facts ------------------------------------------------------------------------------------------------- 2.62s
Configure Interfaces -------------------------------------------------------------------------------------------- 2.28s
Configure Layer 3 Interfaces ------------------------------------------------------------------------------------ 2.00s
Configure BGP on all hosts -------------------------------------------------------------------------------------- 1.72s
Validate Layer 3 Interfaces ------------------------------------------------------------------------------------- 0.67s
Print validation results ---------------------------------------------------------------------------------------- 0.52s
As you can see we have 7 tasks that ran successfully and 3 of those had changes. The changes were to configure the interfaces, configure the IP addresses on those interfaces and to configure the BGP on the routers. The validation results are printed in step 7 and shown on the console and as you can tell everything was successfully validated, so that means everything is successfully configured.
Hope that you were able to learn something new today and gained some insight on how to configure BGP on Cisco IOS devices using Ansible.
Komentáře