How to save IOS configurations with Ansible

  • by Patrick Ogenstad
  • October 23, 2017

Slow save At the outset, a 1200 word article about saving configuration sounds strange. It would perhaps be perfectly normal if the topic was Vi and not Ansible, however there’s a reason for this and its simply speed and itempotency. Saving the configuration in the “wrong” way can take quite a lot of time and one reason for network automation is to accomplish tasks faster and constantly search for ways to improve your processes. This article assumes that you are running Ansible 2.4, but it should work in a similar way regardless.

What is the wrong way to save

First of all, it might not be obvious that changes made by the ios_config module aren’t saved by default. The module does however allow you to save the configuration using the save_when parameter. You could say that it’s a bit harsh to say that it’s wrong to use that option, but I don’t like it very much.

In Ansible 2.2 the save parameter was introduced, it was a boolean option which allowed you to choose if the configuration should be saved or not. While this allowed users to start to save their configurations there was a small problem with this. The save parameter wasn’t idempotent.

- name: Configure SNMP location
  ios_config:
    provider: "{{ cli }}"
    lines: "snmp-server location TEST"
    save: true

Running the above task multiple times wouldn’t change the configuration, it would however always save the configuration and set the changed key to True. So it would look like something changed on the device. Ansible 2.4 deprecated the save parameter and instead introduced save_when which you can set to never (default), always (same as the old save) and modified.

Using “save_when: modified”

Imagine a playbook which looks like the one below, it would have three tasks where each one would trigger a change and save the configuration.

- name: Set SNMP location 1
  ios_config:
    lines: "snmp-server location TEST1"
  save_when: modified

- name: Set SNMP location 2
  ios_config:
    lines: "snmp-server location TEST2"
  save_when: modified

- name: Set SNMP location 3
  ios_config:
    lines: "snmp-server location TEST3"
  save_when: modified

Obviously a playbook like this doesn’t make any sense, the point is that the configuration would be saved on each task. On some devices this wouldn’t matter that much, however it’s not that uncommon that it takes several seconds to write the configuration to flash. On older devices we could be talking about up to a minute so the above playbook could take close to three minutes to complete. If the device is slow enough to take longer than 10 seconds for it to save the configuration the task will fail due to the default timeout of ten seconds. To avoid an issue like that you might have to do something like this instead:

- name: Set SNMP location 1
  ios_config:
    lines: "snmp-server location TEST1"
    timeout: 40
  save_when: modified

- name: Set SNMP location 2
  ios_config:
    lines: "snmp-server location TEST2"
    timeout: 40
  save_when: modified

- name: Set SNMP location 3
  ios_config:
    lines: "snmp-server location TEST3"
    timeout: 40
  save_when: modified

Based on the above it should be quite clear that it’s probably a good idea not to have a save statement for every instance of ios_config. Of course another issue with this is that the ios_config module has an option to save the configuration, but other ios modules released with Ansible 2.4 such as ios_interface, ios_logging and ios_system doesn’t.

When is the configuration saved?

In Ansible 2.4 the modified argument will save the configuration if the startup configuration is different than the running configuration. So you could opt for only using the save_when parameter on the last command as in:

- name: Set SNMP community
  ios_config:
    lines: "snmp-server community networklore"

- name: Set SNMP contact
  ios_config:
    lines: "snmp-server contact SUPPORT"

- name: Set SNMP location
  ios_config:
    lines: "snmp-server location TEST"
    timeout: 40
  save_when: modified

In theory this would only save the configuration if it actually needed to be saved. However on Cisco IOS there exists a number of scenarios where the running configuration could be changed in a dynamic way which either doesn’t appear in the startup configuration or it isn’t relevant from a change management perspective. The most typical example is the ntp period which can look like this in the running configuration:

ntp clock-period 17174051

The counter will change by time and running a playbook with save_when: modified could trigger a change even if the configuration hasn’t actually changed. In order to work around this you can use the diff_ignore_lines parameter to ios_config and send in a list of lines to ignore. Even if you find all of the lines you want to ignore when trying to determine if a save is needed or not you might still run into issues with certificates. Let’s compare how a self-signed certificate is shown in the running configuration compared to the startup configuration.

R1# show running-config | begin crypto pki certificate
crypto pki certificate chain TP-self-signed-963592824
 certificate self-signed 01
  3082032E 30820216 A0030201 02020101 300D0609 2A864886 F70D0101 05050030
  30312E30 2C060355 04031325 494F532D 53656C66 2D536967 6E65642D 43657274
  69666963 6174652D 39363335 39323832 34301E17 0D313731 30323331 34323832
[...]
 058D6639 A7F9CB5B F3BBA7FF 093E745B B50DB469 66492830 060E4C07 47B72D0C
  CB47AEA7 F0F17C7B 15D10F88 D705CB8B 6F2BE8EA 91E9073E 504F231D AFC5F830
  67E4C681 B912CF40 8023B729 19AFAF8E 0F1E
  	quit

R1# show startup-config | begin crypto pki certificate
crypto pki certificate chain TP-self-signed-963592824
 certificate self-signed 01 nvram:IOS-Self-Sig#1.cer

The output from the show running command above is abbreviated for readability but just by looking at the short snippet it should be obvious that it isn’t practical to compare the two line by line and do a diff, in this case the diff_ignore_lines doesn’t offer much help. What the ios_config module does it to run show running-config followed by show startup-config, substract the diff_ignore_lines and calculate a sha1 hash of the resulting configurations. If the hash differs a Ansible sends a copy running-config startup-config command.

The result could be that the playbook isn’t idempotent and also takes longer time to complete as we have to wait for the configuration to be saved to startup.

Saving with a handler

A solution to this problem is to notify a handler. A handler let’s you run a specific task if another one has changed. You can have several tasks which notify the same handler and it will still only run once.

---
-  hosts: all
   connection: local
   gather_facts: no

   tasks: 
    - name: Set SNMP community
      ios_config:
       lines: "snmp-server community networklore"
      notify: "save ios"

    - name: Set SNMP contact
      ios_config:
        lines: "snmp-server contact SUPPORT"
      notify: "save ios"

    - name: Set SNMP location
      ios_config:
        lines: "snmp-server location TEST"
      notify: "save ios"

   handlers:
     - name: save ios
       ios_command:
         commands: "write mem"
         timeout: 40
       when: not ansible_check_mode

This playbook would only save the configuration if any of the config tasks resulted in a changed status. The reason to add the when condition is so that the handler wouldn’t trigger a save if the playbook was run in check mode.

Roles and larger playbooks

If you are using a larger structure with several roles you don’t want to trigger a handler local to each of the roles as that would just trigger a save action for each role. Instead you can create a specific role to save configuration and add that role as a dependency under the a meta folder in main.yml as in:

---
dependencies:
  - role: ios_wr_mem

Summary

I hope this will help you to make sure your playbooks remain idempotent as well as perhaps shave of some time for your runs. Hopefully you learned something more about Ansible too.