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

VendorCollectionConnectionPlatforms
Cisco IOS/IOS-XEcisco.iosnetwork_cliCatalyst, ISR, ASR
Cisco NX-OScisco.nxosnetwork_cli / httpapiNexus 3K/5K/7K/9K
Juniper Junosjunipernetworks.junosnetconfQFX, EX, MX, SRX
Arista EOSarista.eosnetwork_cli / httpapi7050X, 7280R, 720XP
Multi-vendoransible.netcommonVariousResource 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

  1. Job Template: Pre-Change Backup → backs up all devices
  2. Approval Node → network team reviews change request
  3. Job Template: Apply Configuration → rolls out with serial: 2
  4. Job Template: Validation Tests → runs compliance + connectivity checks
  5. Approval Node → confirm or rollback
  6. 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

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.