TTechclick ⚡ XP 0% All lessons
Ansible · Basics · Network Automation 101Interactive · L1 / L2 / L3

Ansible for Network Automation: — From Zero to Your First Playbook

You have 500 switches to touch and one pair of hands. Doing it by SSH means drift, typos and 2 a.m. callbacks. Ansible logs in over SSH like you would, but reads a checklist instead — agentless, declarative, and safe to re-run. This lesson builds your first working playbook against a Cisco router.

📅 2026-06-11 · ⏱ 12 min · 3 live demos · 4 infographics · 🏷 10-Q assessment + AI Tutor inline

⚡ Quick Answer

Ansible network automation for L1/L2 engineers and RHCE EX294: agentless/declarative/idempotent, the inventory + cisco.ios collection, network_cli connection, and your first ios_config + ios_facts playbook with --check.

🎯 By the end you will be able to

Read as:

Pick where you want to start

1

Why Ansible

Agentless, declarative, idempotent — the three words that matter.

2

The pieces

Control node, inventory, modules, collections, plays.

3

First playbook

Gather facts, push one line, run with --check.

4

Toy to real

group_vars, idempotency, ad-hoc, and where to grow.

🧠 Warm-up — 3 questions, no score

Just notice which ones make you pause. We answer all three inside the lesson.

1. You need to configure 200 Cisco switches the same way. With Ansible, what do you install ON each switch first?

Answered in Why Ansible.

2. For a Cisco IOS router, which connection does Ansible use instead of the normal SSH module?

Answered in First playbook.

3. You run the SAME playbook twice in a row against a router that is already configured. What does run #2 report?

Answered in The pieces.

Most engineers think…

Most engineers first hear "Ansible for networks" and picture installing some software on every router, then writing a giant script that runs commands top-to-bottom like a bash file.

Wrong on both counts — and it will cost you in interviews. Ansible is agentless (nothing installed on the device — it logs in over SSH/API), declarative (you describe the desired state, not the keystrokes), and idempotent (re-running a playbook that already matches makes zero changes). For routers and switches it does not even use the normal SSH module — it loads a network_cli connection that knows how to drive a Cisco/Arista/Juniper CLI. The mental model is a checklist of desired states, not a script of commands.

① Why Ansible for networks — drift, typos and 2 a.m. callbacks

Meet Rahul, an L1 engineer at Infosys looking after 500 branch switches. The standard rollout — say, a new login banner and an NTP server on every box — means he opens PuTTY, SSHes in, pastes four lines, saves, logs out, and repeats. Five hundred times. By switch 347 he fat-fingers an IP, by switch 480 he is copy-pasting yesterday's banner, and a week later no two configs are identical. That slow divergence is called configuration drift, and it is the quiet disease of every hand-run network.

There is also no record. When something breaks at 2 a.m., nobody can say who changed what on which box. Rahul is not lazy or careless — he is human, and humans do not scale to 500 identical tasks. This is exactly the gap Ansible was built to fill.

👉 So far: by-hand config means drift, typos and no audit trail. Next: the three words — agentless, declarative, idempotent — that make Ansible different from a shell script.

Three properties define Ansible, and you must be able to say each one in an interview. Agentless: you install no agent on the device — Ansible logs in over SSH (or an API) the same way you do, runs its work, and disconnects. Nothing to install, patch or break on 500 switches. Declarative: you describe the desired state ("this interface should have this description"), not the keystrokes to get there. Idempotent: run the same playbook ten times and it makes a change only the first time — after that it sees the state already matches and does nothing.

Figure 1 — 500 devices by hand vs one Ansible playbook
Configuring 500 devices by hand drifts and errors; Ansible runs one declarative checklist against all of them, safely re-run A two-column comparison for the same fleet of network devices. Left in red is the manual way: an engineer SSHes into each box one by one, copy-pastes commands, makes typos, and the running-configs drift apart over time with no single source of truth. Right in green is the Ansible way: one control node reads an inventory and a playbook of desired state, connects over SSH using the network_cli connection, and pushes the same state to every device; re-running changes nothing because it is idempotent. Red marks the error-prone manual path, green marks the automated path, amber marks the playbook decision point. 500 devices by hand vs one Ansible playbook By hand — SSH into each box ✗ log in, paste, log out — 500 times ✗ one typo on box 347 → silent drift ✗ no record of WHO changed WHAT ✗ re-doing a change = redo it all by hand sw-01 sw-02 …sw-500 each config slowly drifts from the others 2 a.m. debugging, no rollback Ansible — one playbook, all devices control nodeinventory + playbook playbook = desiredstate (declarative) SSH over network_cli — no agent on the box sw-01 sw-02 …sw-500 re-run = changed=0 (idempotent, safe) same state everywhere, in version control minutes, repeatable, reviewable error-prone / driftcontrolled / inspectedpolicy / decisionkey insightapplied / converged
Read both columns for the same fleet. Left (red) = SSH into each box, drift and typos, no rollback. Right (green) = one control node, one declarative playbook over SSH, re-runnable with changed=0 and kept in version control.

One more thing that trips up beginners: for routers and switches, Ansible does not use its normal SSH/shell module (the one it uses for Linux servers). Network gear has a CLI, not a shell, so Ansible loads a special network_cli connection. You point it at the right OS with ansible_network_os, and it knows how to handle the Cisco prompt, paging, and enable mode. We will set those variables in the next section.

The three words, one tap each

Tap each card — these are the answers to "what is Ansible?" that a hiring manager wants to hear.

🪶
Agentless
tap to flip

No software on the device — Ansible logs in over SSH/API and leaves. So: nothing extra to install or patch on 500 boxes.

🧾
Declarative
tap to flip

You declare the end state, not the keystrokes. So: the playbook reads like a checklist, and the tool works out the diff.

♻️
Idempotent
tap to flip

Re-running a matching playbook changes nothing. So: it is safe to run on a schedule and to prove a box is compliant.

🔌
network_cli
tap to flip

For routers/switches Ansible uses a CLI connection, not the Linux SSH module. So: it handles the prompt + enable mode for you.

Daily-life analogy — the society gate-pass register

Hand-config is like every flat owner phoning the guard with their own instructions — nobody agrees, and there is no record. Ansible is the society gate-pass register: one written list of who is allowed in (the desired state), the guard checks each visitor against it, and if a name is already on the list he does nothing new. Re-reading the same register tomorrow changes nothing — that is idempotency. And the guard (Ansible) carries the list to every gate; he does not install a copy at each flat (agentless).

Quick check · Q1 of 10

Sneha at TCS says: "We run our compliance playbook every night. Won't that re-apply the banner and NTP config 365 times a year and risk breaking something?" What is the correct reassurance?

Correct: b. Idempotency is the whole point: a task that finds the desired state already present makes no change and reports changed=0. So a nightly run is safe and even useful as a drift check. There is no agent (it is agentless), and you do not need a special flag to make tasks idempotent — well-written modules like ios_config are idempotent by design.

Pause & Predict

Predict: Ansible is "agentless" and just uses SSH. Name ONE thing that gets easier because there is no agent, and ONE new thing you now depend on instead. Type your guess.

Answer: Easier: nothing to install, version or patch on the 500 devices — onboarding a new switch is just adding it to the inventory, and a vendor agent CVE can never hit you. New dependency: solid SSH reachability and credentials to every device, because SSH (and, for enable mode, the enable password) is now the single path in. If SSH is blocked or the host key is unknown, Ansible cannot reach the box at all — which is why the first gotcha everyone hits is host-key checking.

② The pieces — control node, inventory, modules, collections, plays

Ansible has a small number of moving parts. Learn the names once and the rest of the series clicks. The control node is the only box with Ansible on it — your laptop or a jump host. Everything else is a managed device with nothing installed.

The inventory lists your devices and sorts them into groups[routers], [switches], [mumbai]. Groups matter because you attach shared settings to a group once via group_vars, instead of repeating them per host. A task calls a module; a group of tasks aimed at a set of hosts is a play; one or more plays in a YAML file is a playbook.

Modules do not all ship in the box anymore. They live in collections you install from Ansible Galaxy. For Cisco IOS you install cisco.ios; for Arista, arista.eos; for Juniper, junipernetworks.junos; for VyOS, vyos.vyos. The Cisco collection depends on the shared ansible.netcommon collection, so installing cisco.ios pulls it in.

Figure 2 — Ansible's pieces in one map
Ansible's pieces in one map: a control node reads inventory and modules from collections, then fans plays out to managed devices over network_cli An architecture map of Ansible for networks. On the left is the control node, the only machine with Ansible installed. It holds the inventory file listing hosts and groups, the playbook of plays and tasks, and collections installed from Ansible Galaxy such as cisco.ios, arista.eos, junipernetworks.junos and vyos.vyos which supply the modules. Connection variables ansible_connection=network_cli, ansible_network_os and become turn each task into a CLI session. The control node fans out over SSH to managed devices grouped as routers and switches; nothing is installed on those devices. Blue marks the trusted control side, amber marks the connection decision, green marks the managed devices that end up in the desired state. The pieces — one control node drives the whole fleet Control node the only box with Ansible installed inventoryhosts + [routers]/[switches] groups playbookplays → tasks (YAML, desired state) collections (from Galaxy)cisco.ios · arista.eos · junipernetworks.junosvyos.vyos — they ship the modules connection varsansible_connection=network_cliansible_network_os=cisco.ios.iosbecome: yes (enable) Managed devicesnothing installed here — agentless [routers] rtr-mum-01 rtr-pun-01 [switches] sw-blr-01 sw-blr-02 end state = exactly what theplaybook declared SSH (network_cli) — no agent, no extra port error-prone / driftcontrolled / inspectedpolicy / decisionkey insightapplied / converged
Look for the one-way arrows: only the control node has Ansible. It reads inventory + collections, then fans plays out over SSH (network_cli) to devices that have nothing installed on them.
👉 So far: control node, inventory + groups, modules from collections, plays + tasks. Next: the connection variables that turn a generic task into a Cisco CLI session — and the YAML rule that breaks everyone's first playbook.

Now the network-specific part. By default Ansible would try to SSH and run a shell — useless on a router. So in your inventory or group_vars you set three things. ansible_connection: ansible.netcommon.network_cli loads the CLI connection. ansible_network_os: cisco.ios.ios tells it the platform. And to get into enable mode, you add become: yes with ansible_become_method: enable and an ansible_become_password (the enable secret).

inventory.ini + group_vars/routers.yml — where the connection lives
# inventory.ini
[routers]
rtr-mum-01 ansible_host=10.10.1.1
rtr-pun-01 ansible_host=10.10.2.1

# group_vars/routers.yml
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.ios.ios
ansible_user: netauto
ansible_become: yes
ansible_become_method: enable
ansible_become_password: "{{ vault_enable_secret }}"
Expected output
# group_vars apply to every host in [routers],
# so you set the connection ONCE, not per device.
# The enable secret is pulled from an Ansible Vault
# variable — never hard-coded in the file.
Common mistake — "the playbook ran but Ansible just sat there and timed out"

Symptom: you wrote a clean playbook, but the run hangs and ends in a timeout or returns garbage. Cause is almost always a wrong or missing ansible_network_os, or forgetting ansible_connection: ansible.netcommon.network_cli — so Ansible tries to open a Linux shell on a device that only has a Cisco CLI. Fix: set both in group_vars and confirm the platform string exactly (cisco.ios.ios, not ios or cisco_ios). A second classic: the very first connection prompts to accept the SSH host key and the run stalls — set host_key_checking = False in ansible.cfg for a lab.

A word on YAML, because it causes more first-day pain than any module. YAML uses spaces, never tabs, and indentation defines structure — two spaces in is "inside" the line above. A list item starts with - (dash-space). A key: value needs the space after the colon. Mis-indent one line and you get a cryptic "could not find expected ':'" error. When in doubt, run ansible-playbook --syntax-check site.yml before anything else.

Quick check · Q2 of 10

Aditya at Wipro writes a playbook for a Cisco router but leaves the connection at the default. The run hangs and times out. Which single change is the fix?

Correct: c. A Cisco device has a CLI, not a Linux shell, so the default connection times out. Setting network_cli plus the correct ansible_network_os makes Ansible drive the IOS CLI properly. gather_facts does not fix the connection; Ansible is agentless so you never install an agent; and the playbook language (YAML) is unrelated to the connection timeout.

Pause & Predict

Predict: you put ansible_connection and ansible_network_os in group_vars/routers.yml instead of repeating them on every host line in the inventory. Why is that the better habit? Type your guess.

Answer: Because every host in the [routers] group inherits those variables automatically, so you set the connection details exactly once. Add a 50th router to the group and it just works — no copy-paste of connection vars, no risk of one host having a typo'd platform string. group_vars is the single source of truth for "what every router in this group has in common", which is also why secrets like the enable password live there (encrypted with Vault), not scattered across the inventory.

③ Your first playbook — gather facts, push one line, run with --check

Time to build something real. A first playbook should do two safe things: read state and write one line. We will use ios_facts to gather facts, then ios_config to set an interface description. Both come from the cisco.ios collection.

site.yml — your first network playbook
---
- name: First IOS playbook
  hosts: routers
  gather_facts: false
  tasks:
    - name: Collect device facts
      cisco.ios.ios_facts:

    - name: Show the IOS version we found
      ansible.builtin.debug:
        msg: "Running IOS {{ ansible_net_version }} on {{ ansible_net_model }}"

    - name: Set the WAN interface description
      cisco.ios.ios_config:
        parents: interface GigabitEthernet0/1
        lines:
          - description WAN-uplink-to-Pune
Expected output
PLAY [First IOS playbook] ******************************
TASK [Collect device facts] ***************************
ok: [rtr-mum-01]
TASK [Set the WAN interface description] *************
changed: [rtr-mum-01]
PLAY RECAP ********************************************
rtr-mum-01 : ok=3  changed=1  unreachable=0  failed=0

Notice gather_facts: false at the play level — the normal Linux fact-gathering does not work on a router, so we disable it and use the network-specific ios_facts task instead. The facts land in variables like ansible_net_version and ansible_net_model, which you can print or reuse. In ios_config, parents is the section header (interface GigabitEthernet0/1) and lines are the commands inside it.

🖥️ This is the run you will actually watch — a terminal on the control node executing ansible-playbook site.yml --check --diff. (Recreated for clarity — your terminal output matches this.)
netauto@control:~/playbooks
1
Command
ansible-playbook -i inventory.ini site.yml --check --diff
2
TASK [Collect device facts]
ok: [rtr-mum-01]
3
TASK [Set WAN description]
changed: [rtr-mum-01] (would change, --check)
diff
+ description WAN-uplink-to-Pune
4
PLAY RECAP
ok=3 changed=1 unreachable=0 failed=0
▶ run
👉 So far: a 3-task playbook that reads facts and sets one description. Next: the run flags — --check, --diff, --limit and -v — that keep you safe and help you debug.

You never run a new playbook straight at production. The flags are your seatbelt. --check is a dry run — it reports what would change and touches nothing. --diff shows the precise lines. --limit scopes the run to one box for a first test. And -v (up to -vvvv) turns up verbosity when something misbehaves.

Figure 3 — Desired vs current — why --check is your seatbelt
Run --check first: Ansible compares desired state to the live running-config and shows the diff without touching the device A flow showing how one ios_config task is processed. Step 1 the playbook declares desired state, for example an interface description line. Step 2 ios_facts and the module read the current running-config from the router. Step 3 Ansible compares desired versus current. If they already match, the result is changed equals false and nothing is sent. If they differ, in check mode Ansible only prints the diff and reports what would change; in a real run it pushes only the missing lines and reports changed equals true. Re-running afterwards goes back to changed equals false. Amber marks the compare decision, green marks the safe applied path, lime marks the key insight that check mode is risk-free. Desired vs current — and why --check is your seatbelt 1· desired stateplaybook: description WAN-uplink 2· read currentmodule reads running-config 3· comparedesired == current ? already matches →changed=false (no-op) same differs + --checkprints the diff only,device untouched (dry run) differs differs + real runpush ONLY the missing lineschanged=true run it AGAIN → changed=falseproof it is idempotent error-prone / driftcontrolled / inspectedpolicy / decisionkey insightapplied / converged
Follow the compare diamond: if desired already equals current → changed=false (no-op). If they differ, --check only prints the diff (device untouched), a real run pushes ONLY the missing lines → changed=true, and re-running drops back to changed=false.

▶ Watch one ios_config task decide what to do

You run the playbook against rtr-mum-01. Follow how ios_config compares your desired line to the live config and chooses to change or skip. Press Play for the healthy path, then Break it to see the failure.

① Connectcontrol node opens network_cli SSH to rtr-mum-01 (10.10.1.1), enters enable mode
② Read currentios_config reads the running-config under interface GigabitEthernet0/1
③ Comparedesired description WAN-uplink-to-Pune vs current — not present
④ Applypushes ONLY that one line → changed=1; re-run later = changed=0
Press Play to step through the healthy path. Then press Break it.
ansible-playbook — dry run first, then for real, then prove idempotency
# 1) dry run on ONE box, show the diff — changes nothing
ansible-playbook -i inventory.ini site.yml --limit rtr-mum-01 --check --diff

# 2) apply for real
ansible-playbook -i inventory.ini site.yml --limit rtr-mum-01

# 3) run again — proof it is idempotent
ansible-playbook -i inventory.ini site.yml --limit rtr-mum-01
Expected output
# run 2 -> changed: [rtr-mum-01]   ok=3 changed=1 failed=0
# run 3 -> ok: [rtr-mum-01]        ok=3 changed=0 failed=0
# changed=0 the second time = the box already matches
# desired state. That is idempotency, proven.

Priya at ICICI faces this

Priya, an L1 analyst, runs her first playbook and it stalls, then fails with a host-key error / prompt to accept the SSH key on the new lab routers.

Likely cause

On the very first SSH to a device, the control node has never seen its host key, and Ansible (like ssh) refuses or prompts. The playbook is fine — the connection trust is not set up.

Diagnosis

She separates "did the task fail?" from "did the connection even open?". The error is a connection/host-key error, before any module runs, so it is an ansible.cfg / SSH trust issue, not a YAML or module bug.

control node → ansible.cfg → [defaults] host_key_checking (or env ANSIBLE_HOST_KEY_CHECKING=False)
Fix

For a lab, set host_key_checking = False in ansible.cfg [defaults]. For production, pre-seed the known_hosts file with the real device keys instead of disabling the check.

Verify

Re-run ansible-playbook site.yml --limit rtr-mum-01 --check -> the connection opens, ios_facts returns ok, and the recap shows unreachable=0.

Quick check · Q3 of 10

Karthik at HCL is about to run a new config playbook against 80 production routers for the first time. Which single command should he run FIRST?

Correct: b. The safe first move is a dry run scoped to a single device: --check makes no changes, --diff shows exactly what would change, and --limit keeps the blast radius to one box. Running straight at all 80, cranking forks, or just adding verbosity all still APPLY changes to production untested. Verify on one box in check mode, read the diff, then widen.

Pause & Predict

Predict: you run the playbook once and get changed=1. You run the exact same playbook again with no edits. What does the PLAY RECAP show the second time, and what does that prove? Type your guess.

Answer: The second run shows changed=0 (everything ok, nothing changed). It proves the task is idempotent: ios_config read the running-config, saw the description line was already present, and made no change. This is the single most useful behaviour in network automation — it means you can safely re-run the playbook any time to enforce/verify state, and a changed=0 result is also a quick compliance check that the box still matches your intended config.

④ From toy to real — variables, idempotency, ad-hoc & where to grow

Hard-coding one description in one playbook is a demo. Real automation drives data. You move per-device values into group_vars and host_vars, and your tasks reference variables instead of literals. Now the same playbook configures 500 different switches, each with its own NTP server or VLAN, just by changing the data.

group_vars + a templated config line (data drives the play)
# group_vars/routers.yml (added)
ntp_server: 10.20.0.10
banner_text: "Authorised access only - Infosys NetOps"

# task in site.yml
- name: Enforce NTP + banner from group_vars
  cisco.ios.ios_config:
    lines:
      - ntp server {{ ntp_server }}
      - banner motd ^{{ banner_text }}^
Expected output
changed: [rtr-mum-01]   (first run, lines added)
ok:      [rtr-mum-01]   (second run, already present)
# change ntp_server in group_vars -> next run
# re-converges every router to the new value.

The reason re-running is safe bears repeating because it is the heart of the job: idempotency means "changed: false" on the second run. ios_config reads the running-config, compares your lines, and only sends what is missing. So a playbook is not a one-shot script — it is a statement of how the box should look that you can apply as often as you like.

👉 So far: variables + group_vars make one playbook serve 500 boxes, and idempotency makes re-runs safe. Next: ad-hoc commands for quick one-offs, the real gotchas, and the road to roles/templates/vault.

Not everything needs a playbook. For a quick one-off — "show me the version on every router right now" — use an ad-hoc command. It is the ansible command (not ansible-playbook) with -m for the module and -a for arguments.

ad-hoc — one-off checks without writing a playbook
# gather facts from every router, one line
ansible routers -i inventory.ini -m cisco.ios.ios_facts

# run a show command on one box
ansible rtr-mum-01 -i inventory.ini \
  -m cisco.ios.ios_command -a "commands='show ip int brief'"
Expected output
rtr-mum-01 | SUCCESS => {
  "ansible_facts": { "ansible_net_version": "17.9.4a",
    "ansible_net_model": "ISR4331", ... },
  "changed": false }
Figure 4 — Ansible network basics — the cheat-sheet
Ansible network basics on one card — connection vars, the inventory, the first playbook skeleton and the commands you will type A nine-tile cheat sheet for Ansible network automation basics. Tiles cover the three properties agentless declarative idempotent, the install commands, the connection variables, the inventory layout with groups and group_vars, the first playbook skeleton with ios_facts and ios_config, the run commands including check and limit and verbose, the meaning of changed status, the common gotchas, and where to grow next with roles templates and vault. Ansible network basics — your one-glance card The 3 wordsagentless · nothing on the devicedeclarative · describe the end stateidempotent · safe to re-run Installpip install ansibleansible-galaxy collection install cisco.iosalso pulls ansible.netcommon Connection varsansible_connection= ansible.netcommon.network_cliansible_network_os=cisco.ios.iosbecome: yes (=enable) Inventory[routers]rtr-mum-01 ansible_host=10.10.1.1group_vars/routers.yml holdsthe shared connection vars First playbookhosts: routersgather_facts: falsetasks: ios_facts → ios_confignetwork OS facts, then one line Run commandsansible-playbook site.yml --check --diff (dry run + diff)--limit rtr-mum-01 -vvvscope to one box · verbose debug Read the resultchanged=1 → it did somethingchanged=0 → already correctunreachable / failed → fix first Top gotchashost key prompt → host_key_checkingenable pw → ansible_become_passwordwrong network_os → timeout/garbage Grow nextroles · reusable bundles of taskstemplates · Jinja2 per-device configvault · encrypt the passwords
Your one-card map of this lesson — the three words, install, connection vars, inventory, the first-playbook skeleton, run flags, how to read changed=, the gotchas, and where to grow. Keep it open in week one and before any interview.
Common mistake — "ios_config works but my changes vanish after a reload"

Symptom: the playbook reports changed=1, the config is live, but after the device reloads it is gone. Cause: ios_config changes the running-config; by default it does not save to startup-config (save_when defaults to never). Fix: add save_when: modified (or changed) to the task so Ansible copies running-config to startup-config when it makes a change. Two more first-week gotchas: a wrong ansible_network_os causes hangs/garbage, and forgetting ansible_become_password (the enable secret) means config tasks fail with an authorization error even though login worked.

Where do you grow next? Three steps. Roles package your tasks into reusable bundles. Templates (Jinja2) build a whole per-device config from variables — one template, every switch. And Ansible Vault encrypts your enable passwords and tokens so the repo is safe to commit. Those are the next lessons.

For certification, this lesson maps onto the RHCE EX294 and the broader Ansible Network track. EX294 is a hands-on exam: you write real playbooks, use inventories and group_vars, run with the right modules, and rely on idempotency — exactly what you just did. The same building blocks (inventory, connection vars, modules, check mode) are what every "Ansible Network Automation" blueprint tests, and what a netauto interviewer probes in the first ten minutes.

Next: Ansible Playbooks for Cisco IOS at scale
Prove you own the basics

Cold, in 30 seconds: define agentless / declarative / idempotent; name the connection vars a Cisco router needs (ansible_connection=network_cli, ansible_network_os=cisco.ios.ios, become for enable); and say what --check --diff does and why changed=0 on a second run is good news. If you can do that without notes, you are ready for the Cisco IOS at-scale lesson and for the EX294 Ansible basics.

Quick check · Q4 of 10

An interviewer asks Meera: "Give me the single biggest reason a team trusts running the same Ansible playbook against production on a nightly schedule." Best answer?

Correct: d. Idempotency is the trust mechanism: a nightly run only changes a box if it has drifted from desired state, otherwise it reports changed=0 and is a no-op (and a free compliance check). There is no agent (agentless); YAML is just the format, not a safety guarantee; and --forks only affects parallelism/speed, not whether re-runs are safe.

🤖 Ask the AI Tutor

Tap any question — instant, scoped to this lesson. No login, no waiting.

Pre-curated from Ansible docs + community Q&A, scoped to this lesson. For a live prod issue, paste your export into chat.techclick.in.

📝 Wrap-up assessment — six more

You've answered 4 inline. Six left. 70% (7 of 10) marks the lesson complete on your profile. Tap Submit all answers at the end.

Q5 · Remember

Which connection plugin does Ansible use to configure a Cisco IOS router, instead of its default Linux SSH/shell connection?

Correct: c. Network devices have a CLI, not a shell, so Ansible loads ansible.netcommon.network_cli and is told the platform via ansible_network_os=cisco.ios.ios. The default ssh/shell connection assumes a Linux shell and times out; Ansible is agentless so no agent is installed; and local runs tasks on the control node itself.
Q6 · Apply

You add a 51st router to the [routers] group in your inventory. You already have ansible_connection, ansible_network_os and the enable password in group_vars/routers.yml. What extra connection config does the new host need?

Correct: a. Variables in group_vars/.yml apply to every host in that group, so a new member of [routers] inherits the connection settings with zero extra config. Copying the vars per host defeats the purpose of group_vars; collections are installed once on the control node, not per host; and Ansible never installs an agent on the device.
Q7 · Apply

Before pushing a brand-new config playbook to 80 production routers, which command gives you a safe preview on a single device without changing anything?

Correct: b. --check is a dry run (no changes), --diff shows the exact lines that would change, and --limit scopes it to one box — the safe first preview. --forks only changes parallelism and still applies to all 80; -vvvv only adds verbosity while still applying; and the last line is malformed (ansible runs ad-hoc modules, not a playbook file).
Q8 · Analyze

Login succeeds and ios_facts returns ok, but the ios_config task fails with an authorization/enable error. The username and password are correct. Most likely root cause?

Correct: d. Reading facts works in user mode, but ios_config needs privileged EXEC (enable) to change config — so a missing/wrong enable secret fails only the config task while login and ios_facts still succeed. Bad YAML would fail at syntax/parse time; a missing collection would fail the ios_facts task too; and gather_facts being true would error or be skipped, not cause an enable-mode authorization failure.
Q9 · Analyze

You run the same playbook twice. Run 1: ok=3 changed=1. Run 2 (no edits): ok=3 changed=0. What does the changed=0 on run 2 tell you, and why is it useful?

Correct: a. changed=0 with ok means ios_config compared desired vs running-config, found them identical, and made no change — that is idempotency. It is useful because you can re-run the playbook any time to enforce or verify state. There is no failure (failed=0), no client-side cache skipping the device, and the connection clearly succeeded (ok=3, unreachable=0).
Q10 · Evaluate

Two ways to describe Ansible to a network hiring manager: (A) "it is a script tool that SSHes in and runs my commands faster"; (B) "it is agentless and declarative — you state the desired config and it idempotently pushes only the diff over a network_cli connection." Which is stronger and why?

Correct: b. B explains the architecture that produces the real benefits: agentless (nothing on the device), declarative (state, not keystrokes), idempotent (re-runs are safe), over a network_cli connection — which is exactly why it scales to 500 boxes and survives nightly re-runs. A reduces it to "a faster script", missing idempotency and the desired-state model that the job and the EX294 actually test.
Lesson complete — saved to your profile.
Almost! You need 70% (7 of 10) — re-read the path that tripped you up and tap "Try again".

🧠 In your own words

Type one line: In one line, why is it safe to run the same Ansible config playbook against a production router every single night? Then compare to the expert version.

Expert version: Because Ansible is idempotent: ios_config reads the running-config and only pushes lines that are missing, so a router already in the desired state reports changed=0 and is left untouched — the nightly run becomes a free drift/compliance check rather than a risk.

🗣 Teach a friend

Best way to lock it in — explain it in one line to a teammate. Tap to generate a paste-ready summary.

📖 Glossary

Ansible
Red Hat's agentless automation tool — configures servers and network devices from a central control node over SSH/API.
Agentless
No software is installed on the managed device; Ansible connects over SSH/API, does its work, and disconnects.
Declarative
You describe the desired end state, not the step-by-step commands; the module computes and applies the difference.
Idempotent
Running the same playbook again makes no change if the state already matches — the second run reports changed=0.
Control node
The single machine with Ansible installed, from which you run playbooks against everything else.
Inventory
A file listing managed devices, organised into groups like [routers] and [switches]; hosts can carry per-host variables.
group_vars / host_vars
YAML files of variables that apply to a whole group (group_vars/routers.yml) or a single host (host_vars/rtr-mum-01.yml).
Module
The code that does one unit of work, e.g. cisco.ios.ios_config (write config) or cisco.ios.ios_facts (read facts).
Collection
A packaged bundle of modules/plugins/roles for a platform, installed from Ansible Galaxy — e.g. cisco.ios, arista.eos.
network_cli
The ansible.netcommon connection that drives a vendor CLI over SSH (prompt, paging, enable mode) — used instead of the Linux SSH module.
ansible_network_os
Variable that tells network_cli which platform it is talking to, e.g. cisco.ios.ios, arista.eos.eos, junipernetworks.junos.junos.
become (enable)
Privilege escalation; with ansible_become_method=enable Ansible enters Cisco privileged EXEC (#) before making config changes.
--check / --diff
Run flags: --check is a no-change dry run; --diff prints the exact config lines that would be added or removed.
Ansible Vault
ansible-vault encrypts secrets (enable passwords, tokens) so they can live safely in a version-controlled repo.

📚 Sources

  1. Ansible Community Documentation — "Network Getting Started: Run Your First Command and Playbook" (ansible_connection=ansible.netcommon.network_cli, ansible_network_os, gather_facts:false, the ansible-playbook example and the PLAY RECAP ok=5 changed=1). docs.ansible.com/projects/ansible/latest/network/getting_started/first_playbook.html
  2. cisco.ios.ios_config module — Ansible Community Documentation (parameters lines/parents/match/replace/backup/save_when with defaults match=line, replace=line, save_when=never; interface description example; network_cli requirement). docs.ansible.com/projects/ansible/latest/collections/cisco/ios/ios_config_module.html
  3. cisco.ios.ios_facts module + cisco.ios collection index — Ansible Community Documentation (facts prefixed ansible_net_; ansible-galaxy collection install cisco.ios pulls ansible.netcommon; become with become_method=enable). docs.ansible.com/ansible/latest/collections/cisco/ios/ios_facts_module.html · docs.ansible.com/projects/ansible/latest/collections/cisco/ios/index.html
  4. Ansible Network Best Practices — Ansible Community Documentation (use resource modules where possible, ios_config for the rest; test with --check --diff to review the running-config diff before applying). docs.ansible.com/projects/ansible/latest/network/user_guide/network_best_practices_2.5.html
  5. cisco.ios collection Releases — GitHub ansible-collections/cisco.ios (2025-2026: ios_config gains a content parameter; minimum ansible.netcommon >=8.5.2; test matrix adds stable-2.20, drops stable-2.16 libssh; ios_ action-plugin renames). github.com/ansible-collections/cisco.ios/releases · galaxy.ansible.com/cisco/ios
  6. Red Hat EX294 — Red Hat Certified Engineer (RHCE) exam objectives (hands-on: install/configure a control node, build inventories, use variables and group_vars, write playbooks with modules, idempotency; dynamic inventory, Vault, handlers, loops, roles). redhat.com/en/services/training/ex294-red-hat-certified-engineer-rhce-exam-red-hat-enterprise-linux

What's next?

You can stand up one playbook against one router. Next we scale it: real interface/VLAN/ACL config across a whole fleet with resource modules, loops over group_vars, and handlers that only save when something actually changed.