Mental model
You manage 30 Cisco switches. You need to add VLAN 50 to every one of them. Two options:
- SSH into each manually — type the same 4 lines, 30 times, hope you don’t fat-finger anything.
- Run an Ansible playbook — describe the change once in YAML, push to all 30 in parallel, get a report.
Ansible is option 2. It’s agentless (you don’t install anything on the switches), declarative (you describe the end state, not the steps), and idempotent (running it twice doesn’t break anything — if VLAN 50 already exists, it just confirms and moves on).
For network engineers in 2026, Ansible is the most common starting point for automation. More approachable than writing custom Python; more powerful than scripting SSH commands.
The four core concepts
- Inventory — a file listing all your devices, grouped however you want (by region, by role, etc.)
- Playbook — a YAML file describing what you want done
- Module — pre-built code that knows how to talk to a specific platform (Cisco IOS, NX-OS, Junos, etc.)
- Task — one step in a playbook, calling one module with specific parameters
Inventory — list of devices
# inventory.yml
all:
children:
switches:
hosts:
sw1:
ansible_host: 10.0.0.10
sw2:
ansible_host: 10.0.0.11
sw3:
ansible_host: 10.0.0.12
vars:
ansible_network_os: ios
ansible_user: admin
ansible_password: cisco123
ansible_connection: network_cli
Anything you’d repeat (credentials, OS) goes in vars — applied to every host in the group.
Playbook — describe the change
# add-vlan-50.yml
- name: Add VLAN 50 to all switches
hosts: switches
gather_facts: no
tasks:
- name: Ensure VLAN 50 exists
cisco.ios.ios_vlans:
config:
- vlan_id: 50
name: GUEST
state: active
state: merged
Read aloud: “On every host in the ‘switches’ group, ensure VLAN 50 with name GUEST exists in ‘active’ state.”
Run with:
$ ansible-playbook -i inventory.yml add-vlan-50.yml
Ansible connects to all switches in parallel, checks if VLAN 50 already exists, adds it if not, reports back. Output looks like:
PLAY [Add VLAN 50 to all switches] ******
TASK [Ensure VLAN 50 exists] ************
changed: [sw1]
changed: [sw2]
ok: [sw3] ← already had it, no change made
PLAY RECAP *****
sw1 : ok=1 changed=1 unreachable=0 failed=0
sw2 : ok=1 changed=1 unreachable=0 failed=0
sw3 : ok=1 changed=0 unreachable=0 failed=0
Idempotency — the killer feature
Running the playbook a second time:
PLAY RECAP *****
sw1 : ok=1 changed=0
sw2 : ok=1 changed=0
sw3 : ok=1 changed=0
No changes made. The playbook checked, found the state already matched the desired state, and did nothing. This is what makes Ansible safe to re-run — and what makes “drift detection” trivial: run your playbook in --check mode and any changed line tells you the device drifted from intended config.
Common modules for Cisco gear
| Module | What it does |
|---|---|
cisco.ios.ios_config | Push raw config lines (universal but less idempotent) |
cisco.ios.ios_vlans | Manage VLANs declaratively |
cisco.ios.ios_interfaces | Interface basics (description, enabled, mtu) |
cisco.ios.ios_l3_interfaces | Layer-3 settings (IP addresses) |
cisco.ios.ios_static_routes | Static routes |
cisco.ios.ios_command | Run any show command, capture output |
cisco.ios.ios_facts | Gather device info (OS version, hostname, interfaces, etc.) |
Prefer the declarative resource modules (ios_vlans, ios_interfaces) over raw ios_config when possible — they’re more idempotent and produce cleaner diffs.
Common mistakes
-
Hardcoding credentials in the playbook. Use Ansible Vault (
ansible-vault encrypt) or environment variables. Never commitansible_password: secretvalueto Git. -
Forgetting
gather_facts: noon network playbooks. Default Ansible tries to run a Python module on the target to gather facts. Network devices can’t run Python. Skip facts unless you explicitly need them viaios_facts. -
Using
ios_configfor everything. It works but generates messy diffs and can lose idempotency. Use resource modules where they exist. -
Running playbooks without
--checkfirst.--check(also called dry-run) shows what would change without making changes. Always preview before applying to production. -
Not version-controlling playbooks. Playbooks are infrastructure-as-code. Store them in Git, review changes in PRs. The whole point of automation is removing one-off manual operations.
-
Misreading the OK count.
ok=5 changed=0means everything matched intended state.ok=5 changed=3means 3 things were modified.failed=1means stop and investigate.
Lab to try tonight
- Install Ansible on your laptop:
pip install ansible(or use the official package for your OS). Also:ansible-galaxy collection install cisco.ios. - Spin up two Cisco IOS devices in CML or grab a DevNet sandbox. Enable SSH + a local user.
- Write a minimal
inventory.ymllisting both devices. - Write a playbook that creates VLAN 100 with name TEST on both. Run with
--checkfirst, then for real. - Run the playbook a second time. Verify both hosts report
changed=0. - Modify the playbook to also configure an SVI for VLAN 100 with an IP. Re-run.
- Bonus: write a playbook that uses
ios_factsto gather IOS version from each device and writes a report to a file.
Cheat strip
| Concept | Plain English |
|---|---|
| Inventory | List of devices, grouped |
| Playbook | YAML file describing desired state |
| Module | Pre-built code for a specific task |
| Task | One step calling one module |
| Idempotent | Re-running doesn’t change unchanged things |
| —check | Dry-run mode — show changes without applying |
| ansible-vault | Encrypts sensitive values in playbooks |
changed | Task modified the device |
ok | Task succeeded (may or may not have changed) |
failed | Task hit an error — stop and investigate |