Ansible for Cisco IOS XE¶
Ansible runs tasks against an inventory of devices from plain YAML playbooks, with no agent on the device. For Cisco IOS and IOS XE the work is done by the cisco.ios collection. This article covers the inventory and connection variables, the main module families, what idempotency really means for network modules, how to keep credentials in Vault, and two complete playbooks: a configuration backup and a VLAN push.
Install¶
python3 -m venv .venv
source .venv/bin/activate
pip install ansible-core
ansible-galaxy collection install cisco.ios ansible.netcommon
Pin collection versions in a requirements.yml so every engineer and the pipeline run the same code.
Network modules run on the control node, not on the device. Ansible opens an SSH session from your machine and sends CLI commands, which is why the connection type below is different from the Linux default.
Inventory and connection variables¶
A small project layout:
net-ansible/
ansible.cfg
inventory/
hosts.yml
group_vars/
all/
vars.yml
vault.yml
switches.yml
playbooks/
backup.yml
vlans.yml
backups/
inventory/hosts.yml:
all:
children:
switches:
hosts:
sw-core-1:
ansible_host: 192.0.2.11
sw-access-1:
ansible_host: 192.0.2.21
sw-access-2:
ansible_host: 192.0.2.22
inventory/group_vars/switches.yml:
ansible_network_os: cisco.ios.ios
ansible_connection: ansible.netcommon.network_cli
ansible_become: true
ansible_become_method: enable
inventory/group_vars/all/vars.yml holds the non-secret values and references the vaulted ones:
ansible_user: automation
ansible_password: "{{ vault_ansible_password }}"
ansible_become_password: "{{ vault_enable_password }}"
ansible.cfg:
Warning
host_key_checking = False is convenient in a lab. In production, distribute SSH host keys through known_hosts instead, so a man-in-the-middle cannot impersonate a switch.
Test the connection with an ad-hoc command:
Module families¶
The collection has three groups of modules worth knowing.
Command and config modules
cisco.ios.ios_commandrunsshowcommands and returns the output. It can also wait for a condition withwait_for.cisco.ios.ios_configpushes configuration lines. It compares them against the running config and sends only what is missing.cisco.ios.ios_factsgathers hardware, software and interface facts into variables.
Resource modules
Resource modules manage one feature as structured data. They are the better choice for anything they cover. Examples are ios_vlans, ios_interfaces, ios_l2_interfaces, ios_l3_interfaces, ios_acls, ios_ospfv2, ios_bgp_global and ios_static_routes. They share a state parameter:
| State | What it does |
|---|---|
merged |
Add or update the given config, leave the rest |
replaced |
Make the listed resources match exactly, leave other resources alone |
overridden |
Make the whole feature on the device match the given data, remove everything else |
deleted |
Remove the listed config |
gathered |
Read the device and return it as structured data |
rendered |
Turn data into CLI without touching a device |
parsed |
Turn CLI text you supply into structured data |
gathered, rendered and parsed do not change the device, so they are safe for audits and for building a source of truth from what already exists.
Utility modules: ios_ping, ios_banner, ios_user, ios_logging_global, ios_ntp_global, and others. Run ansible-doc -l cisco.ios to list everything in your installed version.
Idempotency¶
An idempotent task changes the device only when the device differs from the desired state. Run it a hundred times and the result is the same, and runs after the first report changed=0. That makes playbooks safe to schedule and gives you drift detection for free.
Resource modules are idempotent by design because they compare structured data. ios_config is idempotent for most lines because it checks the running config, but there are traps:
- The lines must match how the device displays them. If you write
ip address 10.0.0.1 255.255.255.0and the device shows the same, fine. Abbreviations and different ordering can cause a task to reportchangedevery time. - Nested config needs
parents. Without it, the module cannot tell thatdescription xbelongs under a specific interface. - Commands that do not appear in the running config (for example
no shutdownon some platforms, or one-shot exec commands) cannot be compared, so they send every time. Do not useios_configfor exec-mode actions.
Use check mode with diff to preview a change without applying it:
Check mode support depends on the module. Resource modules and ios_config support it; ios_command does not make changes anyway.
Vault¶
Credentials stay out of Git in vault.yml, encrypted with ansible-vault.
Contents before encryption:
Run playbooks with the vault password:
For automation, point to a password file outside the repository, for example --vault-password-file ~/.vault_pass, or have your CI system supply it from its secret store. Use ansible-vault edit to change the file later and ansible-vault rekey to rotate the vault password. Commit the encrypted file; never commit the password file.
Playbook 1: back up configurations¶
playbooks/backup.yml:
---
- name: Back up IOS XE running configs
hosts: switches
gather_facts: false
tasks:
- name: Save running config to a timestamped file
cisco.ios.ios_config:
backup: true
backup_options:
dir_path: "{{ playbook_dir }}/../backups/{{ inventory_hostname }}"
filename: "{{ inventory_hostname }}-{{ lookup('pipe', 'date +%Y%m%d-%H%M') }}.cfg"
register: result
- name: Show where the backup went
ansible.builtin.debug:
msg: "Saved {{ result.backup_path }}"
ios_config with backup: true and no lines only downloads the config; it changes nothing on the device. The directory is created if it does not exist. Commit the backups/ directory to Git and you have a configuration history with diffs, which is the first step toward the workflow in Git and CI/CD for network configs. Running configs can contain password hashes, SNMP communities and keys, so review what your devices show in show running-config and scrub or restrict the repository before you share it.
Playbook 2: push VLANs¶
Put the desired VLANs in data, not in the playbook. inventory/group_vars/switches.yml gains:
playbooks/vlans.yml:
---
- name: Ensure standard VLANs exist
hosts: switches
gather_facts: false
tasks:
- name: Merge VLANs
cisco.ios.ios_vlans:
config: "{{ vlans }}"
state: merged
register: vlan_result
- name: Save config if something changed
cisco.ios.ios_config:
save_when: modified
when: vlan_result.changed
- name: Verify the VLANs are present
cisco.ios.ios_command:
commands:
- show vlan brief
register: show_vlan
- name: Fail if an expected VLAN is missing
ansible.builtin.assert:
that:
- item.name in show_vlan.stdout[0]
fail_msg: "VLAN {{ item.vlan_id }} not found"
loop: "{{ vlans }}"
Run it in three steps:
ansible-playbook playbooks/vlans.yml --check --diff --ask-vault-pass
ansible-playbook playbooks/vlans.yml --limit sw-access-1 --ask-vault-pass
ansible-playbook playbooks/vlans.yml --ask-vault-pass
The first previews the change. The second applies it to a single switch. The third runs against the whole group. The second run of any of them should show changed=0.
state: merged only adds. To make the switch hold exactly these VLANs and delete the rest, use state: overridden. That is a destructive choice: it will remove a VLAN somebody created by hand, so run it with --check --diff first and make sure the data covers every VLAN in use. The module is for VLANs in the VLAN database on switches that use it, so check your platform's behaviour in a lab before pointing it at production.
Tip
Use ios_vlans with state: gathered against a known-good switch and save the output. It gives you the correct data structure to start your own variable files from.
Operational notes¶
- Keep
forksinansible.cfgreasonable (the default is 5). Raise it to speed up large runs, but watch your AAA servers. - Use
serial: 5at play level to roll out to a few devices at a time, andmax_fail_percentageto stop a bad change early. - Tag tasks so you can run subsets, for example
--tags backup. - Use
ansible-lintin CI to catch style and syntax problems. - Ansible says what it changed; it does not know whether the change was wise. Always add validation tasks after the change, as the VLAN playbook does.
Ansible fits teams that want a shared, readable format for routine changes and audits. If you need loops, error handling and logic that is awkward in YAML, drop to Netmiko or NAPALM in Python, or write a custom module.
References¶
- Ansible documentation
- cisco.ios collection on Ansible Galaxy
- cisco.ios collection on GitHub
- Ansible network automation guide
- Ansible Vault guide
- CCNP study plan if you want the VLAN and trunk fundamentals behind the playbook
Unlock the full question banks and cram sheets
A free account opens every question bank, the timed practice exams, the printable cram sheets, all the simulations and the lab files. Without one you see a 5-question sample of each.
No card needed. Study tips by email are optional and off unless you tick the box. Your address is never sold or shared. Privacy policy ยท Terms
Signed in as . Your account
You're on the free account, so lessons after the first module, question banks, exams and cram sheets show a sample. See Track Pass and All-Access
Also from NetManager Systems: NetManager, network monitoring for multi-vendor networks. Try the NetManager, NetDiag, PCAP Analyzer and OpsDash demos: every new account gets a free 7-day read-only login to each.