Cisco IOS configurations use a simple block indent file syntax for segmenting configuration into sections. Ansible ios_config module provides an implementation for working with IOS configuration sections in a deterministic way.
This post continues my previous post Anisble with Cisco Part 1.
Ansible with Cisco - Part 1 Installation and basic set up
InstallationI’m going to install Ansible on my Raspberry Pi for this examle. You can use anyother OS including Ubuntu, MacOS etc. Our end goal is to manage both of therouters via Ansible. In this example, we will get the ‘show version | inclVersion’ output on both routers using a very basic Ansi…
Agenda Make some basic configuration changes to the Cisco IOS routers using ios_config module.
Configure syslog server on both routers.
notify and handlers
ios_config with variables
parents
host_vars
Diagram diagram Documentation https://docs.ansible.com/ansible/2.9/modules/ios_config_module.html
Please note that I'm using Ansible on a different VM now. I copied the playbooks, inventory and variables to the new VM. I have also created an ansible.cfg
file so, I don't have to specify the hosts
location each time I run a play book.
ubuntu@ubuntu:~/playbooks/network_ops$ cat playbooks/ansible.cfg
[defaults]
inventory = /home/ubuntu/playbooks/network_ops/inventory/host-file
ubuntu@ubuntu:~/playbooks/network_ops$ pwd
/home/ubuntu/playbooks/network_ops
ubuntu@ubuntu:~/playbooks/network_ops$ tree
.
├── inventory
│ ├── group_vars
│ │ └── routers
│ │ ├── routers.yml
│ │ └── vault
│ └── host-file
└── playbooks
├── ansible.cfg
├── show_ip_int_brief.yml
├── show_run.yml
└── show_version.yml
4 directories, 7 files
Imagine you are managing 50 routers and you need to configure logging host 192.168.1.125
on each routers. It would be time consuming to configure each device one by one. Let's do this first task via Ansible.
The commands under lines: are exactly what you would type into the CLI if you are on that device.
Let's create a playbook and run it.
Please note that I'm using ios_config module in this example. I have used ios_command module in the previous example. (part - 1) ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ cat logging_host.yml
---
- name: Cisco syslog server
hosts: routers
gather_facts: false
connection: network_cli
tasks:
- name: configure logging host
ios_config:
lines: logging host 192.168.1.125
register: output
- name: print output
debug:
var: output
playbook ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ ansible-playbook logging_host.yml --ask-vault-pass
Vault password:
PLAY [Cisco syslog server] **********************************************************************************************************
TASK [configure logging host] *******************************************************************************************************
changed: [router-1]
changed: [router-2]
TASK [print output] *****************************************************************************************************************
ok: [router-1] => {
"output": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"banners": {},
"changed": true,
"commands": [
"logging host 192.168.1.125"
],
"failed": false,
"updates": [
"logging host 192.168.1.125"
]
}
}
ok: [router-2] => {
"output": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"banners": {},
"changed": true,
"commands": [
"logging host 192.168.1.125"
],
"failed": false,
"updates": [
"logging host 192.168.1.125"
]
}
}
PLAY RECAP **************************************************************************************************************************
router-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
router-2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
run playbook Let's verify on both routers.
router-1#show run | incl logging host
logging host 192.168.1.125
router-2#show run | incl logging host
logging host 192.168.1.125
That was very easy, isn't? If I run the playbook again, Ansible will not re-apply the config. Ansible is clever enough to know that the config line is already there.
Notify and Handlers Sometimes we need a task to run only when a change is made to the devices. For example, we may want to save the running-config
if a change is made, but not if the configuration is unchanged.
Ansible uses handlers to address this use case. Handlers are tasks that only run when notified. Let's make another change and make sure the running-config
is saved. I'm going to disable CDP on both routers by using no cdp run
command.
ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ cat no_cdp.yml
---
- name: Disable CDP
hosts: routers
gather_facts: false
connection: network_cli
tasks:
- name: Disable CDP on both routers
ios_config:
lines:
- no cdp run
notify: save config
handlers:
- name: save config
ios_command:
commands: wr
no cdp run playbook ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ ansible-playbook no_cdp.yml --ask-vault-pass
Vault password:
PLAY [Disable CDP] *************************************************
TASK [Disable CDP on both routers] ********************************************************************
changed: [router-2]
changed: [router-1]
RUNNING HANDLER [save config] ********************************************************************
ok: [router-2]
ok: [router-1]
PLAY RECAP *********************************************************
router-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
router-2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
playbook run router-1#show startup-config | incl cdp
no cdp run
router-2#show startup-config | incl cdp
no cdp run
start-up config on both routers save_when parameter There is another way to save the configuration by using save_when parameter as shown below. If the argument is set to modified , then the running-config will only be copied to the startup-config if it has changed since the last save to startup-config.
ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ cat save_when.yml
---
- name: Disable CDP
hosts: routers
gather_facts: false
connection: network_cli
tasks:
- name: Disable CDP on both routers
ios_config:
lines:
- no cdp run
- name: Save running-config
ios_config:
save_when: modified
save_when ios_config with Variables We can also have the configuration lines in the group_vars file as a variable instead of hard coding in the playbook.
ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ cat ../inventory/group_vars/routers/routers.yml
---
ansible_network_os: ios
ansible_user: ansible
ansible_become: yes
ansible_become_method: enable
ansible_become_password: "{{ vault_ansible_become_password }}"
ansible_password: "{{ vault_ansible_password }}"
acl:
- access-list 101 permit tcp any any eq 80
- access-list 101 permit tcp any any eq 443
group_var ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ cat acl.yml
---
- name: Variable Lab
hosts: routers
gather_facts: false
connection: network_cli
tasks:
- name: configure ACL
ios_config:
lines: "{{ acl }}"
register: output
- name: print output
debug:
var: output
acl playbook ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ ansible-playbook acl.yml --ask-vault-pass
Vault password:
PLAY [Variable Lab] *****************************************************************************************************************
TASK [configure ACL] ****************************************************************************************************************
changed: [router-2]
changed: [router-1]
TASK [print output] *****************************************************************************************************************
ok: [router-1] => {
"output": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"banners": {},
"changed": true,
"commands": [
"access-list 101 permit tcp any any eq 80",
"access-list 101 permit tcp any any eq 443"
],
"failed": false,
"updates": [
"access-list 101 permit tcp any any eq 80",
"access-list 101 permit tcp any any eq 443"
]
}
}
ok: [router-2] => {
"output": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"banners": {},
"changed": true,
"commands": [
"access-list 101 permit tcp any any eq 80",
"access-list 101 permit tcp any any eq 443"
],
"failed": false,
"updates": [
"access-list 101 permit tcp any any eq 80",
"access-list 101 permit tcp any any eq 443"
]
}
}
PLAY RECAP **************************************************************************************************************************
router-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
router-2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
playbook run parents The ordered set of parents that uniquely identifies the section or hierarchy the commands should be checked against. If the parents argument is omitted, the commands are checked against the set of top-level or global commands.
The commands in the previous examples are meant to be in global config mode so, we didn't use parents. However, for example, If we need to configure an interface, the commands need to be under the specific Interface. So, we need to add the parents: line in the task.
ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ cat parents.yml
---
- name: Parents Example
hosts: routers
gather_facts: false
connection: network_cli
tasks:
- name: Interface Description - WAN
ios_config:
lines:
- description WAN
parents: Interface GigabitEthernet0/1
notify: save config
handlers:
- name: save config
ios_command:
commands: wr
parents playbook ubuntu@ubuntu:~/playbooks/network_ops/playbooks$ ansible-playbook parents.yml --ask-vault-pass
Vault password:
PLAY [Parents Example] ********************************************************************************************************************************************
TASK [Interface Description - WAN] ********************************************************************************************************************************
changed: [router-2]
changed: [router-1]
RUNNING HANDLER [save config] *************************************************************************************************************************************
ok: [router-1]
ok: [router-2]
PLAY RECAP ********************************************************************************************************************************************************
router-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
router-2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
parents playbook run router-1#show interfaces description
Interface Status Protocol Description
Gi0/1 admin down down WAN
router-2#show interfaces description
Interface Status Protocol Description
Gi0/1 admin down down WAN
host_vars The host-specific variables are defined in the host_vars directory . Each file in the host_vars directory is named after the host it represents.
host_vars Let's create a playbook to demonstrate the usage of host_vars. So far in our examples, we have seen how to send the exact same commands to both of the devices. In a real world, we may need to send different commands to each device, for example interface configs where the configs are unique to each device.
ubuntu@ubuntu:~/ansible_cisco$ tree
.
├── inventory
│ ├── group_vars
│ │ └── routers
│ │ ├── routers.yml
│ │ └── vault
│ ├── host-file
│ └── host_vars <<< host_vars directory
│ ├── router-1.yml <<< represents the host
│ └── router-2.yml <<< represents the host
├── playbooks
│ ├── acl.yml
│ ├── ansible.cfg
│ ├── host_vars_example.yml <<< host_var example
│ ├── logging_host.yml
│ ├── no_cdp.yml
│ ├── parents.yml
│ ├── save_when.yml
│ ├── show_ip_int_brief.yml
│ ├── show_run.yml
│ └── show_version.yml
└── README.md
5 directories, 16 files
host_var directory ubuntu@ubuntu:~/ansible_cisco$ cat inventory/host_vars/router-1.yml
---
desc:
- description WAN1
- ip address 172.16.1.1 255.255.255.0
- no shutdown
router-1.yml ubuntu@ubuntu:~/ansible_cisco$ cat inventory/host_vars/router-2.yml
---
desc:
- description WAN2
- ip address 172.16.1.2 255.255.255.0
- no shutdown
router-2.yml ubuntu@ubuntu:~/ansible_cisco$ cat playbooks/host_vars_example.yml
---
- name: host_vars Example
hosts: routers
gather_facts: false
connection: network_cli
tasks:
- name: Interface configs
ios_config:
lines: "{{ desc }}"
parents: Interface GigabitEthernet0/1
before: default interface GigabitEthernet0/1
register: output
- name: Save running-config
ios_config:
save_when: modified
- name: print output
debug:
var: output
host_var example before - Ansible will run the default interface GigabitEthernet0/1
command prior to actually running the interface configs.
ubuntu@ubuntu:~/ansible_cisco/playbooks$ ansible-playbook host_var_example.yml --ask-vault-pass
Vault password:
PLAY [host_vars Example] ************************************************************************************************************************************************************************************************************
TASK [Interface configs] ************************************************************************************************************************************************************************************************************
changed: [router-1]
changed: [router-2]
TASK [Save running-config] **********************************************************************************************************************************************************************************************************
ok: [router-1]
ok: [router-2]
TASK [print output] *****************************************************************************************************************************************************************************************************************
ok: [router-1] => {
"output": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"banners": {},
"changed": true,
"commands": [
"default interface GigabitEthernet0/1",
"Interface GigabitEthernet0/1",
"description WAN1",
"ip address 172.16.1.1 255.255.255.0",
"no shutdown"
],
"failed": false,
"updates": [
"default interface GigabitEthernet0/1",
"Interface GigabitEthernet0/1",
"description WAN1",
"ip address 172.16.1.1 255.255.255.0",
"no shutdown"
]
}
}
ok: [router-2] => {
"output": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"banners": {},
"changed": true,
"commands": [
"default interface GigabitEthernet0/1",
"Interface GigabitEthernet0/1",
"description WAN2",
"ip address 172.16.1.2 255.255.255.0",
"no shutdown"
],
"failed": false,
"updates": [
"default interface GigabitEthernet0/1",
"Interface GigabitEthernet0/1",
"description WAN2",
"ip address 172.16.1.2 255.255.255.0",
"no shutdown"
]
}
}
PLAY RECAP **************************************************************************************************************************************************************************************************************************
router-1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
router-2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
playbook run Reference https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html
https://netsudrun.wordpress.com/
Thanks for reading. As always, your feedback and comments are more than welcome.