Ansible for network automation: agentless/idempotent architecture, inventory, playbooks/roles/handlers, variables & precedence, Jinja2 templates, network connection plugins (network_cli/netconf/httpapi), resource modules, and Vault.
Ansible is the default answer to "how do you push config to a fleet without writing bespoke scripts." It's agentless (SSH / network API, nothing to install on the device), declarative (YAML describing desired state), and idempotent (re-running converges, it doesn't re-do). For network automation it drives CLI, NETCONF, and HTTP-API devices the same way.
The one-liner: Ansible reads an inventory, runs tasks (each calling a module) from a playbook against those hosts, over a connection plugin — and reports changed vs ok so re-runs are safe.
| Piece | Role |
|---|---|
| Control node | Where Ansible runs (needs Python). No controller agent on targets. |
| Managed nodes | Servers (SSH + Python) or network devices (CLI/NETCONF/API — no Python needed). |
| Inventory | The hosts and groups, plus their variables. Static (INI/YAML) or dynamic (a script/plugin). |
| Modules | The unit of work (ios_config, ansible.builtin.copy, uri). Idempotent; report changed/ok/failed. |
| Plugins | Connection (network_cli, netconf, httpapi, ssh), filter, lookup, callback, inventory. |
| Collections | Packaged modules/roles/plugins from Galaxy (cisco.ios, arista.eos, community.general). |
roles/
with a standard layout). The way you keep playbooks DRY.notify'd by a changed task (e.g.
"save config" / "restart service").host_vars/, group_vars/, play/role vars,
set_fact, and gathered facts (ansible_facts). Network facts come from
gather_facts / *_facts modules..j2) renders config from variables — the
heart of config generation (template: module).loop, when (conditionals), register (capture
output), block/rescue/always (error handling), tags, serial (batching).--check (dry run) and --diff show what
would change without touching the device.Network devices differ from servers: usually no Python on-box, so you pick a network connection plugin and set
ansible_network_os.
| Connection | Use |
|---|---|
network_cli | SSH into the CLI (ios_*, eos_*, nxos_*, cli_command). |
netconf | NETCONF/YANG (netconf_config, junipernetworks.junos). |
httpapi | REST/eAPI (Arista eAPI, Cisco NX-API, F5). |
ios_vlans, ios_l3_interfaces) model config as
structured data with state: merged/replaced/overridden/deleted — true idempotent,
declarative network config (vs raw ios_config lines).*_facts gathers structured state; ios_config's
backup: yes snapshots running-config.ansible-vault encrypt).When the same variable is defined in several places, the most specific / latest wins. Simplified low→high:
| Priority | Source |
|---|---|
| lowest | role defaults/ |
| ↓ | inventory group_vars/all → specific group → host_vars |
| ↓ | play vars → role vars (vars/) → block/task vars |
| highest | set_fact / registered vars, then -e extra-vars (always win) |
Rule of thumb: defaults/ is the weakest (meant to be overridden), extra-vars
(-e) always win, and host_vars beat group_vars. More drills in
Python Automation.
# ad-hoc: one module, no playbook
ansible ios --connection network_cli -m cisco.ios.ios_command -a "commands='show version'"
# playbook, dry-run first, then for real
ansible-playbook site.yml -i inventory.yml --check --diff
ansible-playbook site.yml -i inventory.yml --limit edge --tags vlans
ansible-vault encrypt group_vars/all/secrets.yml # protect secrets
--check --diff)group_vars, host_vars, and -e sit?ios_config (raw lines) and a resource module (ios_vlans, structured + state)?network_cli/netconf/httpapi + ansible_network_os)