top of page
  • Writer's pictureTaylor Etheredge

Configuring BGP on Cisco IOS devices using Ansible

Updated: Nov 10, 2023

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.

29 views0 comments
bottom of page