Ansible doesn’t actually "run" on your network devices; it orchestrates changes to them using SSH or NETCONF, treating your network configuration like software code.
Let’s see it in action. Imagine you have a Cisco IOS device and want to ensure a specific VLAN exists.
---
- name: Ensure VLAN 100 exists
hosts: cisco_routers
gather_facts: no
tasks:
- name: Create VLAN 100 if it doesn't exist
cisco.ios.ios_vlan:
config:
- vlan_id: 100
name: "Ansible_VLAN"
state: present
register: vlan_output
- name: Print VLAN creation status
ansible.builtin.debug:
var: vlan_output
When you run this playbook against a host defined in your Ansible inventory (e.g., cisco_routers), Ansible connects via SSH, sends the command to create VLAN 100 with the name "Ansible_VLAN" if it’s not already there, and then tells you what happened. The register task captures the output, and debug shows it. If VLAN 100 was already present, the task would report "ok" and no change. If it was created, it would report "changed". This is declarative configuration management: you state the desired end state, and Ansible figures out how to get there.
This approach fundamentally shifts how we manage networks. Instead of logging into devices one by one to make manual changes, we define our desired network state in YAML files (playbooks). Ansible then uses these playbooks to idempotently apply configurations across potentially hundreds or thousands of devices. Idempotency means running the same playbook multiple times has the same effect as running it once – it only makes changes if the current state doesn’t match the desired state. This drastically reduces human error and provides a clear, auditable history of all network changes.
The core components you interact with are:
- Inventory: A list of your network devices, often organized into groups. This can be a static file (INI or YAML) or dynamically generated from a source of truth like NetBox or CMDB.
[cisco_routers] 192.168.1.10 ansible_user=admin ansible_password=mysecretpassword 192.168.1.11 ansible_user=admin ansible_password=mysecretpassword [juniper_switches] 192.168.2.20 ansible_user=admin ansible_password=mysecretpassword - Playbooks: YAML files that describe tasks to be executed on your inventory. They define what you want to achieve.
- Modules: The actual pieces of code that Ansible uses to interact with devices. For network devices, these are vendor-specific (e.g.,
cisco.ios.ios_vlan,junipernetworks.junos.junos_interface) or platform-agnostic (e.g.,ansible.builtin.templatefor pushing config files). - Roles: A way to organize playbooks, variables, and tasks into reusable units. This promotes modularity and makes complex configurations easier to manage.
The power lies in treating your network configuration as code. This means you can leverage version control systems like Git for change tracking, collaboration, and rollbacks. You can automate testing of your network configurations before deploying them, just like you would with application code. This paradigm shift is often referred to as "Network as Code" or "Infrastructure as Code" applied to networking.
Consider the task of configuring an access control list (ACL). Instead of manually typing ip access-list standard NO_SSH and then adding lines, you’d use a playbook like this:
---
- name: Configure Standard ACL on Cisco IOS
hosts: cisco_routers
gather_facts: no
tasks:
- name: Apply standard ACL
cisco.ios.ios_acl:
acl_name: "NO_SSH"
acl_type: "standard"
rules:
- rule: "deny host 10.1.1.1"
- rule: "permit any"
state: present
register: acl_output
- name: Print ACL status
ansible.builtin.debug:
var: acl_output
This playbook ensures that a standard ACL named NO_SSH is configured on the target devices, denying traffic from 10.1.1.1 and permitting everything else. If the ACL already exists but has different rules, Ansible will modify it to match the rules specified in the playbook. This ensures consistency and removes the possibility of typos or missed steps during manual configuration.
Many network engineers struggle with how to manage sensitive information like device credentials. Ansible Vault is the solution. It allows you to encrypt sensitive data (like passwords or API keys) within your playbooks or in separate variable files. When you run a playbook that uses Vault-encrypted data, Ansible prompts you for a password to decrypt it on the fly, ensuring that sensitive credentials are never stored in plaintext in your version control system.
ansible-vault encrypt_string 'mysecretpassword' --name 'ansible_password'
This command encrypts a password, and the output can be pasted directly into your inventory or variable files.
The most surprising aspect for many is how Ansible handles configuration drift. By default, Ansible modules for network devices are designed to be idempotent. This means that if you run a playbook that configures an interface with a specific IP address, and the interface already has that IP address configured, Ansible will detect that no change is needed and simply report "ok". It doesn’t re-apply the configuration unnecessarily. However, if the interface’s IP address is different from what’s defined in your playbook, Ansible will attempt to change it to match your desired state, effectively correcting the drift. This idempotency is crucial for maintaining a stable and predictable network environment without manual intervention.
The next hurdle is integrating Ansible with existing network monitoring and troubleshooting tools.