IOS and IOS-XE Modules
Objective
In this lesson you will learn to use the ios_facts, ios_command, and ios_config Ansible modules (FQCN) to discover, verify, and configure IOS / IOS‑XE devices. You will gather device facts, run ad‑hoc show commands, and push configuration (hostname and a loopback) using Ansible against devices in the lab topology. In production, these tasks are common when onboarding devices, verifying baseline state, and applying standard configuration templates across many routers/switches.
Quick Recap
We continue with the fabric topology introduced in Lesson 1. No new physical devices are added in this lesson — we will operate against the same hosts and IP addresses.
ASCII Topology (simplified, with exact IPs from the reference):
+-----------------+
| Spine 1 |
| S1 |
| 10.15.1.11 |
+--------+--------+
|
|
+--------+--------+
| Spine 2 |
| S2 |
| 10.15.1.12 |
+--------+--------+
|
+----------------------+------+----------------+
| | |
+--+--+ +----+----+ +---+----+
| Leaf | | Leaf | | Leaf |
| L1 | | L2 | | L3 |
|10.15.1.13| |10.15.1.14| |10.15.1.15|
+-------+ +--------+ +--------+
Device table
| Role | Hostname (logical) | Management IP |
|---|---|---|
| Spine | S1 | 10.15.1.11 |
| Spine | S2 | 10.15.1.12 |
| Leaf | L1 | 10.15.1.13 |
| Leaf | L2 | 10.15.1.14 |
| Leaf | L3 | 10.15.1.15 |
Tip: Throughout this lesson we use the exact IPs listed above. For domain names use lab.nhprep.com. Passwords in examples use Lab@123.
Key Concepts (theory + practical)
- Ansible FQCN and network_cli — Always use the Fully Qualified Collection Name (FQCN) for modules (for example
cisco.ios.ios_facts). Ansible talks to IOS devices via thenetwork_cliconnection. In production this ensures consistent module resolution and predictable behavior across control nodes. - ios_facts gathers read‑only state —
ios_factsqueries a device for structured data (platform, version, interfaces, serial numbers). This is how automation confirms device identity before making changes, avoiding accidental changes to the wrong platform. - ios_command runs arbitrary show commands — When a module for a particular function doesn't exist (or you need a precise output),
ios_commandsends show commands and returns the raw output. It's equivalent to typingshow versionorshow ip interface briefat the CLI. - ios_config pushes intended config idempotently —
ios_configsends configuration commands. Using state merge or explicit config blocks allows automation to apply only necessary changes. In production, this is used to enforce baseline configurations and reduce human error. - Verification is critical — After applying config, always verify with both Ansible facts/commands and device
showoutputs. Automation can (and should) check result code and re‑read state to ensure convergence.
Step-by-step configuration
Step 1: Create Ansible inventory and group variables
What we are doing: Build an inventory that declares the devices we will manage and the connection parameters. This is the foundation for all subsequent Ansible tasks — without a correct inventory, Ansible cannot reach the devices.
all:
vars:
ansible_connection: ansible.netcommon.network_cli
ansible_user: "nxos_username"
ansible_password: "Lab@123"
ansible_network_os: cisco.ios.ios
children:
spines:
hosts:
10.15.1.11:
10.15.1.12:
leafs:
hosts:
10.15.1.13:
10.15.1.14:
10.15.1.15:
What just happened: This YAML tells Ansible to use network_cli to reach all devices (connects via SSH and uses the network CLI transport). ansible_user and ansible_password define the credentials; ansible_network_os indicates the module collection to use. Grouping into spines and leafs allows targeted playbooks.
Real-world note: Inventory grouping is how you can apply different baseline roles to spines vs leafs across hundreds of devices.
Verify:
# From the control node, list inventory hosts
ansible-inventory --list -i inventory.yml
# Expected output (trimmed for clarity but showing hosts):
{
"_meta": {
"hostvars": {
"10.15.1.11": {},
"10.15.1.12": {},
"10.15.1.13": {},
"10.15.1.14": {},
"10.15.1.15": {}
}
},
"all": {
"children": [
"spines",
"leafs"
],
"vars": {
"ansible_connection": "ansible.netcommon.network_cli",
"ansible_user": "nxos_username",
"ansible_password": "Lab@123",
"ansible_network_os": "cisco.ios.ios"
}
},
"spines": {
"hosts": [
"10.15.1.11",
"10.15.1.12"
]
},
"leafs": {
"hosts": [
"10.15.1.13",
"10.15.1.14",
"10.15.1.15"
]
}
}
Step 2: Gather facts from devices with ios_facts
What we are doing: Use cisco.ios.ios_facts to collect device facts (platform, version, uptime, interfaces). Gathering facts before making changes lets automation confirm the device type and current state.
- name: Gather IOS facts from all devices
hosts: all
gather_facts: no
tasks:
- name: Collect IOS facts
cisco.ios.ios_facts:
register: ios_facts_result
What just happened: Ansible connected to each host and ran the module which issues the appropriate show commands under the hood (for example show version, show inventory, show ip interface brief). The returned data is stored in ios_facts_result — a structured dictionary you can reference in later tasks to make decisions.
Real-world note: In large deployments you might gate configuration changes by facts (for example, only apply a template if the OS version meets a minimum).
Verify:
# Run a playbook to gather facts
ansible-playbook -i inventory.yml gather_facts.yml
# Expected snippet of output for one device (full JSON-like fact data):
TASK [Collect IOS facts] *******************************************************************
ok: [10.15.1.13] => {
"ansible_facts": {
"ansible_net_version": "16.9.4",
"ansible_net_hostname": "L1",
"ansible_net_model": "ISR4451",
"ansible_net_interfaces": {
"GigabitEthernet0/0": {},
"Loopback0": {}
}
},
"changed": false
}
Step 3: Run ad-hoc show commands with ios_command
What we are doing: Use cisco.ios.ios_command to run specific show commands, for example show version and show ip interface brief. This is useful when you need parsed output not available via facts, or to collect a specific verification snapshot.
- name: Run show commands on leafs
hosts: leafs
gather_facts: no
tasks:
- name: Run show version and show ip interface brief
cisco.ios.ios_command:
commands:
- show version
- show ip interface brief
register: show_output
What just happened: Ansible sent the two show commands to each leaf device and returned the raw output as text in show_output. Under the hood, the module handles prompts and pagination so automation gets deterministic output.
Real-world note: Use
ios_commandwhen you need exact CLI output (for auditing) or to capture information not included in facts.
Verify:
# Execute the playbook
ansible-playbook -i inventory.yml show_commands.yml
# Expected output snippet for one device:
TASK [Run show version and show ip interface brief] ***************************************
ok: [10.15.1.13] => {
"stdout": [
[
"Cisco IOS XE Software, Version 16.09.04",
"Technical Support: http://www.cisco.com/techsupport",
"ROM: Bootstrap program is C1100"
],
[
"Interface IP-Address OK? Method Status Protocol",
"GigabitEthernet0/0 10.15.1.13 YES manual up up",
"Loopback0 unassigned YES manual administratively down down"
]
],
"changed": false
}
Step 4: Configure hostname and a Loopback with ios_config
What we are doing: Use cisco.ios.ios_config to set a standardized hostname and create a Loopback0 with a /32 address. This demonstrates idempotent configuration changes — Ansible will only push changes if they are not already present.
- name: Configure hostname and Loopback0
hosts: leafs
gather_facts: no
tasks:
- name: Set hostname to match inventory IP (example)
cisco.ios.ios_config:
lines:
- hostname L-{{ inventory_hostname | replace('.','-') }}
- name: Create Loopback0 with unique address per device
cisco.ios.ios_config:
lines:
- interface Loopback0
- ip address 10.15.99.{{ inventory_hostname.split('.')[-1] }} 255.255.255.255
What just happened: The first ios_config block entered global configuration mode and set the device hostname. The second block created Loopback0 and assigned a /32 using the last octet of the management IP to guarantee uniqueness. ios_config sends the commands via configuration mode and can detect whether the commands resulted in changes.
Real-world note: Using variables (like the inventory hostname) to generate unique addresses is a common pattern for predictable device addressing during provisioning.
Verify:
# Run the playbook to apply config
ansible-playbook -i inventory.yml configure_loopbacks.yml
# Expected verification using device show commands:
# On device 10.15.1.13
show running-config | include hostname
hostname L-10-15-1-13
show ip interface brief | include Loopback0
Loopback0 10.15.99.13 YES manual up up
Step 5: Re-gather facts and validate configuration
What we are doing: Re-run ios_facts and ios_command to confirm the hostname and loopback are present, ensuring idempotency and correctness.
- name: Validate config changes
hosts: leafs
gather_facts: no
tasks:
- name: Re-gather facts
cisco.ios.ios_facts:
register: facts_after
- name: Check loopback via show ip interface brief
cisco.ios.ios_command:
commands:
- show ip interface brief | include Loopback0
register: loopback_check
What just happened: This playbook re-queries the devices; facts_after should reflect the new hostname and interface list, while loopback_check contains the show output confirming the loopback state. If automation finds discrepancies, you can have conditional tasks trigger remediation.
Real-world note: Always validate after changes — automation that doesn't validate risks drifting state across the fleet.
Verify:
# Expected snippets after validation
TASK [Re-gather facts] *********************************************************************
ok: [10.15.1.13] => {
"ansible_facts": {
"ansible_net_hostname": "L-10-15-1-13",
"ansible_net_interfaces": {
"Loopback0": {...}
}
},
"changed": false
}
TASK [Check loopback via show ip interface brief] *****************************************
ok: [10.15.1.13] => {
"stdout": [
[
"Loopback0 10.15.99.13 YES manual up up"
]
],
"changed": false
}
Verification Checklist
- Check 1: Inventory resolves all five devices — run
ansible-inventory --list -i inventory.ymland confirm 10.15.1.11–10.15.1.15 appear. - Check 2: Facts gathered — run the facts playbook and confirm
ansible_net_hostnameandansible_net_versionare present for each device. - Check 3: Configuration applied — on each leaf (10.15.1.13, .14, .15) verify
show running-config | include hostnameshows the configured hostname andshow ip interface brief | include Loopback0shows the /32 address.
Common Mistakes
| Symptom | Cause | Fix |
|---|---|---|
| Ansible cannot connect to devices | Wrong ansible_connection or incorrect credentials | Ensure ansible_connection: ansible.netcommon.network_cli and ansible_password: "Lab@123" are correct; verify SSH reachability to the management IPs |
| Module not found / wrong FQCN | Using non‑qualified module names or wrong collection | Use FQCN like cisco.ios.ios_config; install the collection if needed |
| Loopback not appearing after playbook | Template variable produced invalid IP or command failed | Check task lines: formatting and verify the computed IP (e.g., last octet extraction) — rerun with -vvv to inspect task details |
| Changes re-applied on every run (not idempotent) | Commands pushed in a way Ansible cannot detect state (e.g., using raw commands) | Use ios_config with proper lines or use state: merged patterns to ensure idempotency |
Key Takeaways
- Always use the module FQCN (for example
cisco.ios.ios_facts,cisco.ios.ios_command,cisco.ios.ios_config) andnetwork_clifor consistent, predictable automation. - Use
ios_factsbefore making changes to validate device identity and OS version — this prevents misconfigurations across different platforms. - Prefer
ios_configfor configuration to achieve idempotent changes; useios_commandwhen you need specific show output. - Verify every change programmatically: re-gather facts and run show commands to confirm success. In production, this verification step is essential for safe, automated operations.
Warning: In multi‑vendor environments, verify the
ansible_network_osand target collection before running config playbooks — applying IOS commands to a NX‑OS device (or vice versa) will cause failures.