Prerequisites: CCNA-level networking knowledge (OSPF, BGP, routing/switching)
Table of Contents
- Course Overview
- Ansible Philosophy & Network Automation Applications
- Installing Ansible
- Ansible Concepts & Terminology
- Building Basic Network Automation
- Configuring Network Devices with Ansible
- Creating & Using Ansible Roles
- Reference Tables
- Architecture Diagrams
1. Course Overview
This course covers the fundamentals of Ansible, a powerful IT infrastructure automation tool that not only automates repetitive tasks but can fundamentally change how you design, configure, and operate a network.
Learning Objectives:
- Understand the philosophy and history of Ansible
- Develop and execute automations: tasks, plays, playbooks, and roles
- Automate the day-to-day operational tasks of a network engineer
- Apply network configuration changes with Ansible
Course Use Case: The fictional company Globomantics, an industrial parts distributor suffering from configuration drift on its network devices.
2. Ansible Philosophy & Network Automation Applications
Regaining Control of the Data Center
Modern enterprise networks often suffer from configuration drift — a gradual divergence between the desired configuration and the actual configuration of devices. This occurs due to undocumented manual changes, human errors during late-night change windows, and the absence of a tool to enforce the desired configuration state.
What is Ansible?
Ansible is an open-source IT automation tool, initially developed by Michael DeHaan and acquired by Red Hat in 2015. It allows interaction with one or more devices connected to a network.
What Ansible can do:
- Retrieve specific data from devices
- Copy files to/from devices
- Install and configure software
- Apply network configuration changes idempotently
Key differentiators compared to Puppet, Chef, SaltStack:
- Agentless: no agent to install on managed devices
- Simple: based on SSH and YAML, easy to learn
- Batteries included: everything is included on installation
Typical Ansible workflow:
- Before the change window: dry run (
--check) to identify changes to be made - During the change window: actual execution to apply changes
- After: compliance verification
Network Automation vs Systems Administration
| Aspect | Systems Administration (Linux) | Network Administration |
|---|---|---|
| Configuration | Text file modification | Context-sensitive CLI command sequences |
| Primary transport | SSH | SSH, REST API, NETCONF/RESTCONF, JSON-RPC |
| Vendor-specific | No (relatively standardized) | Yes (Cisco, Juniper, Arista, etc.) |
| OS versions | Minor impact | Significant impact on commands |
| Data models | Config files | YANG + protocols (NETCONF, RESTCONF) |
Network-specific challenges:
- CLIs vary between vendors, network OS versions, and even between versions of the same OS
- How to reach a desired state depends on the transport used
- Some transports have limitations on what can be configured
Ansible Package History
Ansible uses semantic versioning since version 3.0.0 (February 2021):
- Major (e.g., 3 → 4): breaking changes
- Minor (e.g., 3.1 → 3.2): backward-compatible new features
- Patch (e.g., 3.1.1 → 3.1.2): backward-compatible bug fixes
Architectural evolution:
- Before v2.9: monorepo — everything in a single Git repository (batteries included)
- v2.10: separation into
ansible-base(core) + collections distributed via Ansible Galaxy - v3.0+: rename
ansible-base→ansible-core, semantic versioning adopted
Porting Guides & Changelogs
For each new major version of Ansible, the team publishes a porting guide explaining the changes needed for existing automation. Accessible at docs.ansible.com.
Best practice: Always review the porting guide before a major Ansible upgrade in production.
Declarative vs Imperative Programming
| Type | Description | Analogy | Example |
|---|---|---|---|
| Declarative | Describes what to achieve, not how | Ordering at a restaurant | Ansible playbooks (desired state) |
| Imperative | Describes how to achieve the goal | Cooking yourself | Python/Bash scripts (sequence of steps) |
Ansible combines both approaches:
- Declarative for resource state (resource modules)
- Imperative for control logic (
whenconditions,loopiterations)
3. Installing Ansible
Control Node Prerequisites
An Ansible control node must satisfy three requirements:
- Unix-like OS: Linux (Ubuntu, CentOS, Fedora, RHEL…) or BSD. Windows is not supported (WSL possible but unofficial).
- Up-to-date Python: Ansible is primarily written in Python. The minimum version depends on the installed Ansible version.
- Low latency to managed hosts: ideally in the same data center or geographic region.
Options for creating a control node:
- VM in a type-2 hypervisor (VirtualBox, VMware Workstation)
- VM in a type-1 hypervisor (ESXi, Hyper-V)
- VM in a network emulator (GNS3, EVE-NG, Cisco Modeling Labs)
- VM in a public cloud (AWS, GCP)
Installation via Package Manager (APT/YUM/DNF)
Ubuntu/Debian (APT):
# Update package list
apt -y update
# Install dependencies
apt -y install software-properties-common
# Add Ansible PPA
add-apt-repository --yes --update ppa:ansible/ansible
# Install Ansible
apt -y install ansible
CentOS 8 / Fedora 22+ / Rocky Linux 8 (DNF):
dnf -y install ansible
CentOS 7 / Fedora 21 (YUM):
yum -y install ansible
Note: Package manager installation may not provide the latest version of Ansible. For production environments requiring a specific version, prefer installation via
pip.
Installation via pip in a Virtual Environment
Recommended method — allows multiple Ansible versions on the same machine.
# Create the project folder
mkdir network-automation
cd network-automation
# Create a Python virtual environment
python3 -m virtualenv venv
# If "No module named virtualenv" error:
# Ubuntu/Debian : apt -y install python3-virtualenv
# DNF : dnf -y install python3-virtualenv
# YUM : python3 -m pip install virtualenv
# Activate the virtual environment
source venv/bin/activate
# Prompt changes to show (venv)
# Install Ansible in the virtual environment
pip install ansible
# Verify the installation
ansible --version
# Deactivate the virtual environment
deactivate
Installation from Source
To use fixes or features from the development branch not yet released:
# Activate the virtual environment
source venv/bin/activate
# Install from the devel branch on GitHub
pip install https://github.com/ansible/ansible/archive/devel.tar.gz
# Verify (a "development version" warning should appear)
ansible --version
Upgrading Between Major Versions
# Check installed version
ansible --version
pip freeze | grep ansible
# Uninstall the old version
pip uninstall ansible
# Install the new version
pip install ansible==4.0.0
# Verify the upgrade
ansible --version
pip freeze | grep ansible
Warning: Always review the porting guide for the new version before upgrading.
4. Ansible Concepts & Terminology
Ansible Node Types
graph TD
CN["🖥️ Control Node\n(Ansible installed)"]
MN1["📦 Managed Node / Host\n(Switch S1 - NX-OS)"]
MN2["📦 Managed Node / Host\n(Switch S2 - NX-OS)"]
MN3["🖧 Managed Node / Host\n(Router R1)"]
CN -->|SSH / API / NETCONF| MN1
CN -->|SSH / API / NETCONF| MN2
CN -->|SSH / API / NETCONF| MN3
style CN fill:#1f6feb,color:#fff
style MN1 fill:#238636,color:#fff
style MN2 fill:#238636,color:#fff
style MN3 fill:#238636,color:#fff
| Node | Role | Notes |
|---|---|---|
| Control Node | Machine where Ansible is installed and runs automation | Must be Unix-like; multiple allowed |
| Managed Node / Host | Target device of the automation | Network device, server, load balancer, etc. |
Key points:
- You can have multiple control nodes simultaneously (one per engineer, or in a CI/CD pipeline)
- A node can be both a control node and a host (e.g., deploying Docker containers locally)
Ansible Configuration Files
Ansible looks for its configuration file (ansible.cfg) in this priority order:
ANSIBLE_CONFIGenvironment variable./ansible.cfg(current directory) ← recommended for projects~/.ansible.cfg(user home directory)/etc/ansible/ansible.cfg(system configuration)
Important: Configuration values do not inherit from one file to another. Only the first file found is used.
Example ansible.cfg file:
[defaults]
# Disable cowsay (ASCII cow status messages)
nocows = 1
# Enable colors in output
force_color = 1
# Disable verbose mode by default
verbosity = 0
# Default inventory file
inventory = inventory.yaml
# Disable SSH host key checking (lab use only, not recommended for production)
host_key_checking = False
# Fork level (parallelism)
forks = 10
Introduction to YAML
YAML (YAML Ain’t Markup Language) is a data serialization language designed to be human-readable. Ansible uses it for inventories, playbooks, variables, etc.
Fundamental YAML structures:
---
# Start of a YAML document (three dashes)
# Dictionary (key: value)
hostname: S1
vendor: Cisco
platform: NX-OS
# List
interfaces:
- Ethernet1/1
- Ethernet1/2
- Loopback0
# Nested dictionary
bgp:
asn: 65535
router_id: 10.1.1.1
neighbors:
- ip: 10.2.2.2
remote_as: 65535
# Multi-line string (literal block |)
description: |
WAN interface to the datacenter
Bandwidth: 10 Gbps
# Booleans
enabled: true
shutdown: false
# Integers and floats
vlan_id: 100
bandwidth_gbps: 1.0
Recommended tools:
yamllint: YAML syntax checkeransible-lint: Ansible-specific linter for playbooks
Ansible Inventory Files
The inventory defines which hosts should be managed by Ansible and how they are grouped.
Inventory types:
- Static inventory: defined in a YAML or INI file
- Dynamic inventory: fed from an external source (IPAM, cloud provider)
Default location: /etc/ansible/hosts (but recommended: in the project)
Example inventory.yaml file:
---
all:
vars:
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.nxos.nxos
ansible_user: netadmin
ansible_password: "{{ vault_password }}" # Use ansible-vault in production
children:
switches:
children:
west:
hosts:
S1:
loopback: 10.1.1.1
east:
hosts:
S2:
loopback: 10.2.2.2
graph TD
ALL["Group: all"]
SW["Group: switches"]
W["Group: west"]
E["Group: east"]
S1["Host: S1\nloopback: 10.1.1.1"]
S2["Host: S2\nloopback: 10.2.2.2"]
ALL --> SW
SW --> W
SW --> E
W --> S1
E --> S2
style ALL fill:#6e40c9,color:#fff
style SW fill:#1f6feb,color:#fff
style W fill:#0969da,color:#fff
style E fill:#0969da,color:#fff
style S1 fill:#238636,color:#fff
style S2 fill:#238636,color:#fff
Inspect the inventory with ansible-inventory:
# Show variables for a specific host
ansible-inventory -i inventory.yaml --host S1
# Show the full inventory as JSON
ansible-inventory -i inventory.yaml --list
# Show the inventory graph
ansible-inventory -i inventory.yaml --graph
Host/Group Variables and Inheritance
Variables can be defined at multiple levels. Priority order (lowest to highest):
defaults (role) → group vars (all) → group vars (parent) → group vars (child) → host vars → extra vars (-e)
Variables in the inventory:
---
all:
vars:
ntp_server: ntp.global.corp.local
children:
west:
vars:
ntp_server: ntp.west.corp.local # Override for the west group
hosts:
S1:
loopback: 10.1.1.1
# S1 inherits ntp.west.corp.local
east:
vars:
ntp_server: ntp.east.corp.local # Override for the east group
hosts:
S2:
loopback: 10.2.2.2
# S2 inherits ntp.east.corp.local
group_vars and host_vars directories:
network-automation/
├── group_vars/
│ ├── all.yaml # Variables for all hosts
│ ├── switches.yaml # Variables for the switches group
│ ├── west.yaml # Variables for the west group
│ └── east.yaml # Variables for the east group
├── host_vars/
│ ├── s1.yaml # Variables specific to S1
│ └── s2.yaml # Variables specific to S2
└── inventory.yaml
Example group_vars/switches.yaml:
---
bgp_asn: 65535
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.nxos.nxos
Example host_vars/s1.yaml:
---
loopback: 10.1.1.1
bgp_router_id: 10.1.1.1
interfaces:
- name: Ethernet1/1
ip: 192.168.12.1/30
description: "Link to S2"
- name: Loopback0
ip: 10.1.1.1/32
description: "Loopback interface"
Ansible Facts
Facts are special variables that Ansible automatically collects from hosts before executing automation. They represent the current operational state of a device.
Typical facts on a Cisco Nexus:
ansible_facts['net_hostname']: configured hostnameansible_facts['net_version']: software versionansible_facts['net_interfaces']: interface informationansible_facts['net_filesystems']: available filesystems
Variables required for fact collection on NX-OS:
# In inventory.yaml or group_vars/all.yaml
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.nxos.nxos
ansible_user: netadmin
ansible_password: cisco123
Disabling fact collection to speed up execution:
---
- name: Configure BGP on switches
hosts: switches
gather_facts: false # Explicit disabling
tasks:
- name: Enable BGP feature
cisco.nxos.nxos_feature:
feature: bgp
state: enabled
Ansible Project Structure
network-automation/
├── ansible.cfg # Project Ansible configuration
├── site.yaml # Main playbook (entry point)
├── switches.yaml # Playbook for switches
├── inventory.yaml # Static inventory
├── group_vars/
│ ├── all.yaml
│ ├── switches.yaml
│ ├── west.yaml
│ └── east.yaml
├── host_vars/
│ ├── s1.yaml
│ └── s2.yaml
├── roles/
│ ├── network_devices/
│ ├── routers/
│ ├── ospf_routers/
│ └── bgp_speakers/
├── collections/
│ └── requirements.yaml # Collection dependencies
└── venv/ # Python virtual environment
Ansible Automation Components
graph TD
PB["📋 Playbook\n(YAML file)"]
P1["🎭 Play 1\n(hosts: switches)"]
P2["🎭 Play 2\n(hosts: routers)"]
T1["✅ Task 1\n(name + module)"]
T2["✅ Task 2\n(name + module)"]
T3["✅ Task 3\n(name + module)"]
M1["🔧 Module\ncisco.nxos.nxos_feature"]
M2["🔧 Module\ncisco.nxos.nxos_config"]
PB --> P1
PB --> P2
P1 --> T1
P1 --> T2
P2 --> T3
T1 --> M1
T2 --> M2
style PB fill:#6e40c9,color:#fff
style P1 fill:#1f6feb,color:#fff
style P2 fill:#1f6feb,color:#fff
style T1 fill:#0969da,color:#fff
style T2 fill:#0969da,color:#fff
style T3 fill:#0969da,color:#fff
style M1 fill:#238636,color:#fff
style M2 fill:#238636,color:#fff
| Component | Description | Analogy |
|---|---|---|
| Module | Reusable script performing a specific task | Tool in a toolbox |
| Task | Defines when and how a module is executed | Instruction for using the tool |
| Play | Ordered set of tasks targeting a group of hosts | Complete procedure |
| Playbook | YAML file containing one or more plays | Procedures manual |
Complete playbook example:
---
# Playbook: copy a file to Nexus switches via SCP
- name: Copy config.txt file to Nexus switches via SCP
hosts: switches
gather_facts: false
tasks:
- name: Enable SCP feature on switches
cisco.nxos.nxos_feature:
feature: scp-server
state: enabled
- name: Copy config.txt file to switches
cisco.nxos.nxos_file_copy:
local_file: config.txt
vrf: management
- name: Disable SCP feature on switches
cisco.nxos.nxos_feature:
feature: scp-server
state: disabled
Ansible Collections
A collection is a distribution format for Ansible content (modules, plugins, roles, playbooks). Collections are distributed via Ansible Galaxy.
FQCN (Fully Qualified Collection Name) naming:
<namespace>.<collection>.<module>
# Example:
cisco.nxos.nxos_config
│ │ └── Module
│ └──────── Collection
└────────────── Namespace
Managing collections with ansible-galaxy:
# List installed collections
ansible-galaxy collection list
# Check the version of a specific collection
ansible-galaxy collection list cisco.nxos
# Install the latest version
ansible-galaxy collection install cisco.nxos
# Install a specific version
ansible-galaxy collection install cisco.nxos:==2.4.0
# Force reinstall of an older version
ansible-galaxy collection install cisco.nxos:==2.4.0 --force
collections/requirements.yaml file (tracking dependencies):
---
collections:
- name: cisco.nxos
version: ">=2.4.0,<3.0.0"
- name: ansible.netcommon
version: ">=2.0.0"
- name: ansible.utils
version: ">=2.0.0"
# Install all required collections
ansible-galaxy collection install -r collections/requirements.yaml
5. Building Basic Network Automation
Verifying Connectivity to Network Devices
Common issues during first connection:
-
Connection plugin not found:
ansible.netcommon.network_cli connection plugin could not be found- Solution: verify
collections_scan_sys_path = trueinansible.cfg
- Solution: verify
-
Paramiko library missing:
pip install paramiko -
SSH host key not in
known_hosts:# Option 1: add manually ssh-keyscan -H <switch_ip> >> ~/.ssh/known_hosts # Option 2: disable verification (lab only!) # In ansible.cfg: host_key_checking = False
Test connectivity with the ansible.builtin.ping module:
# Test a specific host
ansible -i inventory.yaml -m ansible.builtin.ping S1
# Test a group
ansible -i inventory.yaml -m ansible.builtin.ping switches
# Test all hosts
ansible -i inventory.yaml -m ansible.builtin.ping all
Test with a network module:
ansible -i inventory.yaml -m cisco.nxos.nxos_command \
-a "commands='show version'" S1
Ansible Ad Hoc Commands
Ad hoc commands allow executing a single module against multiple hosts quickly. Ideal for one-time tasks.
General syntax:
ansible -i <inventory> -m <module> -a "<arguments>" <hosts>
Practical examples:
# Enable the SCP server on all switches
ansible -i inventory.yaml -m cisco.nxos.nxos_feature \
-a "feature=scp-server" all
# Copy a file to all switches
ansible -i inventory.yaml -m cisco.nxos.nxos_file_copy \
-a "local_file=config.txt vrf=management" all
# Retrieve the hostname of all switches
ansible -i inventory.yaml -m cisco.nxos.nxos_command \
-a "commands='show hostname'" switches
Ansible Playbooks
For repeatable and more complex tasks, write a playbook.
Execute a playbook:
ansible-playbook -i inventory.yaml playbook.yaml
Example: OSPF and BGP configuration playbook:
---
# playbook.yaml
- name: Configure OSPF on switches
hosts: switches
gather_facts: false
tasks:
- name: Enable OSPF feature
cisco.nxos.nxos_config:
lines:
- feature ospf
- name: Configure OSPF process
cisco.nxos.nxos_config:
lines:
- router ospf 1
- name: Configure BGP on switches
hosts: switches
gather_facts: false
tasks:
- name: Enable BGP feature
cisco.nxos.nxos_config:
lines:
- feature bgp
- name: Configure BGP process
cisco.nxos.nxos_config:
lines:
- router bgp 65535
Interpretation of Ansible output colors:
| Color | Meaning |
|---|---|
| 🟢 Green (ok) | Task executed, no change required |
| 🟡 Yellow (changed) | Task executed, a change was made |
| 🔴 Red (failed) | Task failed |
| 🔵 Cyan (skipped) | Task skipped (when condition not met) |
The —limit Parameter for Testing
Restricts playbook execution to a subset of targeted hosts without modifying the playbook.
# Target a single host
ansible-playbook -i inventory.yaml playbook.yaml --limit S1
# Target a group
ansible-playbook -i inventory.yaml playbook.yaml --limit west
# Target multiple hosts/groups (separator: :)
ansible-playbook -i inventory.yaml playbook.yaml --limit "west:east"
# Combine with --check for a targeted dry run
ansible-playbook -i inventory.yaml playbook.yaml --limit S1 --check
Important rule: The host/group specified in
--limitmust be a subset of the group targeted by the playbook.
Leveraging Facts on Network Devices
---
- name: Display and use Ansible facts
hosts: switches
gather_facts: true # Enable fact collection
tasks:
- name: Display all facts
ansible.builtin.debug:
var: ansible_facts
- name: Display available filesystems
ansible.builtin.debug:
var: ansible_facts['net_filesystems']
- name: Copy file only if bootflash is available
cisco.nxos.nxos_file_copy:
local_file: running_config.txt
file_system: bootflash:
when: "'bootflash:' in ansible_facts['net_filesystems']"
Archiving CLI Output to Disk
The delegation concept (delegate_to) allows executing an action on the control node rather than the target host.
---
- name: Archive hostname from network devices
hosts: switches
gather_facts: false
tasks:
- name: Get configured hostname
cisco.nxos.nxos_command:
commands:
- show hostname
register: device_hostname
- name: Display hostname output
ansible.builtin.debug:
var: device_hostname.stdout
- name: Save hostname to file on control node
ansible.builtin.copy:
content: "{{ device_hostname.stdout[0] }}"
dest: "/tmp/hostname_{{ inventory_hostname }}.txt"
delegate_to: localhost # Execute on the control node
6. Configuring Network Devices with Ansible
Configuration with Playbooks
flowchart LR
A["📋 Playbook\nYAML"] --> B["🔍 Ansible parses\nthe playbook"]
B --> C{"Facts\nneeded?"}
C -->|Yes| D["📊 Gather Facts\nfrom hosts"]
C -->|No| E["⚙️ Execute tasks\non hosts"]
D --> E
E --> F{"Change\nneeded?"}
F -->|Yes - changed 🟡| G["✏️ Apply the\nchange"]
F -->|No - ok 🟢| H["✔️ No action\nrequired"]
G --> I["📊 Final report\n(recap)"]
H --> I
Example: Enable OSPF and BGP with the nxos_config module:
---
- name: Configure OSPF and BGP features
hosts: switches
gather_facts: false
tasks:
- name: Enable OSPF feature
cisco.nxos.nxos_config:
lines:
- feature ospf
- name: Configure OSPF process
cisco.nxos.nxos_config:
lines:
- router ospf 1
- name: Enable BGP feature
cisco.nxos.nxos_config:
lines:
- feature bgp
- name: Configure BGP process
cisco.nxos.nxos_config:
lines:
- router bgp 65535
- name: Configure BGP address-family
cisco.nxos.nxos_config:
parents: router bgp 65535
lines:
- address-family ipv4 unicast
Idempotence
Idempotence is the property whereby an operation can be applied multiple times without changing the result beyond the first application.
Idempotence example on NX-OS:
switch# configure terminal
switch(config)# feature ospf # First call: enables OSPF
switch(config)# feature ospf # Second call: nothing changes
switch(config)# feature ospf # Third call: nothing changes
Idempotence in Ansible:
- If the desired state is already reached → status ok (green)
- If a change is needed → status changed (yellow)
- Ansible compares the current state with the desired state before acting
Best practice: Run playbooks regularly to automatically detect and fix configuration drift.
Check Mode (Dry Run)
Check mode performs a non-destructive dry run of the playbook. Ansible reports what it would do without actually applying changes.
# Dry run of the full playbook
ansible-playbook -i inventory.yaml playbook.yaml --check
# Targeted dry run on S1 only
ansible-playbook -i inventory.yaml playbook.yaml --check --limit S1
# Dry run with diff (shows lines that would be added/removed)
ansible-playbook -i inventory.yaml playbook.yaml --check --diff
Recommended workflow for change windows:
- During normal hours:
--checkto plan and validate - During the change window: normal execution to apply
Configuration with Variables
# group_vars/switches.yaml
---
bgp_asn: 65535
dns_servers:
- 8.8.8.8
- 8.8.4.4
ntp_server: ntp.corp.local
# host_vars/s1.yaml
---
loopback: 10.1.1.1
bgp_router_id: 10.1.1.1
# playbook.yaml
---
- name: Configure network devices with variables
hosts: switches
gather_facts: false
tasks:
- name: Configure DNS servers
cisco.nxos.nxos_config:
lines:
- "ip name-server {{ item }}"
loop: "{{ dns_servers }}"
- name: Configure NTP server
cisco.nxos.nxos_config:
lines:
- "ntp server {{ ntp_server }}"
- name: Configure BGP process with ASN variable
cisco.nxos.nxos_config:
lines:
- "router bgp {{ bgp_asn }}"
- name: Configure loopback interface
cisco.nxos.nxos_config:
parents: "interface Loopback0"
lines:
- "ip address {{ loopback }}/32"
Configuration with JSON/YAML Data
To load configuration data from an external file:
interface_data.json file:
{
"S1": [
{
"interface": "Loopback0",
"ip": "10.1.1.1/32",
"description": "Loopback interface S1"
},
{
"interface": "Ethernet1/1",
"ip": "192.168.12.1/30",
"description": "Link to S2"
}
],
"S2": [
{
"interface": "Loopback0",
"ip": "10.2.2.2/32",
"description": "Loopback interface S2"
},
{
"interface": "Ethernet1/1",
"ip": "192.168.12.2/30",
"description": "Link to S1"
}
]
}
Playbook using a lookup plugin and filters:
---
- name: Configure Layer 3 interfaces from JSON data
hosts: switches
gather_facts: false
tasks:
- name: Load interface data from JSON file
ansible.builtin.set_fact:
interface_data: "{{ lookup('file', 'interface_data.json') | from_json }}"
- name: Configure layer 3 interfaces
cisco.nxos.nxos_config:
parents: "interface {{ item.interface }}"
lines:
- "description {{ item.description }}"
- "ip address {{ item.ip }}"
- "no shutdown"
loop: "{{ interface_data[inventory_hostname] }}"
Debugging Playbooks
Ansible’s built-in debugger allows inspecting the state of a task during execution.
Possible values for debugger:
| Value | Behavior |
|---|---|
never | Never (default) |
always | Always |
on_failed | Only if the task fails |
on_unreachable | If the host is unreachable |
on_skipped | If the task is skipped (when condition = false) |
---
- name: Debug example
hosts: switches
gather_facts: false
tasks:
- name: Load interface data
ansible.builtin.set_fact:
interface_data: "{{ lookup('file', 'interface_data.json') | from_json }}"
debugger: on_failed # Enter the debugger if this task fails
Interactive debugger commands:
[1] TASK DEBUGGER> p task # Display the task name
[1] TASK DEBUGGER> p host # Display the target host
[1] TASK DEBUGGER> p task.args # Display the task arguments
[1] TASK DEBUGGER> p result # Display the task result
[1] TASK DEBUGGER> vars # Display all available variables
[1] TASK DEBUGGER> r # Replay (retry) the task
[1] TASK DEBUGGER> c # Continue execution
[1] TASK DEBUGGER> q # Quit the debugger and execution
Resource Modules
Resource modules are specialized Ansible modules that manage the configuration of a specific network feature in a declarative manner. They embody the “restaurant menu” concept from Ansible’s declarative philosophy.
Advantages of resource modules:
- Declarative syntax (desired state, not a command sequence)
- Guaranteed idempotence
- No need to know the exact CLI syntax
- Dependency management (e.g., enabling a feature before configuring it)
state parameter of resource modules:
| State | Description |
|---|---|
merged | Merge desired config with existing config |
replaced | Replace the relevant config section |
overridden | Replace the entire resource configuration |
deleted | Remove the configuration |
gathered | Collect current configuration as facts |
rendered | Display the configuration that would be generated |
parsed | Parse an existing configuration |
Example: Layer 3 interface configuration with resource modules:
---
- name: Configure L3 interfaces with resource modules
hosts: switches
gather_facts: false
tasks:
- name: Configure layer 3 interfaces
cisco.nxos.nxos_l3_interfaces:
config:
- name: Loopback0
ipv4:
- address: "{{ loopback }}/32"
- name: Ethernet1/1
ipv4:
- address: "{{ eth1_ip }}/30"
state: merged
- name: Configure OSPF with resource module
cisco.nxos.nxos_ospfv2:
config:
processes:
- process_id: "1"
router_id: "{{ loopback }}"
areas:
- area_id: "0.0.0.0"
default_cost: 100
state: merged
- name: Configure BGP with resource module
cisco.nxos.nxos_bgp_global:
config:
as_number: "{{ bgp_asn }}"
router_id: "{{ bgp_router_id }}"
neighbors:
- neighbor_address: "{{ bgp_peer_ip }}"
remote_as: "{{ bgp_peer_asn }}"
state: merged
Alternative Transport Methods (NETCONF/API)
# group_vars/all.yaml - Connect via HTTP API (NXAPI)
---
ansible_connection: ansible.netcommon.httpapi
ansible_network_os: cisco.nxos.nxos
ansible_httpapi_use_ssl: false
ansible_httpapi_validate_certs: false
“Catch-22” situation: Enable the API before using it:
---
# First play: enable NXAPI via SSH
- name: Configure NX-API feature
hosts: switches
gather_facts: false
vars:
ansible_connection: ansible.netcommon.network_cli # Override for this play
tasks:
- name: Enable NX-API HTTP access
cisco.nxos.nxos_nxapi:
http: true
http_port: 80
state: present
# Subsequent plays: use the API (httpapi connection from group_vars)
- name: Configure BGP via NX-API
hosts: switches
gather_facts: false
tasks:
- name: Configure BGP process
cisco.nxos.nxos_bgp_global:
config:
as_number: "{{ bgp_asn }}"
state: merged
7. Creating & Using Ansible Roles
Ansible Roles Philosophy
Roles allow organizing automation into a reusable and shareable format. They replicate the paradigm of real network roles:
Example with the Globomantics environment:
| Network role | Associated configuration |
|---|---|
network_devices | DNS, NTP, syslog, NXAPI (base services) |
routers | Layer 3 interfaces, IP addressing |
ospf_routers | OSPF feature, OSPF process, OSPF interfaces |
bgp_speakers | BGP feature, BGP process, peers, address-families |
Philosophy: Separate what varies per device (variables in host_vars) from what is common to all devices of a role (tasks in the role).
Role Directory Structure
roles/
└── network_devices/ # One role = one folder
├── tasks/
│ ├── main.yaml # Task entry point (required)
│ ├── dns.yaml # DNS tasks (imported from main.yaml)
│ ├── ntp.yaml # NTP tasks
│ └── syslog.yaml # Syslog tasks
├── handlers/
│ └── main.yaml # Handlers (e.g., restart service after config)
├── templates/
│ └── ntp.conf.j2 # Jinja2 templates
├── files/
│ └── banner.txt # Static files to deploy
├── vars/
│ └── main.yaml # Role variables (high priority)
├── defaults/
│ └── main.yaml # Default variables (low priority, easily overridden)
├── meta/
│ └── main.yaml # Metadata, role dependencies
└── README.md # Role documentation
defaults/main.yaml folder: Low-priority variables, easily overridden by the inventory.
---
# defaults/main.yaml for the network_devices role
dns_servers:
- 8.8.8.8
ntp_server: pool.ntp.org
syslog_server: 192.0.2.100
Refactoring with Roles
site.yaml file (main entry point):
---
# site.yaml
- import_playbook: switches.yaml
switches.yaml file:
---
# switches.yaml
- name: Configure Nexus switches
hosts: switches
gather_facts: true
roles:
- network_devices
- routers
- ospf_routers
- bgp_speakers
roles/network_devices/tasks/main.yaml file:
---
# tasks/main.yaml for the network_devices role
- name: Configure NXAPI
cisco.nxos.nxos_nxapi:
http: true
http_port: 80
state: present
when: ansible_network_os == 'cisco.nxos.nxos'
- name: Import DNS tasks
ansible.builtin.import_tasks: dns.yaml
- name: Import NTP tasks
ansible.builtin.import_tasks: ntp.yaml
- name: Import syslog tasks
ansible.builtin.import_tasks: syslog.yaml
roles/network_devices/tasks/dns.yaml file:
---
- name: Configure DNS servers
cisco.nxos.nxos_config:
lines:
- "ip name-server {{ item }}"
loop: "{{ dns_servers }}"
tags:
- dns
roles/network_devices/tasks/syslog.yaml file:
---
- name: Configure syslog server
cisco.nxos.nxos_logging:
dest: server
dest_level: 5
remote_server: "{{ syslog_server }}"
state: present
tags:
- syslog
roles/ospf_routers/tasks/main.yaml file:
---
- name: Enable OSPF feature
cisco.nxos.nxos_feature:
feature: ospf
state: enabled
- name: Configure OSPF with resource module
cisco.nxos.nxos_ospfv2:
config:
processes:
- process_id: "1"
router_id: "{{ loopback }}"
state: merged
- name: Activate interfaces under OSPF
cisco.nxos.nxos_ospf_interfaces:
config:
- name: Ethernet1/1
address_family:
- afi: ipv4
area:
area_id: "0.0.0.0"
state: merged
Tags for Testing Roles and Playbooks
Tags allow executing only specific parts of an Ansible project without modifying playbooks.
Adding tags to tasks:
---
- name: Configure syslog server
cisco.nxos.nxos_logging:
dest: server
dest_level: 5
remote_server: "{{ syslog_server }}"
state: present
tags:
- syslog
- logging
Adding tags to an entire role:
# switches.yaml
- name: Configure Nexus switches
hosts: switches
roles:
- role: network_devices
tags: [network_devices, base]
- role: ospf_routers
tags: [ospf, routing]
- role: bgp_speakers
tags: [bgp, routing]
Using tags on the command line:
# Run only tasks with the "syslog" tag
ansible-playbook -i inventory.yaml site.yaml --tags syslog
# Run tasks with "ospf" or "bgp" tags
ansible-playbook -i inventory.yaml site.yaml --tags "ospf,bgp"
# Skip tasks with the "syslog" tag
ansible-playbook -i inventory.yaml site.yaml --skip-tags syslog
# List all available tags in a playbook
ansible-playbook -i inventory.yaml site.yaml --list-tags
# Combine tags and limit for very targeted testing
ansible-playbook -i inventory.yaml site.yaml --tags syslog --limit S1
8. Reference Tables
Common Network Modules (cisco.nxos)
| Module | Description | Key Arguments |
|---|---|---|
cisco.nxos.nxos_config | Generic CLI configuration | lines, parents, src |
cisco.nxos.nxos_command | Execute show commands | commands |
cisco.nxos.nxos_feature | Enable/disable features | feature, state |
cisco.nxos.nxos_file_copy | Copy files | local_file, vrf |
cisco.nxos.nxos_l3_interfaces | Layer 3 interfaces | config, state |
cisco.nxos.nxos_ospfv2 | OSPFv2 configuration | config, state |
cisco.nxos.nxos_bgp_global | Global BGP configuration | config, state |
cisco.nxos.nxos_bgp_neighbor_address_family | BGP address-families | config, state |
cisco.nxos.nxos_logging | Syslog configuration | dest, dest_level, remote_server, state |
cisco.nxos.nxos_nxapi | NXAPI configuration | http, http_port, state |
cisco.nxos.nxos_vlans | VLAN configuration | config, state |
Useful Ansible Built-in Modules
| Module | Description | Key Arguments |
|---|---|---|
ansible.builtin.ping | Test connectivity | (none) |
ansible.builtin.debug | Display variables/messages | var, msg |
ansible.builtin.copy | Copy a file | content, dest, src |
ansible.builtin.set_fact | Define variables | <variable_name>: <value> |
ansible.builtin.template | Deploy a Jinja2 template | src, dest |
ansible.builtin.import_tasks | Import tasks | <file.yaml> |
ansible.builtin.include_tasks | Dynamically include tasks | <file.yaml> |
ansible.builtin.import_playbook | Import a playbook | <playbook.yaml> |
Network Connection Variables (inventory)
| Variable | Description | Common Values |
|---|---|---|
ansible_connection | Connection plugin | ansible.netcommon.network_cli, ansible.netcommon.httpapi, ansible.netcommon.netconf |
ansible_network_os | Network OS of the device | cisco.nxos.nxos, cisco.ios.ios, cisco.iosxr.iosxr, juniper.junos.junos, arista.eos.eos |
ansible_user | SSH username | admin, netadmin, etc. |
ansible_password | SSH password | Use ansible-vault |
ansible_host | Device IP address or FQDN | 192.168.1.1 |
ansible_port | SSH port | 22 (default) |
ansible_ssh_private_key_file | SSH private key | ~/.ssh/id_rsa |
ansible_become | Enable privilege escalation | true/false |
ansible_become_method | Escalation method | enable (Cisco) |
ansible_become_password | Enable password | Use ansible-vault |
ansible_httpapi_use_ssl | Use HTTPS for httpapi | true/false |
ansible_httpapi_validate_certs | Validate SSL certificates | true/false |
Essential Ansible CLI Commands
| Command | Description |
|---|---|
ansible --version | Check installed version |
ansible-playbook playbook.yaml | Execute a playbook |
ansible-playbook playbook.yaml --check | Dry run |
ansible-playbook playbook.yaml --diff | Show differences |
ansible-playbook playbook.yaml --limit S1 | Target a host |
ansible-playbook playbook.yaml --tags ospf | Execute by tag |
ansible-playbook playbook.yaml -v | Verbose mode (up to -vvvv) |
ansible -m ping all | Ad hoc command |
ansible-inventory --list | Display inventory as JSON |
ansible-inventory --graph | Display the inventory graph |
ansible-galaxy collection install <collection> | Install a collection |
ansible-galaxy collection list | List installed collections |
ansible-config dump | Display active configuration |
ansible-lint playbook.yaml | Lint the playbook |
9. Architecture Diagrams
Ansible Architecture: Control Node & Managed Nodes
graph TB
subgraph "Ansible Control Node"
AN["🖥️ Ansible\n(Python virtualenv)"]
CFG["📄 ansible.cfg"]
PB["📋 Playbooks YAML"]
INV["📦 Inventory"]
ROLES["🗂️ Roles"]
GV["📁 group_vars/\nhost_vars/"]
COL["📦 Collections\n(cisco.nxos, etc.)"]
end
subgraph "Globomantics Network"
S1["🔀 Switch S1\n(NX-OS)\n10.1.1.1"]
S2["🔀 Switch S2\n(NX-OS)\n10.2.2.2"]
end
AN -->|"SSH (network_cli)\nor HTTP (httpapi)\nor NETCONF"| S1
AN -->|"SSH (network_cli)\nor HTTP (httpapi)\nor NETCONF"| S2
CFG -.-> AN
PB -.-> AN
INV -.-> AN
ROLES -.-> AN
GV -.-> AN
COL -.-> AN
style AN fill:#1f6feb,color:#fff
style S1 fill:#238636,color:#fff
style S2 fill:#238636,color:#fff
Playbook Execution Flow
sequenceDiagram
participant ENG as 👤 Network Engineer
participant CN as 🖥️ Control Node
participant S1 as 🔀 Switch S1
participant S2 as 🔀 Switch S2
ENG->>CN: ansible-playbook -i inventory.yaml playbook.yaml
CN->>CN: Load inventory + variables
CN->>CN: Parse the YAML playbook
Note over CN,S2: Play 1: Configure OSPF
CN->>S1: SSH: gather_facts
S1-->>CN: facts (version, interfaces, etc.)
CN->>S2: SSH: gather_facts
S2-->>CN: facts
CN->>S1: Task: Enable OSPF feature
S1-->>CN: ok / changed
CN->>S2: Task: Enable OSPF feature
S2-->>CN: ok / changed
CN->>S1: Task: Configure OSPF process
S1-->>CN: ok / changed
CN->>S2: Task: Configure OSPF process
S2-->>CN: ok / changed
Note over CN,S2: Final summary
CN-->>ENG: PLAY RECAP:\nS1 : ok=3 changed=1 failed=0\nS2 : ok=3 changed=0 failed=0
Inventory Structure and Variable Inheritance
graph TD
ALL["📦 Group: all\nntp=ntp.global.corp.local\nansible_user=netadmin"]
SW["📦 Group: switches\nbgp_asn=65535"]
W["📦 Group: west\nntp=ntp.west.corp.local"]
E["📦 Group: east\nntp=ntp.east.corp.local"]
S1["🔀 Host: S1\nloopback=10.1.1.1\n← ntp inherited: ntp.west.corp.local\n← bgp_asn inherited: 65535"]
S2["🔀 Host: S2\nloopback=10.2.2.2\n← ntp inherited: ntp.east.corp.local\n← bgp_asn inherited: 65535"]
ALL --> SW
SW --> W
SW --> E
W --> S1
E --> S2
style ALL fill:#6e40c9,color:#fff
style SW fill:#1f6feb,color:#fff
style W fill:#0969da,color:#fff
style E fill:#0969da,color:#fff
style S1 fill:#238636,color:#fff
style S2 fill:#238636,color:#fff
Network Automation Pipeline with Ansible
flowchart LR
A["📝 Define\ndesired state\n(YAML/JSON)"] --> B["✅ Validate\nwith\nansible-lint\nyamllint"]
B --> C["🔍 Dry run\n--check\n--diff"]
C --> D{"Changes\ncorrect?"}
D -->|No| A
D -->|Yes| E["🔒 Change\nWindow\n(production)"]
E --> F["▶️ Run\nansible-playbook"]
F --> G{"Success?"}
G -->|failed| H["🐛 Debugger\n--debugger\n-vvvv"]
H --> A
G -->|ok/changed| I["📊 Verify\ncompliance\n(--check post)"]
I --> J["💾 Archive\noutputs\n(delegate_to: localhost)"]
style A fill:#6e40c9,color:#fff
style E fill:#da3633,color:#fff
style F fill:#1f6feb,color:#fff
style I fill:#238636,color:#fff
Ansible Role Structure
graph LR
subgraph "📋 site.yaml (entry point)"
SITE["import_playbook:\nswitches.yaml"]
end
subgraph "📋 switches.yaml"
SW["hosts: switches\nroles:\n- network_devices\n- routers\n- ospf_routers\n- bgp_speakers"]
end
subgraph "🗂️ roles/network_devices/"
TD["tasks/main.yaml\n→ dns.yaml\n→ ntp.yaml\n→ syslog.yaml"]
HD["handlers/main.yaml"]
DD["defaults/main.yaml\n(low priority variables)"]
end
subgraph "🗂️ roles/ospf_routers/"
TO["tasks/main.yaml\n(OSPF feature + process\n+ interfaces)"]
end
subgraph "🗂️ roles/bgp_speakers/"
TB["tasks/main.yaml\n(BGP feature + process\n+ peers + AF)"]
end
SITE --> SW
SW --> TD
SW --> TO
SW --> TB
TD -.-> HD
TD -.-> DD
Recommended next steps: Course: Automating Networks with Ansible the Right Way — to deepen advanced patterns for managing networks with Ansible in production.
Search Terms
ansible · network · automation · networking · web · servers · systems · security · configuration · playbooks · roles · control · devices · installation · inventory · node · variables · architecture · cli · commands · data · facts · inheritance · package