Network configuration with Ansible
It’s the next morning, and in our story, the crisis has been averted… but the way you fixed that router wasn’t exactly sustainable. Manually copying and pasting configs in the middle of the night? We can do better than that!
Infrastructure as Code
We’ll be making use of Network Resource Modules, which differ from other module designs you may have worked with in Ansible.
Ansible network resource modules simplify and standardize how you manage different network devices. Network devices separate configuration into sections (such as interfaces and VLANs) that apply to a network service. Ansible network resource modules take advantage of this to allow you to configure subsections or resources within the network device configuration. Network resource modules provide a consistent experience across different network devices. Source and more info: Ansible Documentation |
In the file explorer on the left side of your VS Code environment, expand the playbooks
folder, then click on gather_ios_routing.yml
to open it.

This is a playbook that gathers a minimal configuration from rtr1
in our lab using resource modules cisco.ios.ios_interfaces
and cisco.ios.ios_bgp_global
. You can see that the state
parameter is set to "gathered".
Since we are only using the "gathered" resource state, this playbook will be read-only. So, we can try this out as much as we want without affecting anything.
Let’s run it (using -vvv
for verbosity level 3):
ansible-navigator run playbooks/gather_ios_routing.yml -vvv
In the first task’s output, we see:
ok: [rtr1] => {
"changed": false,
"gathered": [
{
"enabled": true,
"name": "GigabitEthernet1"
},
{
"enabled": true,
"name": "Loopback0"
},
{
"enabled": true,
"name": "Tunnel0"
},
{
"enabled": true,
"name": "Tunnel1"
},
[...]
With Ansible Network Resources, the interfaces resource only contains basic interface configuration, such as enabled true/false. There are other interface resources (l2_interfaces and l3_interfaces ) for Layer 2 and Layer 3 configuration aspects.
|
In the second task’s output, we see:
ok: [rtr1] => {
"changed": false,
"gathered": {
"as_number": "65000",
"bgp": {
"default": {
"ipv4_unicast": true,
"route_target": {
"filter": true
}
},
"log_neighbor_changes": true,
"router_id": {
"address": "192.168.1.1"
}
},
"neighbors": [
{
"ebgp_multihop": {
"enable": true,
"hop_count": 255
},
"neighbor_address": "10.200.200.2",
"remote_as": "65001"
}
]
},
[...]
This is the same configuration that we just saw in Exercise 1.2, but this time we have the module presenting it in a structured data format that’s standard across network platforms.
Right now we see the data in JSON format, but JSON and YAML are compatible formats, and Ansible has filter plugins like to_nice_yaml
available to convert between them. This capturing and converting has been done ahead of time, as we’ll see next.
In the file explorer, expand the host_vars
directory under playbooks
. There is a directory here for each router in the environment. Expand rtr1
and take a look at the files interfaces.yml
and bgp_global.yml
.

These files are YAML representations of the data we just saw (with one addition for BGP which we will look at later). The locations of these files will get them loaded into Ansible as part of the inventory.
You can look at the other files for more examples of other network resources available, and you can also look at the same files under the rtr2
directory to note the differences.
Collapse host_vars
before moving on.
Next, click on configure_ios_routing.yml
to open it.
This is a playbook that applies the configuration we just looked at. Let’s dig into a few things about this playbook:
-
The hosts line
-
Later in the lab, Event-Driven Ansible (EDA) is going to identify which devices in our lab have a bad configuration. We want to be able to only run remediation on devices that we know need it, which is what the
ansible_eda
variable is for. -
We don’t have EDA involved yet, so
ansible_eda
is not defined, and we’ll therefore default to rtr1 and rtr2 for now.
-
-
The resource
state
parameter-
For the sake of this lab, we’re using state
merged
, which is the least destructive state we can use, and is recommended for testing. -
You can check out the documentation on Network Resource Modules for a description of the available states and why you’d want to use each one. The
replaced
state, for example, would wipe out any configuration in the relevant subsection not found in the resource data, which is helpful for compliance and drift control.
-
-
The resource
config
parameter-
The module inputs are very minimal, as they reference variable data. This data comes from the inventory, which we just looked at.
-
This playbook and its resource data represent an Infrastructure as Code approach to managing the configuration on our routers. The We want to treat this code as the definitive source for what the router configurations should be, regardless of what’s currently applied. With a source of truth in place, if you want to make a change, you modify the source of truth, not the device directly. Automation is responsible for reconciling the two. |
Let’s run this playbook a couple of times.
For our first run, use the following command (using -vv
for verbosity level 2):
ansible-navigator run playbooks/configure_ios_routing.yml -vv
We should see the first task report ok
- so nothing changed. But the in the second task’s output, you should see something like below:
changed: [rtr1] \=> {"after": {"as_number": "65000", "bgp": {"default": {"ipv4_unicast": true, "route_target": {"filter": true}}, "log_neighbor_changes": true, "router_id": {"address": "192.168.1.1"}}, "neighbors": [{"neighbor_address": "10.200.200.2", "remote_as": "65001"}]}, "before": {"as_number": "65000", "bgp": {"default": {"ipv4_unicast": true, "route_target": {"filter": true}}, "log_neighbor_changes": true, "router_id": {"address": "192.168.1.1"}}, "neighbors": [{"neighbor_address": "10.200.200.2", "remote_as": "65001"}]}, "changed": true, "commands": ["router bgp 65000", "timers bgp 5 15"]}
Refer to the commands
output at the end (scroll all the way right in the box above) to see that the resource module was able to figure out which commands needed to be run to accomplish the configuration, even though our configuration data is stored as YAML. We were only missing the timers
part. This is for the lab so that BGP can more quickly tell when something is wrong, and we’ll be making use of that later.
Run the playbook again and drop the verbosity:
ansible-navigator run playbooks/configure_ios_routing.yml
This time both of the tasks report ok
. This means that our live configuration still matches our source of truth. It also means that the playbook is idempotent, meaning that we can run this playbook as many times as we want without applying any change, as long as the configuration stays correct.
Recovering from a misconfiguration
Time to break things. Let’s start by logging into rtr1
over SSH and looking at the output of a couple commands.
Run the following:
ssh rtr1
show ip interface brief
Example output:
rtr1#show ip interface brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 172.16.147.219 YES DHCP up up
Loopback0 192.168.1.1 YES manual up up
Tunnel0 10.100.100.1 YES manual up up
Tunnel1 10.200.200.1 YES manual up up
VirtualPortGroup0 192.168.35.101 YES TFTP up up
We can see that all of the interfaces on the router are up, including Tunnel0
, which we identified earlier as being involved in OSPF. Let’s look at that next.
Run the following:
show ip ospf neighbor
Expected output:
rtr1#show ip ospf neighbor
Neighbor ID Pri State Dead Time Address Interface
192.168.3.3 0 FULL/ - 00:00:30 10.100.100.2 Tunnel0
Again we see the importance of Tunnel0
. What happens if Tunnel0
gets shut down? Let’s find out.
Run the following commands:
configure terminal
interface Tunnel0
shutdown
end
When copy/pasting multi-line blocks like this in the terminal, all lines except the last one will automatically be issued. Be sure to hit [Enter] after pasting so that all lines are issued. |
Check to see that the interface came down:
show ip interface brief
Example output:
rtr1#show ip interface brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 172.16.147.219 YES DHCP up up
Loopback0 192.168.1.1 YES manual up up
Tunnel0 10.100.100.1 YES manual administratively down down
Tunnel1 10.200.200.1 YES manual up up
VirtualPortGroup0 192.168.35.101 YES TFTP up up
And see its effect on OSPF:
show ip ospf neighbor
You get no output from running this command, which is not good. We should turn Tunnel0
back on. But, rather than doing that manually, we can have Ansible get us back to a known-good state.
Exit out of the SSH session and run the configuration playbook that we were running before.
exit
ansible-navigator run playbooks/configure_ios_routing.yml
We should get changed
reported only on rtr1 and only on the interface configuration task.
[student@ansible-1 telemetry]$ ansible-navigator run playbooks/configure_ios_routing.yml
PLAY [Configure IOS Routing] **********************************************************************************
TASK [Apply interfaces config] ********************************************************************************
ok: [rtr2]
changed: [rtr1]
TASK [Apply BGP Global config] ********************************************************************************
ok: [rtr1]
ok: [rtr2]
PLAY RECAP ****************************************************************************************************
rtr1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
rtr2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
SSH back into rtr1 and check the interface state again.
ssh rtr1
show ip interface brief
show ip ospf neighbor
We should see that our state is back to normal. This shows us that we can use this playbook to recover from misconfiguration issues.
Exit your SSH session before moving on.
exit