NetAuto Academy

Ansible for Network Engineers

Capstone: A Complete Ansible-Based Configuration Deployment

Intermediate18 min read

What you'll learn

  • How inventory, variables, the template module, loops, and check mode combine into one deployment
  • How this solves the exact same problem as Configuration & Template Automation's capstone
  • Why Ansible's per-host recap is coarser than the 5-state model built by hand in Reliable Automation
  • That Ansible is an orchestration layer over everything already learned, not a replacement for it
🎓 Teach This Lesson

Listen to a guided explanation of this lesson.

Spoken narration isn't supported in this browser.

The whole curriculum, one more time

  1. Phase 2.6 — Python automationhand-written inventory, connection, validation, report
  2. Phase 2.7 — Netmikoa real SSH connection replaces the simulation
  3. Phase 2.8 — Reliable Automationretry, dry-run, pre/post-check, structured results — all hand-written
  4. Phase 2.9 — Jinja2 templatesstructured data + a template replace a hardcoded config string
  5. Phase 3.0 — Ansible orchestrates itthe same inventory, the same template, now run declaratively

This capstone solves the exact same problem Configuration & Template Automation's capstone did: deploy required VLANs across the SW1–SW5 fleet, safely. Same devices, same required VLANs, same template file. What's different is what orchestrates it.

Quick checkConcept check
What problem does this capstone actually solve?

Inventory and variables

inventory.yml
switches:
hosts:
  SW1:
    ansible_host: 10.10.10.1
  SW2:
    ansible_host: 10.10.10.2
  SW3:
    ansible_host: 10.10.10.3
  SW4:
    ansible_host: 10.10.10.4
  SW5:
    ansible_host: 10.10.10.5
vars:
  ansible_network_os: ios
  ansible_connection: network_cli

Unchanged from Lesson 3 — the same 5-device fleet, translated from devices.csv.

group_vars/switches.yml
vlans_by_host:
SW1:
  - { id: 10, name: USERS }
  - { id: 20, name: VOICE }
SW2:
  - { id: 10, name: USERS }
SW3:
  - { id: 10, name: USERS }
  - { id: 30, name: MGMT }
SW4:
  - { id: 10, name: USERS }
SW5:
  - { id: 10, name: USERS }

The exact REQUIRED_VLANS data from Configuration & Template Automation's capstone, unchanged — SW3 needs two VLANs, the rest need one each. Only the file format changed, from a Python dict to a YAML variables file.

The complete playbook

deploy_vlans.yml
- name: Deploy required VLAN configuration
hosts: switches
gather_facts: false

vars:
  required_vlans: "{{ vlans_by_host[inventory_hostname] }}"

tasks:
  - name: Render this device's VLAN configuration
    ansible.builtin.template:
      src: templates/vlan_config.j2
      dest: "/tmp/{{ inventory_hostname }}_vlans.cfg"
    delegate_to: localhost

  - name: Apply required VLANs
    cisco.ios.ios_config:
      lines:
        - "vlan {{ item.id }}"
        - "name {{ item.name }}"
      parents: "vlan {{ item.id }}"
    loop: "{{ required_vlans }}"

inventory_hostname is Ansible's built-in variable for 'whichever host this task is currently running for' — it's what makes vlans_by_host[inventory_hostname] pick out each device's own VLANs automatically, once per host in the switches group. delegate_to: localhost means the template renders on your machine, not on the switch itself — switches don't have a filesystem for Jinja2 to write to the way a Linux control node does.

Quick checkIdentify the correct code
What does inventory_hostname resolve to when this playbook runs its tasks for SW3?
Quick checkPredict output
Following the same data as Configuration & Template Automation's capstone, which two devices in this playbook's run would each render more than one VLAN into their configuration file?

Preview before anything real happens

terminal — per Ansible's documented behavior, not independently executed in this authoring environment
$ ansible-playbook -i inventory.yml deploy_vlans.yml --check --diff

TASK [Apply required VLANs] **********************************************
--- before
+++ after
@@ -1,3 +1,5 @@
!
+vlan 20
+ name VOICE
changed: [SW2] => (item={'id': 20, 'name': 'VOICE'})
ok: [SW1] => (item={'id': 10, 'name': 'USERS'})
ok: [SW1] => (item={'id': 20, 'name': 'VOICE'})
...

PLAY RECAP ******************************************************************
SW1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0
SW2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0
SW3 : ok=1 changed=1 unreachable=0 failed=0 skipped=0
SW4 : ok=1 changed=0 unreachable=0 failed=0 skipped=0
SW5 : ok=1 changed=0 unreachable=0 failed=0 skipped=0

This describes Ansible's own documented check-mode/diff output for this exact data — SW1 already has both required VLANs (changed=0), SW2 and SW3 each need one new VLAN (changed=1), matching Configuration & Template Automation's own already-compliant / needs-change split for the same devices under DRY_RUN=True.

Compare this to Phase 2.9's own report, device by device

Configuration & Template Automation's template_deployment_report.json, under DRY_RUN=True, classified SW1 as already-compliant and SW2/SW3 as dry-run-preview. This playbook's --check recap shows the same underlying facts — SW1 changed=0, SW2 and SW3 changed=1 — expressed in Ansible's own vocabulary instead of that hand-built 5-state model.

Quick checkScenario
Comparing this lesson's --check recap to Phase 2.9's own JSON report for the same 5 devices, which devices needed a change in both?

What Ansible's recap doesn't tell you that Reliable Automation's report did

Reliable Automation's 5-state report
  1. already-compliant / dry-run-preview / changed / change-not-verified / unreachable / auth-failed
  2. Attempt counts and retry flags, per device
  3. You wrote every state's exact meaning
Ansible's PLAY RECAP
  1. ok / changed / unreachable / failed / skipped, per host
  2. No built-in attempt-count or retry detail
  3. The framework defines what each bucket means

A real trade-off, not a strict upgrade

Ansible's recap is coarser than the 5-state model Reliable Automation built by hand. You gain a framework that handles idempotency and check-mode for you — you give up the fine-grained, custom result detail (retry counts, a distinct change-not-verified state) that hand-written Python gave you complete control over. Neither is simply "better" — this is the real trade-off between imperative and declarative automation this module opened with in Lesson 1.

Applying it for real — Option A only

Only against a disposable lab device you fully control

Removing --check and running this playbook for real requires the cisco.ios collection installed and a real or virtual Cisco device reachable over SSH — never production, and never without reviewing the --check --diff output first. This was not executed against a real device in this authoring environment; it is not claimed as tested, verified, or confirmed.

Quick checkMyth check
Does completing this capstone require running deploy_vlans.yml for real against an actual Cisco device?

What this module actually built

Nothing here replaced Python, Netmiko, Reliable Automation's safety discipline, or Configuration & Template Automation's Jinja2 skill. This capstone reused the same fleet, the same required-VLANs data, and the exact same template file — and wrapped them in a framework that provides idempotency and check-mode as guarantees instead of hand-written functions. That's the whole point of Lesson 1's declarative-vs-imperative framing, now proven end to end.

Key takeaways

  • This capstone solves the exact same VLAN deployment problem as Configuration & Template Automation's capstone — same fleet, same required-VLANs data, same vlan_config.j2 file.
  • inventory_hostname + vlans_by_host[inventory_hostname] is how one playbook picks out each device's own data automatically, the same role a Python loop over devices played by hand.
  • This lesson's --check/--diff output describes Ansible's own documented behavior for this exact data; it was not independently executed against ansible-playbook in this authoring environment, and is never claimed as tested or verified.
  • Ansible's PLAY RECAP is genuinely coarser than the 5-state model Reliable Automation built by hand — a real trade-off between a framework's guarantees and hand-written custom control, not a strict upgrade.
  • Applying this for real (Option A) requires the cisco.ios collection and a real or virtual device, and was not performed in this authoring environment — never claimed as live-tested.