Advanced Network Automation Using the Cisco IOS Command and Configuration Modules in Ansible

The transition from manual Command Line Interface (CLI) management to automated orchestration represents a paradigm shift for network engineers. Within the Cisco ecosystem, the most widely deployed network operating system remains Cisco IOS, which serves as the foundational backbone for enterprise architectures ranging from branch office routers to high-density campus switches. Managing these devices at scale requires a move away from artisanal, one-by-one configuration toward a programmable infrastructure. Ansible emerges as the primary tool for this evolution, utilizing the cisco.ios collection to provide purpose-built modules that abstract the complexities of SSH interactions, privileged mode escalation, and configuration state management. By leveraging modules such as ios_command and ios_config, engineers can push configuration changes, gather operational telemetry, and enforce compliance across a global fleet in minutes rather of hours.

The Architectural Foundation of Ansible for Cisco IOS

To successfully execute any automation task on Cisco IOS, a robust foundation involving the correct collection and environment configuration is mandatory. The primary entry point is the installation of the specialized collection, which contains the logic required to communicate with IOS devices.

The installation is performed via the command line using the following command:

ansible-galaxy collection install cisco.ios

Once the collection is installed, the interaction between the Ansible control node and the network devices is governed by specific connection variables. Because most network operating systems do not support SSH key authentication—the standard for Linux server automation—Ansible relies on password-based authentication and specific network drivers.

The connectivity layer is defined by the ansible_connection variable. In modern deployments, ansible.netcommon.network_cli is the preferred driver, as it is designed specifically for CLI-based network devices. For older setups or specific requirements, paramiko may be used as the underlying driver for CLI interaction.

Comprehensive Inventory Management and Variable Definition

The inventory is the source of truth for the automation engine. It defines which devices are being targeted and provides the necessary credentials and connection parameters. Depending on the engineer's preference, inventories can be structured in .ini or .yaml formats. YAML is generally preferred for its ability to handle complex object hierarchies and cleaner visual structure.

A YAML inventory typically begins with a top-level dictionary and requires a start-of-file marker. For example:

```yaml

cmliosxemachines:
hosts:
AnsLabIOSXEv-1:
ansible
host: "10.7.3.10"
vars:
ansiblenetworkos: "ios"
ansibleuser: "admin"
ansible
password: "cisco"
ansiblebecome: "yes"
ansible
becomemethod: "enable"
ansible
connection: "ansible.netcommon.network_cli"
```

In this structure, cml_ios_xe_machines serves as the root dictionary key. When writing a playbook, this name is used under the hosts: directive to ensure the playbook targets only the devices within that specific group.

The Role of Privileged Access and the Become Mechanism

Most configuration changes on a Cisco device require "privileged EXEC" mode (the enable prompt). Ansible handles this transition automatically through the "become" mechanism.

  • ansible_become: This variable instructs Ansible that an additional command is required to achieve escalated privileges. Setting this to yes triggers the escalation process.
  • ansible_become_method: This defines the specific command used to escalate. For Cisco IOS, this is set to enable.
  • ansible_become_password: This provides the password required to enter the enable mode. In production environments, this should never be stored in plain text; instead, it should be referenced via Ansible Vault, such as {{ vault_enable_password }}.

The impact of these settings is critical; without the proper become configuration, any attempt to modify the running configuration or execute certain privileged show commands will result in a permission denied error from the IOS device.

Deep Dive into the ios_command Module

The cisco.ios.ios_command module is designed for operational tasks. Its primary purpose is to send a command to the device and retrieve the resulting output. Unlike configuration modules, ios_command is used to read the state of the device.

Key Parameters and Functionality

The module offers several parameters to control how commands are executed and how the output is processed:

  • commands: A list of one or more CLI commands to be executed.
  • authorize: When set to yes, the module ensures the device is in privileged mode before executing the command.
  • waitfor: This is a powerful verification tool. It allows the playbook to wait for specific conditions to be met in the command output before progressing. This is typically used to verify that a change has actually taken effect.

Practical Application: Verifying Access Control Lists (ACLs)

A common use case for ios_command is verifying the existence of a specific configuration line after a change has been pushed.

yaml - name: Verify ACL is present ios_command: commands: - sh access-l waitfor: - result[0] contains 'permit 172.16.1.100'

In this scenario, the waitfor parameter checks if the first element of the result contains the string permit 172.16.1.100. If the string is not found, the task fails, providing an immediate feedback loop for the engineer.

Advanced Usage and Output Filtering

The ios_command module can be used to gather specific sections of the configuration. For instance, to isolate interface configurations, an engineer might use the pipe command within the IOS CLI:

yaml - name: Run show command cisco.ios.ios_command: commands: - show running-config | section ^interface wait_for: - result[1] contains GigabitEthernet0/1

It is important to note that some older versions of IOS (such as version 15.0.2 on certain 3750 series switches) may not support the section keyword in the show running-config command. In such cases, the engineer must use different filtering methods or standard show commands to retrieve the necessary data.

Mastering the ios_config Module

While ios_command is for reading, cisco.ios.ios_config is the "workhorse" for writing. This module automates the process of entering global configuration mode (configure terminal) and exiting it (end).

Core Capabilities of ios_config

The module streamlines configuration by allowing the engineer to provide a list of commands that need to be applied to the device.

  • lines: This parameter accepts a list of configuration commands. Each item in the list is sent to the device in configuration mode.
  • save: When set to yes, this parameter ensures that the running configuration is saved to the startup configuration (equivalent to write memory or copy running-config startup-config).
  • authorize: Similar to the command module, this ensures the session has the necessary privileges to enter configuration mode.

Example: Deploying an Access Control List (ACL)

The following example demonstrates how to apply a specific security rule to a switch:

yaml - name: Configure ACL on Cisco Switch ios_config: authorize: yes lines: - access-list 99 permit 172.16.1.100

Following the application of this rule, it is best practice to save the configuration to ensure persistence across reboots:

yaml - name: SAVE CONFIG ios_config: authorize: yes save: yes

Specialized Modules and Advanced Orchestration

Beyond basic commands and configurations, Ansible provides specialized modules for specific networking tasks. One such example is cisco.ios.ios_l2_interfaces, which allows for the structured management of Layer 2 interface properties.

Managing Layer 2 Interfaces

Instead of sending raw CLI strings, the ios_l2_interfaces module uses a structured data format to define the state of an interface.

yaml - name: Merge provided configuration with device configuration cisco.ios.ios_l2_interfaces: config: - name: GigabitEthernet0/1 mode: trunk trunk: allowed_vlans: 10-20,40 native_vlan: 20 pruning_vlans: 10,20 encapsulation: dot1q state: merged

The state: merged parameter is crucial here; it tells Ansible to combine the provided configuration with the existing settings on the device rather than replacing the entire interface configuration.

Handling Non-Idempotent Configurations with Jinja2

A significant challenge in network automation is the lack of specific modules for every possible IOS feature. For example, there is no dedicated cisco.ios module for Layer 3 802.1q subinterfaces. To solve this, engineers can use Jinja2 templates to generate the required CLI commands.

A template file (e.g., subinterface.j2) would look like this:

jinja2 {% for i in ios_interfaces %} interface {{ i.name }} encapsulation dot1q {{ i.tag }} {% endfor %}

This template uses a for loop to iterate through a list of interfaces provided in the Ansible vars section. The i iterator represents each interface dictionary, allowing the template to dynamically insert the interface name and the VLAN tag.

Because the ios_config module is used to push these templated lines, the process becomes non-idempotent. This means Ansible cannot determine if the change is already present on the device and will push the configuration every time the playbook runs.

Operational Data Collection with ios_facts

To move toward a truly data-driven network, engineers use the cisco.ios.ios_facts module. This module collects structured data from the device, which can then be used for conditional logic or reporting.

Gathering and Utilizing Facts

The gather_subset: all parameter ensures that all available hardware, software, and interface information is collected.

yaml - name: Gather Cisco IOS facts cisco.ios.ios_facts: gather_subset: - all register: facts

Once registered, these facts can be printed using the ansible.builtin.debug module to provide a summary of the device state:

yaml - name: Display device summary ansible.builtin.debug: msg: | Device: {{ ansible_net_hostname }} Model: {{ ansible_net_model }} IOS Version: {{ ansible_net_version }} Serial Number: {{ ansible_net_serialnum }} Image: {{ ansible_net_image }}

For more complex analysis, such as listing all interfaces on a device, the ansible_net_interfaces fact can be manipulated using filters:

yaml - name: List all interfaces ansible.builtin.debug: msg: "{{ ansible_net_interfaces | dict2items | map(attribute='key') | list }}"

Summary Comparison of Primary IOS Modules

The following table summarizes the primary modules used for Cisco IOS automation.

Module Name Primary Purpose Key Parameter Idempotent
ios_command Reading operational state commands No (Read-only)
ios_config Pushing configuration lines lines Partially
ios_facts Gathering device telemetry gather_subset Yes
ios_l2_interfaces Managing L2 port settings config Yes

Conclusion

The integration of Ansible with Cisco IOS transforms network management from a manual, error-prone process into a disciplined software engineering practice. By combining the operational capabilities of ios_command for verification, the configuration power of ios_config for deployment, and the data-gathering precision of ios_facts, network engineers can achieve a high degree of consistency across their infrastructure. While some gaps exist—such as the lack of specific modules for L3 subinterfaces—the use of Jinja2 templating provides a flexible workaround. The shift toward structured data and the use of the cisco.ios collection not only reduces the time required for deployment but also ensures that the network state is documented, repeatable, and verifiable.

Sources

  1. Roger Perkin - Ansible IOS Command Example
  2. Cisco Community - Cisco 3750 Ansible IOS Commands
  3. Dev.to - Automate Cisco IOS/IOS-XE Documentation with Ansible
  4. OneUptime - How to Use Ansible with Cisco IOS Devices

Related Posts