In the last post we built an Ansible playbook to manage interfaces on EOS and NX-OS switches. In this post we are going to add BGP configuration to advertise the loopbacks we created early.
Updated Topology
The only change in the drawing is the addition of the AS numbers.
Update host_vars
We will start with the files created in the last post.
.
├── ansible.cfg
├── backup
├── group_vars
│ ├── eos.yml
│ └── nxos.yml
├── hosts-net
├── host_vars
│ ├── sw1.yml
│ └── sw2.yml
└── interface-playbook.yml
The first thing we are going to do is add the BGP configuration to the files in host_vars
.
sw1.yml
bgp:
asn: 1
routerid: 1.1.1.1
neighbors:
- address: 192.168.12.2
remoteas: 2
networks:
- address: 1.1.1.1/32
sw2.yml
bgp:
asn: 2
routerid: 2.2.2.2
neighbors:
- address: 192.168.12.1
remoteas: 1
addrfamily: ipv4 unicast
networks:
- address: 2.2.2.2/32
addrfamily: ipv4 unicast
Create configuration templates
In the last post we used ansible modules that were written to perform interface specific tasks. For BGP, an EOS module does not exist so we are going to
load a configuration files to the switches using the eos_config
and nxos_config
ansible modules. The easiest way to do this is to create Jinja2 templates. They will be stored in new folder called templates
.
eos_bgp.j2
ip routing
!
router bgp {{ bgp.asn }}
router-id {{ bgp.routerid }}
{% for neighbor in bgp.neighbors -%}
neighbor {{ neighbor.address }} remote-as {{ neighbor.remoteas }}
{% endfor -%}
{% for network in bgp.networks -%}
network {{ network.address }}
{% endfor -%}
nxos_bgp.j2
license grace-period
feature bgp
!
router bgp {{ bgp.asn }}
router-id {{ bgp.routerid }}
{% for network in bgp.networks -%}
address-family {{ network.addrfamily }}
network {{ network.address }}
{% endfor -%}
{% for neighbor in bgp.neighbors -%}
neighbor {{ neighbor.address }}
remote-as {{ neighbor.remoteas }}
address-family {{ neighbor.addrfamily }}
{% endfor -%}
The license grace-period
is because I am using an NX-OS VM. A device with a license for BGP would not require this.
The files above will loop through all of the networks and neighbors in each switches Yaml file. We only have one for each right now but we can add networks or neighbors without having to rewrite the template.
Creating and running the playbooks
We currently have a single playbook that backups the configuration, sets up the interfaces, and finally writes the switches memory. In order to avoid duplicating code, I am going to split out the backup and write memory tasks into separate playbooks.
backup-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 }}"
writemem-playbook.yml
---
- hosts: eos
gather_facts: no
tasks:
- name: Save eos configuration
eos_config:
save_when: modified
- hosts: nxos
gather_facts: no
tasks:
- name: Save nxos configuration
nxos_config:
save_when: modified
changed_when: false
Now we can create the playbook that adds the BGP configuration. I will also add a configs
folder.
bgp-playbook.yml
---
- hosts: eos:nxos
gather_facts: no
tasks:
- name: Create configuration templates
template:
src: "templates/{{ ansible_network_os }}_bgp.j2"
dest: "configs/{{ inventory_hostname }}.config"
- hosts: eos
gather_facts: no
tasks:
- name: load BGP config eos
eos_config:
src: "configs/{{ inventory_hostname }}.config"
register: updates
- name: debug updates
debug:
msg: "{{ updates.updates }}"
- hosts: nxos
gather_facts: no
tasks:
- name: load BGP config nxos
nxos_config:
src: "configs/{{ inventory_hostname }}.config"
register: updates
- name: debug updates
debug:
msg: "{{ updates.updates }}
The first task builds the configuration files for the hosts. The next task loads the configuration to switches in the eos group. The final task loads the configuration to switches in the nxos group. The debug task prints the commands that will be loaded on the switch. The final file we are going to create is a playbook that runs all of the playbooks at once.
all-playbook.yml
---
- import_playbook: backup-playbook.yml
- import_playbook: interface-playbook.yml
- import_playbook: bgp-playbook.yml
- import_playbook: writemem-playbook.yml
Here is what our directory looks like now.
.
├── all-playbook.yml
├── ansible.cfg
├── backup
├── backup-playbook.yml
├── bgp-playbook.yml
├── configs
├── group_vars
│ ├── eos.yml
│ └── nxos.yml
├── hosts-net
├── host_vars
│ ├── sw1.yml
│ └── sw2.yml
├── interface-playbook.yml
├── templates
│ ├── eos_bgp.j2
│ └── nxos_bgp.j2
└── writemem-playbook.yml
It is finally time to run the playbooks. I am going omit the output of the tasks that were run in the last post and focus on the BGP changes.
ansible-playbook all-playbook.yml
PLAY [eos] ************************************************************************************************************************************************************************************
TASK [Backup eos device] **********************************************************************************************************************************************************************
--snip--
PLAY [eos:nxos] *******************************************************************************************************************************************************************************
TASK [Create configuration templates] *********************************************************************************************************************************************************
changed: [sw2]
changed: [sw1]
PLAY [eos] ************************************************************************************************************************************************************************************
TASK [load BGP config eos] ********************************************************************************************************************************************************************
changed: [sw1]
TASK [debug updates] **************************************************************************************************************************************************************************
ok: [sw1] => {
"msg": [
"ip routing",
"router bgp 1",
"router-id 1.1.1.1",
"neighbor 192.168.12.2 remote-as 2",
"network 1.1.1.1/32"
]
}
PLAY [nxos] ***********************************************************************************************************************************************************************************
TASK [load BGP config nxos] *******************************************************************************************************************************************************************
changed: [sw2]
TASK [debug updates] **************************************************************************************************************************************************************************
ok: [sw2] => {
"msg": [
"license grace-period",
"feature bgp",
"router bgp 2",
"router-id 2.2.2.2",
"address-family ipv4 unicast",
"network 2.2.2.2/32",
"neighbor 192.168.12.1",
"remote-as 1",
"address-family ipv4 unicast"
]
}
--snip--
PLAY RECAP ************************************************************************************************************************************************************************************
sw1 : ok=8 changed=3 unreachable=0 failed=0
sw2 : ok=7 changed=2 unreachable=0 failed=0
The first play created the configuration files from the templates.
sw1.config
ip routing
!
router bgp 1
router-id 1.1.1.1
neighbor 192.168.12.2 remote-as 2
network 1.1.1.1/32
sw2.config
license grace-period
feature bgp
!
router bgp 2
router-id 2.2.2.2
address-family ipv4 unicast
network 2.2.2.2/32
neighbor 192.168.12.1
remote-as 1
address-family ipv4 unicast
The next two plays load the the .config
file on the devices. Lets see if it worked.
SW1#show ip bgp
BGP routing table information for VRF default
Router identifier 1.1.1.1, local AS number 1
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop
Network Next Hop Metric LocPref Weight Path
* > 1.1.1.1/32 - 0 0 - i
* > 2.2.2.2/32 192.168.12.2 0 100 0 2 i
SW2# show ip bgp
BGP routing table information for VRF default, address family IPv4 Unicast
BGP table version is 5, local router ID is 2.2.2.2
Status: s-suppressed, x-deleted, S-stale, d-dampened, h-history, *-valid, >-best
Path type: i-internal, e-external, c-confed, l-local, a-aggregate, r-redist, I-i
njected
Origin codes: i - IGP, e - EGP, ? - incomplete, | - multipath, & - backup
Network Next Hop Metric LocPrf Weight Path
*>e1.1.1.1/32 192.168.12.1 0 1 i
*>l2.2.2.2/32 0.0.0.0 100 32768 i
Looks like everything worked. The Jinja Templates are a very powerful tool and can handle things like for loops and if statements, which allows you to use the same template for multiple tasks. The source code for this post can be found here.