Chapter 4: Advanced Ansible: Roles, Collections, and Vault
Introduction
In previous chapters, we laid the groundwork for network automation using Ansible, covering basic playbooks, inventories, and modules. As automation initiatives mature and scale within a NetDevOps framework, the need for modularity, reusability, and robust security becomes paramount. This chapter dives into advanced Ansible concepts: Roles, Collections, and Vault.
Ansible Roles provide a structured way to organize automation content, promoting code reusability and simplifying complex workflows. Ansible Collections offer a standardized format for packaging and distributing Ansible content, including roles, modules, and plugins, enhancing collaboration and multi-vendor support. Finally, Ansible Vault addresses the critical security requirement of managing sensitive data (such as API keys, passwords, and secrets) securely within your Infrastructure as Code (IaC) pipelines.
After completing this chapter, you will be able to:
- Structure your Ansible projects using roles for improved organization and reusability.
- Leverage Ansible Collections to access a rich ecosystem of pre-built automation content and share your own.
- Securely manage sensitive information in your Ansible playbooks using Ansible Vault.
- Apply these advanced techniques to build more robust, scalable, and secure network automation solutions across Cisco and multi-vendor environments.
Technical Concepts
4.1 Ansible Roles: Structuring Your Automation
Ansible roles are the cornerstone of modular and reusable automation. A role is a self-contained unit of automation that organizes tasks, variables, handlers, templates, and files into a predefined directory structure. This structure enforces consistency and allows roles to be easily shared and reused across different projects or even entire organizations.
The core principle behind roles is separation of concerns. Instead of monolithic playbooks, you break down automation logic into smaller, manageable roles, each responsible for a specific aspect of configuration (e.g., “configure_interfaces”, “deploy_ospf”, “setup_vlans”).
Role Directory Structure:
roles/
└── my_network_role/
├── tasks/ # Main tasks for the role (main.yml)
│ └── main.yml
│ └── configure_interface.yml
├── handlers/ # Handlers (triggered by tasks) (main.yml)
│ └── main.yml
├── defaults/ # Default variables for the role (main.yml)
│ └── main.yml
├── vars/ # Other variables for the role (main.yml)
│ └── main.yml
├── files/ # Static files copied to target (e.g., firmware)
│ └── my_config_template.j2
├── templates/ # Jinja2 templates rendered on target
│ └── device_banner.j2
├── meta/ # Role metadata (dependencies, author, license)
│ └── main.yml
└── README.md # Documentation for the role
Key Components and Their Purpose:
tasks/: Contains the main playbook tasks executed by the role.main.ymlis the entry point, but you can include other task files for better organization (e.g.,tasks/configure_interfaces.yml,tasks/verify_routing.yml).handlers/: Tasks that are only run when explicitly notified by another task. Handlers are typically used for service restarts or reloading configurations.defaults/: Variables defined here have the lowest precedence. They provide default values that can be easily overridden by variables from other sources (inventory, group_vars, host_vars, extra-vars). This is ideal for role-specific parameters that most users will accept but can customize.vars/: Variables defined here have higher precedence thandefaults/. They are typically used for variables that are essential to the role’s function and less likely to be overridden by external sources.files/: Contains static files that the role might need to copy to remote hosts (e.g., IOS images, license files).templates/: Houses Jinja2 templates. These files are rendered dynamically by Ansible on the control node before being transferred to the target host. This is crucial for generating device configurations based on variables.meta/: Defines role metadata, including dependencies on other roles, author information, and license.
Using Roles in Playbooks:
You can include roles in a playbook using the roles directive:
---
- name: Deploy network configuration using roles
hosts: network_devices
gather_facts: false
connection: network_cli
roles:
- common_device_setup
- configure_vlans
- deploy_ospf
For more dynamic or conditional role execution, include_role and import_role directives can be used within tasks. import_role is static (processed at playbook parsing), while include_role is dynamic (processed at runtime), allowing for loops and conditions.
Network Diagram: Ansible Role Architecture This diagram illustrates the internal structure of an Ansible Role.
@startuml
skinparam node {
BackgroundColor LightBlue
BorderColor DarkBlue
FontColor DarkBlue
RoundCorner 10
}
skinparam folder {
BackgroundColor LightGreen
BorderColor DarkGreen
FontColor DarkGreen
}
cloud "Ansible Control Node" as control_node {
folder "ansible_project/" as project {
folder "playbooks/" as playbooks {
file "site.yml" as site_yml
}
folder "roles/" as roles_folder {
folder "configure_interfaces/" as if_role {
folder "tasks/" as if_tasks {
file "main.yml" as if_tasks_main
file "ios.yml" as if_tasks_ios
file "junos.yml" as if_tasks_junos
}
folder "handlers/" as if_handlers {
file "main.yml" as if_handlers_main
}
folder "defaults/" as if_defaults {
file "main.yml" as if_defaults_main
}
folder "vars/" as if_vars {
file "main.yml" as if_vars_main
}
folder "templates/" as if_templates {
file "interface.j2" as if_template
}
folder "meta/" as if_meta {
file "main.yml" as if_meta_main
}
}
folder "deploy_ospf/" as ospf_role {
...
}
}
}
}
site_yml [label="> if_role : uses role
if_role"] if_tasks : executes
if_tasks [label="> if_handlers : notifies
if_tasks"] if_defaults : uses defaults
if_tasks [label="> if_vars : uses vars
if_tasks"] if_templates : renders templates
@enduml
4.2 Ansible Collections: Packaging and Distribution
Ansible Collections represent the future of Ansible content distribution. Introduced to provide a standardized, robust, and extensible way to package and share Ansible modules, plugins, roles, and playbooks. They solve several challenges faced by older methods (like ansible-galaxy roles):
- Multi-vendor Support: Collections often bundle modules and roles specifically designed for particular network vendors (e.g.,
cisco.ios,juniper.junos,arista.eos). - Namespacing: Collections provide a clear namespace (
namespace.collection_name) preventing naming collisions and making content discovery easier. - Dependencies: Collections can declare dependencies on other collections, ensuring all required components are installed.
- Versioning: Easier management of content versions, allowing users to consume specific versions of a collection.
A collection is essentially a directory structure with a galaxy.yml metadata file, packaged into a .tar.gz archive.
Collection Naming Convention (FQCN):
Every piece of content within a collection is referenced by its Fully Qualified Collection Name (FQCN), which follows the pattern namespace.collection_name.content_name. For example:
cisco.ios.ios_interface(module)community.general.ping(module)ansible.builtin.shell(module fromansible.builtincollection)
Installing Collections:
Collections are typically installed using ansible-galaxy collection install:
ansible-galaxy collection install cisco.ios juniper.junos arista.eos
They can also be specified in a requirements.yml file:
# collections/requirements.yml
collections:
- name: cisco.ios
version: ">=2.0.0"
- name: juniper.junos
version: "==2.1.0"
- name: arista.eos
Network Diagram: Ansible Collection Architecture This diagram illustrates how collections encapsulate various Ansible content and are used in playbooks.
namespace: {
shape: cylinder
label: "Namespace (e.g., cisco)"
collection: {
shape: rectangle
label: "Collection (e.g., ios)"
modules: {
shape: folder
label: "Modules"
module1: { label: "ios_interface" }
module2: { label: "ios_config" }
}
roles: {
shape: folder
label: "Roles"
role1: { label: "vlan_config" }
role2: { label: "ospf_setup" }
}
plugins: {
shape: folder
label: "Plugins"
plugin1: { label: "cli_parse" }
}
}
}
playbook: {
shape: document
label: "Ansible Playbook"
}
inventory: {
shape: database
label: "Inventory"
}
playbook -> namespace.collection.modules: uses FQCN
playbook -> namespace.collection.roles: uses FQCN
playbook -> inventory: targets devices
4.3 Ansible Vault: Secure Secrets Management
In any automation endeavor, especially when dealing with network devices, sensitive data such as API keys, CLI passwords, SNMP community strings, and certificates are inevitable. Storing these credentials in plain text within your IaC repository is a major security risk. Ansible Vault provides a robust solution for encrypting these sensitive variables and files.
Ansible Vault uses AES256 encryption with a user-provided password. When a playbook needs to access vaulted data, Ansible prompts for the vault password (or reads it from a file/environment variable), decrypts the data in memory, uses it, and then discards the decrypted version. The actual vaulted files remain encrypted on disk.
Key Ansible Vault Commands:
ansible-vault create FILENAME: Creates a new encrypted file.ansible-vault create group_vars/all/vault.ymlansible-vault edit FILENAME: Edits an existing encrypted file.ansible-vault edit group_vars/all/vault.ymlansible-vault encrypt FILENAME: Encrypts an existing plain-text file.ansible-vault encrypt host_vars/my_router/credentials.ymlansible-vault decrypt FILENAME: Decrypts an existing encrypted file back to plain text (use with caution).ansible-vault decrypt host_vars/my_router/credentials.ymlansible-vault rekey FILENAME: Changes the encryption password for an existing vaulted file.ansible-vault rekey group_vars/all/vault.ymlansible-vault encrypt_string 'my_secret_password': Encrypts a single string for embedding directly into a playbook or a non-vaulted YAML file.
Integrating Vault with Playbooks:
To use vaulted data, simply reference the variable as you normally would. When running ansible-playbook, you’ll need to provide the vault password:
ansible-playbook site.yml --ask-vault-pass
Or, if storing the password in a file (ensure this file is secure and not committed to source control):
ansible-playbook site.yml --vault-password-file ~/.ansible/vault_pass.txt
Vault IDs:
For projects with multiple vault files protected by different passwords, Ansible Vault IDs allow you to label and manage them distinctly. This is achieved by adding --vault-id to vault commands and --vault-password-file @prompt or @path_to_password_file to distinguish which vault password belongs to which ID.
Network Diagram: Ansible Vault Workflow This diagram illustrates the secure flow of sensitive data using Ansible Vault.
digraph G {
rankdir=LR;
node [shape=box, style=filled, fillcolor=lightgray];
edge [color=gray, fontcolor=darkgray];
user [label="Network Engineer"];
playbook [label="Ansible Playbook"];
vaulted_file [label="Encrypted secrets (vault.yml)\n(On Disk)", fillcolor=lightcoral];
ansible_engine [label="Ansible Engine\n(In Memory)", fillcolor=lightblue];
network_device [label="Network Device"];
user -> playbook [label="Executes"];
playbook -> vaulted_file [label="References vaulted vars"];
user -> ansible_engine [label="Provides Vault Password"];
vaulted_file -> ansible_engine [label="Encrypted data"];
ansible_engine -> ansible_engine [label="Decrypts in Memory", style=dotted, dir=none];
ansible_engine -> network_device [label="Uses decrypted credentials"];
}
RFC/Standard references: While Ansible Vault itself isn’t an RFC, its underlying encryption (AES256) adheres to NIST standards for symmetric key encryption (e.g., FIPS 197). Best practices for secrets management are often outlined by security frameworks like OWASP.
4.4 Multi-Vendor Configuration with Roles and Collections
Ansible’s network modules and collections are designed for multi-vendor environments. When building roles, you can leverage platform-specific tasks using conditionals or by structuring tasks to include files based on the device’s operating system.
Example Role Structure for Multi-Vendor:
roles/
└── configure_interface/
├── tasks/
│ ├── main.yml # Entry point, includes platform-specific tasks
│ ├── _ios.yml # Tasks for Cisco IOS/IOS-XE
│ ├── _junos.yml # Tasks for Juniper Junos
│ └── _eos.yml # Tasks for Arista EOS
├── defaults/
│ └── main.yml
└── vars/
└── main.yml
Within tasks/main.yml, you might have logic like this:
# roles/configure_interface/tasks/main.yml
---
- name: Determine network OS
set_fact:
network_os: ""
- name: Include Cisco IOS tasks
include_tasks: _ios.yml
when: network_os == 'cisco.ios.ios'
- name: Include Juniper Junos tasks
include_tasks: _junos.yml
when: network_os == 'juniper.junos'
- name: Include Arista EOS tasks
include_tasks: _eos.yml
when: network_os == 'arista.eos'
Each platform-specific task file (_ios.yml, _junos.yml, _eos.yml) would then use the appropriate modules from the respective collections.
Configuration Examples
This section provides practical Ansible examples demonstrating roles, collections, and vault for multi-vendor network automation.
Scenario: Configure VLANs on Cisco IOS-XE and Juniper Junos devices, using a shared role and sensitive data stored in Ansible Vault.
4.4.1 Project Setup
First, create the project directory and ansible.cfg:
ansible_project/
├── ansible.cfg
├── inventory.yml
├── playbooks/
│ └── site.yml
├── roles/
│ └── configure_vlan/
│ ├── tasks/
│ │ ├── main.yml
│ │ ├── _cisco_ios.yml
│ │ └── _juniper_junos.yml
│ ├── defaults/
│ │ └── main.yml
│ └── vars/
│ └── main.yml
├── group_vars/
│ └── all/
│ └── vault.yml # Encrypted file for all groups
├── host_vars/
│ ├── cisco_router/
│ │ └── vars.yml
│ └── juniper_switch/
│ └── vars.yml
└── collections/
└── requirements.yml
ansible.cfg:
[defaults]
inventory = ./inventory.yml
remote_user = ansible_user
private_key_file = ~/.ssh/id_rsa
host_key_checking = false
gathering = smart
collections_paths = ./collections
[privilege_escalation]
become = true
become_method = enable
become_user = root
become_ask_pass = false # Set to true if privilege escalation requires a password
[paramiko_connection]
record_host_keys = false # WARNING: Disables host key checking. Use only for labs or trusted environments.
Security Warning: host_key_checking = false and record_host_keys = false are dangerous in production environments as they disable SSH host key verification, making your connections vulnerable to man-in-the-middle attacks. Always enable host key checking and manage known hosts (~/.ssh/known_hosts) properly in production.
collections/requirements.yml:
collections:
- name: cisco.ios
- name: juniper.junos
Install collections: ansible-galaxy collection install -r collections/requirements.yml
inventory.yml:
all:
children:
cisco_devices:
hosts:
cisco_router:
ansible_host: 192.168.1.10
ansible_network_os: cisco.ios.ios
juniper_devices:
hosts:
juniper_switch:
ansible_host: 192.168.1.20
ansible_network_os: juniper.junos
group_vars/all/vault.yml (create and encrypt):
First, create the file: ansible-vault create group_vars/all/vault.yml
Enter a strong password when prompted.
Then add the following content (which will be encrypted):
ansible_user: "devnet_user"
ansible_ssh_pass: "YourSecurePassword123!" # This will be encrypted!
ansible_become_pass: "YourEnablePassword!" # This will be encrypted!
host_vars/cisco_router/vars.yml:
device_name: "Cisco-Router-10"
vlan_id: 100
vlan_name: "ENGINEERING"
host_vars/juniper_switch/vars.yml:
device_name: "Juniper-Switch-20"
vlan_id: 200
vlan_name: "DEVELOPMENT"
4.4.2 Ansible Role: configure_vlan
roles/configure_vlan/tasks/main.yml:
---
- name: Include Cisco IOS VLAN tasks
ansible.builtin.include_tasks: _cisco_ios.yml
when: ansible_network_os == 'cisco.ios.ios'
- name: Include Juniper Junos VLAN tasks
ansible.builtin.include_tasks: _juniper_junos.yml
when: ansible_network_os == 'juniper.junos'
roles/configure_vlan/tasks/_cisco_ios.yml:
---
- name: Configure VLAN on Cisco IOS-XE
cisco.ios.ios_config:
lines:
- "name "
parents: "vlan "
save_when_changed: true
delegate_to: ""
connection: network_cli
Note: save_when_changed: true will automatically save the configuration if changes are made. Be cautious with this in production.
roles/configure_vlan/tasks/_juniper_junos.yml:
---
- name: Configure VLAN on Juniper Junos
juniper.junos.junos_vlans:
config:
- name: ""
vlan_id: ""
state: merged
delegate_to: ""
connection: network_cli
roles/configure_vlan/defaults/main.yml:
---
# Default values for VLAN configuration
# These can be overridden by host_vars or group_vars
vlan_id: 1
vlan_name: "DEFAULT_VLAN"
4.4.3 Main Playbook
playbooks/site.yml:
---
- name: Deploy multi-vendor VLAN configuration
hosts: all
gather_facts: false
roles:
- configure_vlan
4.4.4 Execution and Verification
To run the playbook:
ansible-playbook playbooks/site.yml --ask-vault-pass
You will be prompted for the vault password.
Verification Commands:
For Cisco IOS-XE (cisco_router):
show vlan brief
Expected Output:
VLAN Name Status Ports
---- -------------------------------- --------- -------------------------------
100 ENGINEERING active
For Juniper Junos (juniper_switch):
show vlans
Expected Output:
VLAN Tag Interfaces
default 1
DEVELOPMENT 200
Troubleshooting Note: If you encounter SSH authentication errors, double-check the ansible_user and ansible_ssh_pass in your group_vars/all/vault.yml and ensure your vault password is correct. Use ansible-playbook -vvv for detailed output.
4.5 Automation Examples (Python Integration)
While Ansible is excellent for declarative configuration, Python is often used for dynamic inventory, complex data manipulation, or integration with external systems. Here’s how you might use Python with Ansible Vault for a different kind of secrets management, such as a custom script that needs access to vaulted data.
Scenario: A Python script needs to read a credential from an Ansible Vault file.
python_scripts/read_vaulted_secret.py:
This script demonstrates how to programmatically decrypt an Ansible Vault string. Note: For actual integration, you’d typically use the ansible.parsing.vault module, which is more robust. This example uses ansible-vault CLI for simplicity.
import subprocess
import json
import os
def decrypt_vaulted_string(vaulted_string, vault_password):
"""
Decrypts an Ansible vaulted string using the ansible-vault CLI.
This is a simplified example; for robust integration, use Ansible's API.
"""
try:
# Create a temporary file for the vaulted string
temp_vaulted_file = "temp_vaulted_string.yml"
with open(temp_vaulted_file, "w") as f:
f.write(f"secret_data: !vault |\n {vaulted_string}")
# Use ansible-vault decrypt to get the plain text
# -v is for verbose output, --output=- to print to stdout
command = [
"ansible-vault", "decrypt", temp_vaulted_file,
"--output=-",
"--vault-password-file", "-" # Read password from stdin
]
process = subprocess.run(
command,
input=vault_password.encode('utf-8'),
capture_output=True,
text=True,
check=True
)
# Parse the YAML output to extract the secret_data
# This is a very basic YAML parse, more robust parsing needed for complex files
output_lines = process.stdout.splitlines()
for line in output_lines:
if line.strip().startswith("secret_data:"):
return line.split(':', 1)[1].strip()
return None # Not found
except subprocess.CalledProcessError as e:
print(f"Error decrypting vault: {e.stderr}")
return None
finally:
if os.path.exists(temp_vaulted_file):
os.remove(temp_vaulted_file)
if __name__ == "__main__":
# Example vaulted string (replace with your actual vaulted string)
# You can get this by running: ansible-vault encrypt_string "MySuperSecretValue"
# Make sure to remove the `!vault |` and leading spaces from the generated string.
example_vaulted_string = """
$ANSIBLE_VAULT;1.1;AES256
66316263303661336465363065633630323334653334313334336166306161313330383163653139366432323062323864616261313636306663366431666465383561653136610a6237303038623766646539663738663639353931393033323030336239326162393033613637373830386665306636663965643431643433393038666135363665310a30363233393165386465393132643534346166663636306138373434643037
"""
# In a real scenario, you'd get the password from an environment variable,
# a secure prompt, or a secrets management system.
vault_password = input("Enter vault password: ")
decrypted_value = decrypt_vaulted_string(example_vaulted_string.strip(), vault_password)
if decrypted_value:
print(f"Decrypted Secret: {decrypted_value}")
else:
print("Failed to decrypt secret.")
To run this, first generate an encrypted string:
ansible-vault encrypt_string "MySuperSecretValue" --name 'secret_data'
Copy the full output (including !vault | and the encrypted string) into the example_vaulted_string variable in the Python script.
Then run:
python python_scripts/read_vaulted_secret.py
And enter your vault password.
4.6 Security Considerations
Ansible Vault is a critical security tool, but its effectiveness depends on proper implementation and adherence to best practices.
Attack Vectors & Mitigation:
- Vault Password Compromise:
- Attack Vector: Storing vault passwords in plain text, hardcoding them, or using weak passwords.
- Mitigation:
- Use strong, unique passwords for the vault.
- Never hardcode vault passwords in scripts or commit them to source control.
- Prefer prompting for passwords (
--ask-vault-pass) or using a dedicated~/.ansible/vault_pass.txtfile (secured with strict permissions likechmod 600). - Integrate with external secrets management solutions (e.g., HashiCorp Vault, CyberArk, AWS Secrets Manager) for production environments. Ansible Vault can act as a local secrets manager, but for enterprise-scale, external systems are preferred.
- Use
ANSIBLE_VAULT_PASSWORD_FILEenvironment variable for CI/CD pipelines, where the file content is provided by the CI/CD system’s secret store.
- Access to Decrypted Data:
- Attack Vector: Ansible decrypts vault files in memory during execution. If the control node is compromised, sensitive data could be exposed in RAM or logs.
- Mitigation:
- Secure your Ansible control node following hardening guides.
- Restrict access to the control node to authorized personnel.
- Ensure minimal logging of sensitive data. Ansible automatically redacts some sensitive variables, but custom tasks might expose data if not handled carefully (e.g.,
debug: var=ansible_ssh_pass).
- Malicious Collections/Roles:
- Attack Vector: Using unverified collections or roles from untrusted sources, which could contain malicious code.
- Mitigation:
- Only install collections from trusted sources (Ansible Galaxy, Red Hat Automation Hub, or your internal private galaxy).
- Review the code of external roles/collections before deploying them, especially for production.
- Use
checksumorsignatureverification if available for collections.
- Improper File Permissions:
- Attack Vector: Vault files (even encrypted) or vault password files having overly permissive file permissions.
- Mitigation:
- Set strict file permissions:
chmod 600for vault files and vault password files. - Ensure the Ansible user has the minimum necessary permissions to run playbooks and access only required resources.
- Set strict file permissions:
Compliance Requirements: Many compliance standards (PCI DSS, HIPAA, ISO 27001) mandate secure handling of sensitive data. Ansible Vault helps meet these requirements by encrypting credentials at rest and during transit (within the Ansible workflow). Always document your secrets management strategy for audits.
4.7 Verification & Troubleshooting
4.7.1 Verification
Successful execution of playbooks using roles, collections, and vault can be verified by:
- Playbook Output: Look for
changed=Xorok=Xstates in the playbook summary, indicating tasks completed successfully.PLAY RECAP ********************************************************************* cisco_router : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 juniper_switch : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 - Device State: Manually or automatically verify the configuration on the target network devices using vendor-specific
showcommands (as demonstrated in section 4.4.4). - Idempotence Check: Run the playbook again. If it’s truly idempotent, the output should show
changed=0, indicating no further changes were needed.
4.7.2 Troubleshooting Common Issues
| Issue | Description | Resolution Steps |
|---|---|---|
| Role Not Found | Ansible cannot locate the specified role. | 1. Ensure the role directory exists in roles/ or a path specified in ansible.cfg roles_path.2. Check for typos in the role name in your playbook. |
| Collection Not Found | Ansible cannot find the module or role referenced by an FQCN (e.g., cisco.ios.ios_config). | 1. Verify the collection is installed: ansible-galaxy collection list.2. Ensure collections_paths in ansible.cfg points to the correct location.3. Check for typos in the FQCN. 4. Run ansible-galaxy collection install -r requirements.yml if missing. |
| Vault Decryption Failed | Incorrect vault password or corrupted vault file. | 1. Double-check your vault password. 2. If using --vault-password-file, verify the file path and permissions (chmod 600).3. If a vault file is corrupted, you might need to recover from a backup (another reason for version control!). 4. Ensure you’re providing the correct --vault-id if used. |
| Variable Precedence Issues | A variable is not taking the expected value within a role (e.g., default not overridden). | 1. Understand Ansible’s variable precedence order (defaults are lowest). 2. Use ansible-playbook -vvv to see variable values during execution.3. Use ansible --syntax-check with -v to check variable definitions. |
| Multi-vendor Logic Not Triggering | Platform-specific tasks are not running for a device. | 1. Verify ansible_network_os is correctly set in your inventory.2. Check the when: conditions in roles/my_role/tasks/main.yml for correctness.3. Use ansible-playbook -vvv to inspect ansible_network_os value for the target host. |
| Syntax Errors in Role/Collection Content | YAML formatting errors or incorrect module parameters within tasks. | 1. Use ansible-lint to check for common issues and best practices.2. Use ansible-playbook --syntax-check to catch basic YAML errors.3. Refer to module documentation for correct parameter usage (e.g., ansible-doc cisco.ios.ios_config). |
Debug Commands:
ansible-playbook -vvv playbooks/site.yml --ask-vault-pass: Provides extremely verbose output, showing task details, variable values, and module calls. Invaluable for debugging.ansible --list-tasks -i inventory.yml playbooks/site.yml: Lists all tasks that would be executed by a playbook, useful for understanding flow.ansible-inventory --list -i inventory.yml: Shows the parsed inventory, including all host and group variables, which helps debug variable precedence.
4.8 Performance Optimization
While roles and collections improve organization and reusability, their impact on performance is generally positive due to better modularity. Vault has a minimal performance overhead.
- Role Design:
- Granularity: Create roles that are granular enough to be reusable but not so fine-grained that they introduce excessive overhead from context switching between tasks.
- Conditional Tasks: Use
when:conditions effectively to skip tasks that are not relevant to a particular device or state. - Idempotence: Ensure tasks are idempotent to avoid unnecessary changes and associated network calls.
- Collection Usage:
ansible.builtinvs. FQCN: For commonly used modules (e.g.,command,shell,copy), preferansible.builtin.<module_name>to avoid the overhead of collection lookup if you haven’t specifiedcollections:in your playbook or role.- Avoid Redundant Collections: Only install collections that are actively used to keep the Ansible environment lean.
- Vault Performance:
- Ansible Vault decryption happens in memory on the control node. For typical network automation, the overhead is negligible.
- If you have thousands of vaulted variables or very large vaulted files, there might be a slight increase in playbook startup time, but this is rarely a bottleneck.
- Network Module Optimization:
_configmodules: Whenever possible, use vendor-specific_configmodules (e.g.,cisco.ios.ios_config) that handle batching of commands more efficiently than sending individualcommandmodule calls.diff: true: Usediff: trueonly when necessary, as generating diffs can add a small overhead.- Parallelism: Adjust
forksinansible.cfgto optimize parallel execution for your environment, but be mindful of rate limiting or resource constraints on network devices.
4.9 Hands-On Lab: Implementing Multi-Vendor VLAN Role with Vault
Lab Topology (nwdiag):
nwdiag {
network core_network {
address = "192.168.1.0/24"
cisco_ios_xe [address = "192.168.1.10", description = "Cisco Catalyst 9K"];
juniper_junos [address = "192.168.1.20", description = "Juniper EX Series"];
ansible_control [address = "192.168.1.5", description = "Ubuntu VM (Ansible)"];
}
}
Objectives:
- Create an Ansible project structure.
- Develop a multi-vendor Ansible role (
configure_bgp_asn) to set a BGP Autonomous System Number (ASN) on Cisco IOS-XE and Juniper Junos devices. - Encrypt the BGP ASN using Ansible Vault.
- Execute a playbook to apply the role and vaulted variable to both devices.
- Verify the BGP ASN configuration on both devices.
Pre-requisites:
- An Ansible control node with Ansible installed.
- Two network devices (one Cisco IOS-XE, one Juniper Junos) with SSH connectivity from the Ansible control node.
cisco.iosandjuniper.junoscollections installed.- Network credentials (username/password) for
ansible_userwithenableorrootprivileges.
Step-by-Step Configuration:
Initialize Project: Create a new directory
ansible_bgp_laband navigate into it.mkdir ansible_bgp_lab cd ansible_bgp_lab mkdir -p playbooks roles/configure_bgp_asn/{tasks,defaults,vars} group_vars/all collectionsansible.cfg: (Same as previous example, but updatecollections_paths)# ansible.cfg [defaults] inventory = ./inventory.yml remote_user = ansible_user private_key_file = ~/.ssh/id_rsa host_key_checking = false gathering = smart collections_paths = ./collections [privilege_escalation] become = true become_method = enable become_user = root become_ask_pass = falsecollections/requirements.yml:# collections/requirements.yml collections: - name: cisco.ios - name: juniper.junosInstall:
ansible-galaxy collection install -r collections/requirements.ymlinventory.yml:# inventory.yml all: children: cisco_devices: hosts: cisco_router: ansible_host: 192.168.1.10 ansible_network_os: cisco.ios.ios juniper_devices: hosts: juniper_switch: ansible_host: 192.168.1.20 ansible_network_os: juniper.junosgroup_vars/all/vault.yml(Create and Encrypt): First, create the file:ansible-vault create group_vars/all/vault.ymlEnter a strong vault password. Add the following content (which will be encrypted):ansible_user: "your_ssh_username" ansible_ssh_pass: "YourSecureSSHPW!" ansible_become_pass: "YourSecureEnablePW!" bgp_asn: 65001 # This is the sensitive data we want to vaultroles/configure_bgp_asn/tasks/main.yml:# roles/configure_bgp_asn/tasks/main.yml --- - name: Include Cisco IOS BGP tasks ansible.builtin.include_tasks: _cisco_ios.yml when: ansible_network_os == 'cisco.ios.ios' - name: Include Juniper Junos BGP tasks ansible.builtin.include_tasks: _juniper_junos.yml when: ansible_network_os == 'juniper.junos'roles/configure_bgp_asn/tasks/_cisco_ios.yml:# roles/configure_bgp_asn/tasks/_cisco_ios.yml --- - name: Configure BGP ASN on Cisco IOS-XE cisco.ios.ios_config: lines: - "router bgp " - "no bgp default ip-routing" # Example additional config save_when_changed: true delegate_to: "" connection: network_cliroles/configure_bgp_asn/tasks/_juniper_junos.yml:# roles/configure_bgp_asn/tasks/_juniper_junos.yml --- - name: Configure BGP ASN on Juniper Junos juniper.junos.junos_config: lines: - "set routing-options autonomous-system " comment: "Configured BGP ASN via Ansible" diff: true delegate_to: "" connection: network_cliplaybooks/site.yml:# playbooks/site.yml --- - name: Deploy BGP ASN using multi-vendor role hosts: all gather_facts: false roles: - configure_bgp_asn
Verification Steps:
Run the playbook:
ansible-playbook playbooks/site.yml --ask-vault-passEnter your vault password when prompted.
Verify on Cisco IOS-XE (
192.168.1.10):show running-config | section router bgpExpected Output:
router bgp 65001 no bgp default ip-routingVerify on Juniper Junos (
192.168.1.20):show configuration routing-options | display setExpected Output:
set routing-options autonomous-system 65001;
Challenge Exercises:
- Modify the
configure_bgp_asnrole to also configure a BGP neighbor. Vault the neighbor’s IP address and remote ASN. - Add support for Arista EOS to the
configure_bgp_asnrole, including creating a_arista_eos.ymltask file and updatingmain.ymlwith the appropriatewhen:condition. - Explore using
ansible-vault encrypt_stringto embed a single encrypted variable directly intohost_varsinstead of a full vault file.
4.10 Best Practices Checklist
- Use Roles for Modularity: Always organize your automation content into roles for reusability and maintainability.
- Leverage Collections: Utilize vendor-specific and community collections to simplify multi-vendor automation and reduce custom code.
- Secure Secrets with Vault: Never store sensitive data in plain text. Use Ansible Vault for encryption.
- Strong Vault Passwords: Use complex, unique passwords for your vault files.
- Vault Password Management: Prefer
--ask-vault-pass, secure password files, or external secrets managers (HashiCorp Vault, CyberArk) for production. - Strict File Permissions: Apply
chmod 600to vault files and vault password files. - Idempotent Tasks: Design tasks to be repeatable without causing unintended side effects or configuration changes if the desired state already exists.
- Clear Variable Precedence: Understand Ansible’s variable precedence to avoid unexpected behavior in roles.
- Multi-vendor Logic: Use
when:conditions based onansible_network_osor other facts to execute platform-specific tasks within roles. - Ansible Linting: Use
ansible-lintto enforce coding standards and identify potential issues in roles and playbooks. - Version Control: Store all Ansible code (playbooks, roles, inventory,
requirements.yml) in a version control system (e.g., Git). Exclude vault password files from VCS. - Documentation: Document your roles, playbooks, and inventory for clarity and future reference.
4.11 Reference Links
- Ansible Roles: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html
- Ansible Collections: https://docs.ansible.com/ansible/latest/collections_guide/index.html
- Ansible Vault: https://docs.ansible.com/ansible/latest/vault_guide/index.html
- Cisco IOS Collection: https://docs.ansible.com/ansible/latest/collections/cisco/ios/index.html
- Juniper Junos Collection: https://docs.ansible.com/ansible/latest/collections/juniper/junos/index.html
- Arista EOS Collection: https://docs.ansible.com/ansible/latest/collections/arista/eos/index.html
- NIST FIPS 197 (AES): https://csrc.nist.gov/pubs/fips/197/archive/ipd
- OWASP Cheat Sheet Series - Credential Management: https://cheatsheetseries.owasp.org/cheatsheets/Credential_Management_Cheat_Sheet.html
4.12 What’s Next
This chapter propelled your Ansible skills from basic playbooks to advanced, structured automation. You’ve learned how to organize complex tasks with roles, leverage the power of collections for multi-vendor environments, and fortify your automation with Ansible Vault for secure secrets management. These are foundational skills for any serious NetDevOps practitioner.
In the next chapter, we will shift our focus to Advanced Python for Network Automation. We’ll delve deeper into using Python libraries like NAPALM and Nornir to build more programmatic and flexible automation solutions, often integrating them with Ansible for a hybrid approach. This will include advanced data parsing with TextFSM and Genie, and leveraging modern network APIs like NETCONF, RESTCONF, and gRPC with YANG data models, further expanding your NetDevOps toolkit.