Ansible Playbooks and Roles for Networking
Ansible Playbooks and Roles for Networking
Introduction
Ansible has become one of the most widely adopted automation tools in network engineering, and a major reason for that is its low barrier to entry. No programming knowledge is required to get started — only an understanding of data-structure manipulation. Ansible is idempotent, meaning you can run the same automation multiple times and it will only make changes when the desired state differs from the current state. This lesson focuses on the core building blocks you will work with every day: playbooks, roles, inventory files, and collections.
By the end of this lesson you will be able to:
- Set up a Python virtual environment and install Ansible with the correct collections.
- Write an Ansible inventory file that defines your target hosts and credentials.
- Build a playbook that references roles for organized, repeatable automation.
- Use Jinja2 variable substitution to keep your configurations dynamic.
- Execute a playbook against a network fabric and interpret the results.
Key Concepts
The Ansible Architecture
Ansible is built on top of Python and operates through several interconnected components. Understanding how these pieces fit together is critical before writing your first playbook.
| Component | Purpose |
|---|---|
| Ansible Core | The engine that processes playbooks and executes tasks |
| Playbooks | YAML files that declare the automation intent — what to do |
| Inventory | Data structures that define the target hosts — who to do it to |
| Roles | Reusable, self-contained units of tasks — how to organize what you do |
| Collections | Vendor-specific modules bundled independently from core Ansible |
| Modules | Individual units inside collections that perform specific tasks (e.g., create VLANs, bridge domains, VRFs) |
Collections and Modules
Collections were introduced in Ansible 2.9. They allow vendors to decouple their Ansible capabilities (modules) from the core Ansible release schedule. Collections use Ansible Galaxy as the delivery vehicle, and each collection is made up of multiple modules that perform specific tasks. For example, the ACI collection contains modules to create EPGs, bridge domains, VRFs, and many more objects. Collections are actively maintained with a regular cadence that increases the module count and capability over time.
Jinja2 Variables
Ansible uses Jinja2 to enable dynamic expressions and access to variables and facts. Variables are defined by double curly brackets inside quotes: "{{ variable_name }}". This mechanism allows you to write a single playbook or role that adapts its behavior based on the variables supplied at runtime — whether those come from inventory files, environment variables, or the command line.
How It Works
Step 1 — Setting Up a Python Virtual Environment
You should — better yet, must — use a virtual environment. A proper virtual environment allows you to install Ansible inside a contained area with a specific version of Python. This makes it possible to run different Python scripts that require different versions of Python and different libraries without conflicts.
PyENV is the recommended mechanism to control Python virtual environments. It allows control of the Python version to execute independently of the system version. You will also need pyenv-virtualenv installed alongside it.
The setup flow is four commands:
% pyenv install 3.10.13
% pyenv virtualenv 3.10.13 ansible
% mkdir my_ansible_dir
% pyenv local ansible
- Install a specific version of Python (3.10.13).
- Create a virtual environment named
ansibleusing that Python version. - Create a working directory for your Ansible projects.
- Tell PyENV which virtual environment to use in the current directory.
Step 2 — Installing Ansible and Collections
There are two installation approaches:
| Approach | Command | Behavior |
|---|---|---|
| Core only | pip install ansible-core | Installs only the core components. Collections must be installed manually. Smaller footprint, more control, and assures the latest collection version. |
| Full install | pip install ansible | Installs all collections with the Ansible package. Complete but consumes more disk space, and might not install the latest version of every collection. |
After installing Ansible core, you install the collections you need. A collection can be installed in any location using the -p flag:
% ansible-galaxy collection install cisco.aci cisco.mso
You can explore the modules inside an installed collection from the command line:
% ansible-doc -l | grep cisco.aci
cisco.aci.aci_aaa_ssh_auth
cisco.aci.aci_aaa_user_certificate
To view detailed documentation for a specific module:
% ansible-doc cisco.aci.aci_aaa_user
This outputs the module description, all available options, and whether each parameter is mandatory.
Step 3 — Building an Inventory File
The Ansible inventory allows you to build data structures that correlate host-specific variables. It enables grouping and variable inheritance to organize your target devices. Two formats are common — INI and YAML — but best practice is to use YAML because it is less confusing.
Here is an inventory file that defines a fabric group with credentials and a target host:
---
east_fabric:
vars:
username: '{{ lookup("env", "APIC_USERNAME") }}'
password: '{{ lookup("env", "APIC_PASSWORD") }}'
hosts:
'{{ lookup("env", "APIC_IP") }}'
Best Practice: Always use environment variables for credentials. Never add credentials inside a Git repository.
Step 4 — Writing a Playbook with Roles
A playbook is the top-level YAML file that ties everything together. It declares which hosts to target, sets connection parameters, and references the roles to execute. Here is a playbook structured for network fabric automation:
---
- hosts: east_fabric
gather_facts: false
connection: local
any_errors_fatal: true
ignore_errors: false
module_defaults:
group/cisco.aci.aci:
hostname: "{{ inventory_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
validate_certs: no
use_ssl: yes
roles:
- roles/ap-vlans
- roles/ap-domains
- roles/ap-aep
Key parameters explained:
- gather_facts: false — For network fabric automation you do not need Ansible to connect to the device to collect host data, since Ansible interacts via the REST interface.
- connection: local — The computer executing the automation starts the connection locally to the fabric. There is no SSH session to the device itself.
- any_errors_fatal: true — Controls whether faults cause the entire play to stop or continue.
- module_defaults — Passes common parameters to all modules in the specified group. This avoids placing repetitive parameters like credentials in every single task.
- validate_certs: no and use_ssl: yes — Used for self-signed certificate-based HTTPS connections to the fabric.
Important: In Ansible, order matters. You cannot create a physical domain that points to a VLAN pool without first creating that pool. List your roles in the correct dependency order.
Step 5 — Writing Tasks Inside Roles
Roles contain a tasks/main.yaml file where the actual automation work is defined. Here is a role that creates VLAN pools by looping over a variable list:
---
# tasks file for ap-vlans
- name: Create VLAN Pools
cisco.aci.aci_vlan_pool:
pool: "{{ item.vlan_pool_name }}"
pool_allocation_mode: "{{ item.vlan_pool_mode }}"
description: "{{ item.vlan_pool_description }}"
state: present
delegate_to: localhost
loop: "{{ vlan_pools }}"
when: vlan_pools is defined
The variable data lives in a separate host_vars file, keeping task logic and data cleanly separated:
---
vlan_pools:
- vlan_pool_name: "eng_vlan_pool"
vlan_pool_description: "(ANS)Eng VLAN Pool"
vlan_pool_mode: "static"
- vlan_pool_name: "mkt_vlan_pool"
vlan_pool_description: "(ANS)Mkt VLAN Pool"
vlan_pool_mode: "static"
- vlan_pool_name: "hr_vlan_pool"
vlan_pool_description: "(ANS)HR VLAN Pool"
vlan_pool_mode: "static"
- vlan_pool_name: "sales_vlan_pool"
vlan_pool_description: "(ANS)Sales VLAN Pool"
vlan_pool_mode: "static"
Configuration Example
Executing a Playbook
With the inventory, playbook, roles, and host variables in place, you execute the playbook with a single command:
% ansible-playbook -i inventory/east_fabric playbooks/east-fabric/access-policies.yaml
The -i flag specifies the inventory file, and the second argument is the path to the playbook. Ansible reads the inventory to determine the target hosts, loads the playbook to determine the roles, and executes each role's tasks in order.
Understanding Variable Precedence
Ansible evaluates variables from many sources, and each source has a defined precedence level. From highest to lowest priority:
| Priority | Variable Source |
|---|---|
| Highest | Extra vars via CLI (-e "user=my_user") |
| Include params | |
| Role and include_role params | |
| set_facts / registered vars | |
| include_vars | |
| Task vars | |
| Block vars | |
Role vars (role/vars/main.yml) | |
| Play vars_files | |
| Play vars_prompt | |
| Play vars | |
| Host facts / cached set_facts | |
| Playbook host_vars | |
| Inventory host_vars | |
| Playbook group_vars | |
| Inventory group_vars | |
| Lowest | Role defaults (role/defaults/main.yml) |
Note: Command-line extra vars (
-e) always win. Role defaults are always the lowest priority, making them ideal for safe fallback values.
The Complete File Layout
When all the pieces come together, your project directory looks like this:
| File | Purpose |
|---|---|
playbooks/east-fabric/access-policies.yaml | The playbook declaring hosts, defaults, and roles |
inventory/east_fabric.yaml | The inventory with host groups and credentials |
roles/ap-vlans/tasks/main.yaml | The role containing VLAN pool creation tasks |
host_vars/east_fabric/ap-vlan-pools.yaml | The variable data for VLAN pools |
Real-World Application
The playbook-and-roles pattern shown in this lesson was used in production to migrate three independent network fabrics from legacy infrastructure to a modern fabric architecture. Each fabric was independent from the others, and the migration procedures — originally manual — were converted to Ansible playbooks and roles.
This approach scales naturally. When you need to automate a new fabric, you reuse the same roles and simply provide new inventory and host variable files. The separation of logic (roles) from data (host_vars) means that network engineers who are not comfortable writing automation code can still contribute by editing YAML variable files.
Key design considerations for production deployments:
- Environment variables for credentials — Never store passwords in files that are committed to version control.
- Role ordering — Always sequence roles so that dependencies are created before the objects that reference them.
- Module defaults — Use the
module_defaultsblock to avoid repeating connection parameters in every task. - Idempotency — Run your playbooks confidently knowing that Ansible will only make changes when the current state differs from the desired state.
Summary
- Ansible requires no programming knowledge — only data-structure manipulation. A Python virtual environment using PyENV is the recommended way to manage your installation.
- Collections decouple vendor modules from the Ansible core release schedule, giving you control over versions and a smaller installation footprint.
- Playbooks declare the intent (what and who), while roles organize the tasks (how). Keeping them separate enables reuse across multiple fabrics and projects.
- Jinja2 variable substitution and the variable precedence hierarchy give you fine-grained control over configuration data without duplicating logic.
- Execution is a single command —
ansible-playbook -i <inventory> <playbook>— that ties inventory, playbook, roles, and variables together into a repeatable automation workflow.
In the next lesson, we will explore how to extend these patterns with more advanced module usage and error handling strategies to build resilient network automation pipelines.