5. Playbooks
Here I will create a static inventory, (for now), go over read-only show commands, fact gathering, configuration pushes, and create a structured config backup playbook.
What I Will Be Completing In This Part
- Create a static YAML inventory file with
wan-r1andwan-r2 - Verify connenctivity from Ansible using an ad-hoc ping
- Write and run a read-only playbook using
ios_command - Gather structured device facts using
ios_facts - Push a configuration change using
ios_configand observe idempotency - Save the running configuration to startup
- Build a structured backup playbook that stores configs locally with timestamps
01 Static Inventory
I decided on YAML when creating the static inventory file. This file defines the hosts and groups that Ansible targets when running playbooks.
| |
Line 2:
all is the top-level implicit group that contains every host.
- Lines 4-5:
- Any host here inherits the variables from
inventory/group_vars/ios/vars.ymlandinventory/group_vars/ios/vault.ymlautomatically. - Lines 7, 9:
ansible_hosttells Ansible the actual IP address to connect to.
I then verified Ansible can pars the inventory file before running playbooks:
(.venv) $ ansible-inventory --list -yThis will print the full inventory as Ansible sees it.
01a Connectivity Test
I confirmed that Ansible can reach both devices using an ad-hoc command.
(.venv) $ ansible ios -m cisco.ios.ios_command -a "commands='show version | include uptime'"- ios:
- The target group.
- -m cisco.ios.ios_command:
- The module to run.
- -a “commands=”:
- The module arguments.
wan-r1 | SUCCESS => {
"changed": false,
"stdout": [
"wan-r1 uptime is 2 hours, 15 minutes"
],
"stdout_lines": [
[
"wan-r1 uptime is 2 hours, 15 minutes"
]
]
}
wan-r2 | SUCCESS => {
"changed": false,
"stdout": [
"wan-r2 uptime is 2 hours, 14 minutes"
],
"stdout_lines": [
[
"wan-r2 uptime is 2 hours, 14 minutes"
]
]
}02 Playbook
02a Show Commands
I wrote a read-only playbook that runs several show commands and displays the output.
| |
- Line 3:
- Targets every device in the
iosinventory group. - Line 4:
- Disables Ansible’s default fact-gathering step.
- Lines 9-12:
- These commands run sequentially on each device and the output is collected in a list.
- Line 13:
- Captures the module’s return data into a variable.
- Lines 15-17:
- Prints the registered variable to the Ansible output.
Then I ran the playbook:
(.venv) $ ansible-playbook playbooks/ios_show.ymlThe results will be formatted in YAML.
02b Gathering Facts
The ios_facts module collects structured data about devices. The data is returned as Ansible facts, which means it’s accessible as variables in subsequent tasks without needing to parse show command output manually.
| |
- Lines 9-10:
- This tells the module to collect every category of facts.
- Line 15:
- After
ios_factsruns it populates Ansible facts as variables prefixed withansible_net_. - Line 19:
- Is a dictionary containing every interface on the device with its operational state, IP addresses, MTU, speed, and more.
Then tested the playbook:
(.venv) $ ansible-playbook playbooks/ios_facts.yml03b Pushing Configuration
I used ios_config to push a simple change: adding a login banner and disabling DNS lookups.
| |
Then ran the playbook:
(.venv) $ ansible-playbook playbooks/ios_base_config.ymlThe 1st run should show changed=3 for each device. To check idempotency I ran the playbook again, which it should show changed=0.
03c Saving Configuration
Next, I wrote a playbook that saves the running configuration to startup.
| |
- Line 9:
- This triggers a
write memoryevery time this task runs.
I then tested the playbook:
(.venv) $ ansible-playbook playbooks/ios_save.yml03d Structured Backup
I then made a playbook that retrieves the full running configuration from each device and saves it to a local file on the control node, with a timestamp. This is the manual way of doing it, later on I’ll setup Oxidized which does it automatically.
| |
- Line 7:
backup_rootresolves to abackups/directory in the project root.- Line 8:
- This plugin runs a shell command on the control node and captures the output.
- Lines 11-17:
- Creates a per-device subdirectory under
backups/. - Lines 19-23:
- Retrieves the full running config.
- Lines 25-30:
- Writes the timestamped backup file.
- Lines 32-37:
- Writes a
_latest.cfgcopy that is overwritten on every run.
Then ran the playbook:
(.venv) $ ansible-playbook playbooks/ios_backup.yml04 Updating .gitignore
The backups/ directory contains device running configurations which may include sensitive information. I added it to the .gitignore so backup files are not committed to the repo.
backups/05 Commit and Push
Lastly, I committed the inventory file, all four playbooks, and updated the .gitignore using the feature branch workflow:
(.venv) $ git checkout -b feat/first-playbooks
(.venv) $ git add -A
(.venv) $ git statusmodified: .gitignore
new file: inventory/hosts.yml
new file: playbooks/ios_show.yml
new file: playbooks/ios_facts.yml
new file: playbooks/ios_base_config.yml
new file: playbooks/ios_save.yml
new file: playbooks/ios_backup.ymlI committed and pushed:
(.venv) $ git commit -m "feat: add static inventory and first IOS-XE playbooks"
(.venv) $ git push -u origin feat/first-playbooksI then went to Gitea and approved and merged.
After merging:
(.venv) $ git checkout main
(.venv) $ git pull origin mainI now have a working static inventory with 2 Cat9kv nodes in the ios group, 5 playbooks covering the core Ansible network automation.