Introduction
Enterprise networks span thousands of switches, routers, firewalls, and load balancers across multiple vendors. Managing configuration changes manually — SSH into each device, paste commands, hope nothing breaks — doesn't scale. Ansible's agentless architecture and vendor-specific collections make it the de facto tool for multi-vendor network automation: same playbook structure, same inventory, same CI/CD pipeline, regardless of whether the target is a Cisco Nexus, Juniper QFX, or Arista 7050X.
This guide covers practical patterns for enterprise network automation across the three major vendors, from initial setup through production workflows.
Network Collections
| Vendor | Collection | Connection | Platforms |
|---|---|---|---|
| Cisco IOS/IOS-XE | cisco.ios | network_cli | Catalyst, ISR, ASR |
| Cisco NX-OS | cisco.nxos | network_cli / httpapi | Nexus 3K/5K/7K/9K |
| Juniper Junos | junipernetworks.junos | netconf | QFX, EX, MX, SRX |
| Arista EOS | arista.eos | network_cli / httpapi | 7050X, 7280R, 720XP |
| Multi-vendor | ansible.netcommon | Various | Resource modules base |
# Install all network collections
ansible-galaxy collection install cisco.ios cisco.nxos junipernetworks.junos arista.eos ansible.netcommon
Inventory Structure
# inventory/network.ini
[cisco_ios]
core-sw01 ansible_host=10.1.1.1
core-sw02 ansible_host=10.1.1.2
dist-sw[01:20] ansible_host=10.1.2.[1:20]
[cisco_nxos]
dc1-spine[01:04] ansible_host=10.10.1.[1:4]
dc1-leaf[01:16] ansible_host=10.10.2.[1:16]
[juniper_junos]
fw-prod[01:02] ansible_host=10.2.1.[1:2]
edge-rtr[01:04] ansible_host=10.2.2.[1:4]
[arista_eos]
dc2-spine[01:04] ansible_host=10.20.1.[1:4]
dc2-leaf[01:16] ansible_host=10.20.2.[1:16]
[cisco_ios:vars]
ansible_network_os=cisco.ios.ios
ansible_connection=ansible.netcommon.network_cli
[cisco_nxos:vars]
ansible_network_os=cisco.nxos.nxos
ansible_connection=ansible.netcommon.network_cli
[juniper_junos:vars]
ansible_network_os=junipernetworks.junos.junos
ansible_connection=ansible.netcommon.netconf
[arista_eos:vars]
ansible_network_os=arista.eos.eos
ansible_connection=ansible.netcommon.network_cli
Pattern 1: Configuration Backup
The first automation win — backup every device before any change.
---
- name: Backup all network device configurations
hosts: cisco_ios:cisco_nxos:juniper_junos:arista_eos
gather_facts: false
vars:
backup_dir: "/opt/network-backups/{{ inventory_hostname }}"
timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
tasks:
- name: Create backup directory
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
mode: "0750"
delegate_to: localhost
- name: Backup Cisco IOS configuration
cisco.ios.ios_config:
backup: true
backup_options:
dir_path: "{{ backup_dir }}"
filename: "{{ inventory_hostname }}_{{ timestamp }}.cfg"
when: ansible_network_os == 'cisco.ios.ios'
- name: Backup Cisco NX-OS configuration
cisco.nxos.nxos_config:
backup: true
backup_options:
dir_path: "{{ backup_dir }}"
filename: "{{ inventory_hostname }}_{{ timestamp }}.cfg"
when: ansible_network_os == 'cisco.nxos.nxos'
- name: Backup Juniper configuration
junipernetworks.junos.junos_config:
backup: true
backup_options:
dir_path: "{{ backup_dir }}"
filename: "{{ inventory_hostname }}_{{ timestamp }}.cfg"
when: ansible_network_os == 'junipernetworks.junos.junos'
- name: Backup Arista configuration
arista.eos.eos_config:
backup: true
backup_options:
dir_path: "{{ backup_dir }}"
filename: "{{ inventory_hostname }}_{{ timestamp }}.cfg"
when: ansible_network_os == 'arista.eos.eos'
Pattern 2: VLAN Management with Resource Modules
Resource modules provide a vendor-agnostic approach — same data structure, different platforms.
---
- name: Configure VLANs across multi-vendor environment
hosts: cisco_ios
gather_facts: false
vars:
vlans:
- vlan_id: 100
name: SERVERS
state: active
- vlan_id: 200
name: WORKSTATIONS
state: active
- vlan_id: 300
name: VOIP
state: active
- vlan_id: 999
name: QUARANTINE
state: suspend
tasks:
- name: Configure VLANs on Cisco IOS
cisco.ios.ios_vlans:
config: "{{ vlans }}"
state: merged
register: vlan_result
- name: Display changes
ansible.builtin.debug:
msg: "{{ vlan_result.commands }}"
when: vlan_result.changed
# Same VLANs on Arista EOS
- name: Configure VLANs on Arista
hosts: arista_eos
gather_facts: false
tasks:
- name: Configure VLANs
arista.eos.eos_vlans:
config:
- vlan_id: 100
name: SERVERS
state: active
- vlan_id: 200
name: WORKSTATIONS
state: active
state: merged
Pattern 3: Interface Configuration
---
- name: Configure access ports on distribution switches
hosts: cisco_ios
gather_facts: false
tasks:
- name: Configure access interfaces
cisco.ios.ios_l2_interfaces:
config:
- name: GigabitEthernet1/0/1
mode: access
access:
vlan: 200
- name: GigabitEthernet1/0/2
mode: access
access:
vlan: 300
- name: GigabitEthernet1/0/48
mode: trunk
trunk:
allowed_vlans: "100,200,300,999"
native_vlan: 999
state: merged
- name: Configure interface descriptions
cisco.ios.ios_interfaces:
config:
- name: GigabitEthernet1/0/1
description: "User PC - Floor 2"
enabled: true
- name: GigabitEthernet1/0/2
description: "VoIP Phone - Floor 2"
enabled: true
- name: GigabitEthernet1/0/48
description: "Uplink to Core"
enabled: true
state: merged
Pattern 4: NX-OS BGP Configuration (Data Center)
---
- name: Configure BGP on Nexus spine switches
hosts: cisco_nxos:&dc1_spines
gather_facts: false
vars:
bgp_asn: 65001
router_id: "{{ ansible_host }}"
tasks:
- name: Configure BGP process
cisco.nxos.nxos_bgp_global:
config:
as_number: "{{ bgp_asn }}"
router_id: "{{ router_id }}"
log_neighbor_changes: true
bestpath:
as_path:
multipath_relax: true
state: merged
- name: Configure BGP neighbors (leaf switches)
cisco.nxos.nxos_bgp_neighbor_address_family:
config:
as_number: "{{ bgp_asn }}"
neighbors:
- neighbor_address: "{{ hostvars[item].ansible_host }}"
address_family:
- afi: ipv4
safi: unicast
route_map_in: LEAF-IN
route_map_out: LEAF-OUT
state: merged
loop: "{{ groups['dc1_leafs'] }}"
Pattern 5: Juniper Firewall Rules
---
- name: Configure firewall policies on Juniper SRX
hosts: juniper_junos:&firewalls
gather_facts: false
tasks:
- name: Configure security zones
junipernetworks.junos.junos_config:
lines:
- set security zones security-zone TRUST interfaces ge-0/0/1.0
- set security zones security-zone UNTRUST interfaces ge-0/0/0.0
- set security zones security-zone DMZ interfaces ge-0/0/2.0
- name: Configure firewall policies
junipernetworks.junos.junos_config:
src: templates/srx-policies.j2
comment: "Ansible managed - {{ ansible_date_time.date }}"
confirm: 5
register: policy_result
- name: Confirm configuration if successful
junipernetworks.junos.junos_config:
confirm_commit: true
when: policy_result.changed
Pattern 6: Compliance Checking
---
- name: Network compliance audit
hosts: cisco_ios
gather_facts: false
tasks:
- name: Get running configuration
cisco.ios.ios_config:
backup: false
register: running_config
- name: Check NTP configuration
ansible.builtin.assert:
that:
- "'ntp server 10.1.1.100' in running_config.stdout"
- "'ntp server 10.1.1.101' in running_config.stdout"
fail_msg: "NTP servers not configured correctly"
success_msg: "NTP compliance: PASS"
ignore_errors: true
register: ntp_check
- name: Check SSH configuration
ansible.builtin.assert:
that:
- "'ip ssh version 2' in running_config.stdout"
- "'no ip ssh version 1' in running_config.stdout or 'ip ssh version 2' in running_config.stdout"
- "'transport input ssh' in running_config.stdout"
fail_msg: "SSH not properly hardened"
ignore_errors: true
register: ssh_check
- name: Check banner
ansible.builtin.assert:
that:
- "'banner login' in running_config.stdout"
fail_msg: "Login banner missing"
ignore_errors: true
register: banner_check
- name: Generate compliance report
ansible.builtin.template:
src: compliance-report.j2
dest: "/opt/reports/{{ inventory_hostname }}_compliance.html"
delegate_to: localhost
Enterprise Workflow in AAP
Rolling Update with Approval Gates
- Job Template: Pre-Change Backup → backs up all devices
- Approval Node → network team reviews change request
- Job Template: Apply Configuration → rolls out with
serial: 2 - Job Template: Validation Tests → runs compliance + connectivity checks
- Approval Node → confirm or rollback
- Job Template: Post-Change Documentation → updates CMDB
# Limit blast radius with serial execution
---
- name: Apply network changes with rolling strategy
hosts: cisco_ios
serial: 2
max_fail_percentage: 10
gather_facts: false
tasks:
- name: Apply configuration change
cisco.ios.ios_config:
src: "changes/{{ change_ticket }}.j2"
register: change_result
notify: save configuration
- name: Verify connectivity post-change
cisco.ios.ios_ping:
dest: "{{ gateway_ip }}"
count: 5
register: ping_test
failed_when: ping_test.packet_loss_pct > 20
handlers:
- name: Save configuration
cisco.ios.ios_config:
save_when: modified
Troubleshooting
Connection Timeouts
# group_vars/cisco_ios.yml
ansible_command_timeout: 60
ansible_connect_timeout: 30
ansible_persistent_connect_timeout: 60
ansible_persistent_command_timeout: 60
Enable Mode / Privileged EXEC
# For devices requiring enable password
ansible_become: true
ansible_become_method: enable
ansible_become_password: "{{ vault_enable_password }}"
Netconf Not Responding (Juniper)
# Verify NETCONF is enabled on the device
ssh admin@fw-prod01 -s netconf
# Should return <hello> XML
# Enable if missing
set system services netconf ssh
commit
Related Articles
- Ansible Best Practices Guide
- Ansible Automation Platform Guide
- Ansible Roles for Reusable Automation
- MikroTik RouterOS Backup
- Dell EMC OS10 Network Backup
Conclusion
Enterprise network automation with Ansible follows the same patterns across vendors: inventory by platform, resource modules for structured config, _config modules for raw commands, and AAP workflows for approval gates and rolling deployments. Start with backups (zero risk, immediate value), progress to compliance checks, then tackle configuration management. The combination of multi-vendor collections and AAP's credential management and workflow engine makes Ansible the practical choice for networks with thousands of devices across Cisco, Juniper, and Arista.