Lesson 1 of 5

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.

ComponentPurpose
Ansible CoreThe engine that processes playbooks and executes tasks
PlaybooksYAML files that declare the automation intent — what to do
InventoryData structures that define the target hosts — who to do it to
RolesReusable, self-contained units of tasks — how to organize what you do
CollectionsVendor-specific modules bundled independently from core Ansible
ModulesIndividual 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
  1. Install a specific version of Python (3.10.13).
  2. Create a virtual environment named ansible using that Python version.
  3. Create a working directory for your Ansible projects.
  4. Tell PyENV which virtual environment to use in the current directory.

Step 2 — Installing Ansible and Collections

There are two installation approaches:

ApproachCommandBehavior
Core onlypip install ansible-coreInstalls only the core components. Collections must be installed manually. Smaller footprint, more control, and assures the latest collection version.
Full installpip install ansibleInstalls 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:

PriorityVariable Source
HighestExtra 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
LowestRole 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:

FilePurpose
playbooks/east-fabric/access-policies.yamlThe playbook declaring hosts, defaults, and roles
inventory/east_fabric.yamlThe inventory with host groups and credentials
roles/ap-vlans/tasks/main.yamlThe role containing VLAN pool creation tasks
host_vars/east_fabric/ap-vlan-pools.yamlThe 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_defaults block 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 commandansible-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.