Ansible — Automating a Fleet, Not One Device
25 min In ProgressLearning Objectives
- Explain the difference between connecting to one device and describing a fleet-wide end state
- Define idempotent and recognize it in running code
- Build a fleet runner that applies a rule across many simulated devices
Why Ansible — Thinking in Fleets
Paramiko and Netmiko connect to one device per script run. Ansible flips the approach entirely: instead of scripting steps, you describe the end state you want, and it figures out how to get every device in the inventory there. The other defining feature is idempotency — running the same playbook twice should never produce a different result the second time.
The Real Driver (Lab / Real-Device Code)
A real Ansible setup is two files (an inventory and a playbook) plus, if you want to trigger it from Python, a thin wrapper around ansible_runner.
# inventory.ini
[cisco_routers]
192.168.1.1 ansible_network_os=ios ansible_user=admin ansible_password=secret
# backup.yml
- name: Backup router configs
hosts: cisco_routers
gather_facts: no
tasks:
- name: Run show running-config
ios_command:
commands: show running-config
register: config_output
- name: Save to file
copy:
content: "{{ config_output.stdout[0] }}"
dest: "backups/{{ inventory_hostname }}.cfg"
# Driving it from Python:
import ansible_runner
class ansible_fleet_runner:
def __init__(self, playbook_path, inventory_path):
self.playbook_path = playbook_path
self.inventory_path = inventory_path
def run(self):
result = ansible_runner.run(
private_data_dir='.',
playbook=self.playbook_path,
inventory=self.inventory_path,
)
return result.status, result.rc
runner = ansible_fleet_runner('backup.yml', 'inventory.ini')
print(runner.run())
Practice Safely — Simulated Fleet Runner
No YAML engine here — just the concept that matters: one rule, applied to every device, with each device deciding for itself whether a change is even needed. Run this twice in your head and notice the second run should report no changes anywhere.
class simulated_fleet_device:
def __init__(self, name, running_config):
self.name = name
self.running_config = running_config
def apply_if_missing(self, required_line):
if required_line in self.running_config:
return f'{self.name}: already present -- {required_line}'
self.running_config.append(required_line)
return f'{self.name}: ADDED -- {required_line}'
class fleet_runner:
"""Stands in for an Ansible playbook run: the same task, applied
across every device in the inventory, idempotently."""
def __init__(self, devices):
self.devices = devices
def run_playbook(self, required_line):
return [device.apply_if_missing(required_line) for device in self.devices]
fleet = [
simulated_fleet_device('edge-router-1', ['ip ssh version 2']),
simulated_fleet_device('edge-router-2', ['ip ssh version 2', 'ntp server 10.0.0.1']),
simulated_fleet_device('core-switch-1', []),
]
runner = fleet_runner(fleet)
print('First run:')
for line in runner.run_playbook('ntp server 10.0.0.1'):
print(' ', line)
print()
print('Running the exact same playbook again -- nothing should change:')
for line in runner.run_playbook('ntp server 10.0.0.1'):
print(' ', line)
📝 Exercise: Run a Two-Rule Golden-Config Audit
Extend fleet_runner.run_playbook() to accept a list of golden rules instead of one line, apply every rule to every device, and print a summary: how many changes were made vs how many devices were already compliant.