Ansible for Cisco: IOS, NX-OS and ASA Playbook Guide - 夜莺博客

Ansible for Cisco: IOS, NX-OS and ASA Playbook Guide

Ansible automates Cisco devices without installing anything on them: it speaks SSH (network_cli) or an HTTP API to the device using credentials you already have. The reason it wins the first round for most network teams is idempotency — a module compares desired state against the running configuration and reports changed only when the device actually needed the change. This guide builds a single inventory covering IOS, NX-OS and ASA, then pushes configuration the safe way.

Install the collections you actually need

python3 -m venv venv && source venv/bin/activate
pip install ansible
ansible-galaxy collection install cisco.ios
ansible-galaxy collection install cisco.nxos
ansible-galaxy collection install cisco.asa
ansible-galaxy collection install ansible.netcommon
ansible-galaxy collection list | grep -i cisco

Install into a virtual environment rather than system Python: a distribution upgrade that silently bumps Ansible underneath an automation repository is a classic way to break production playbooks.

One inventory, three platforms

# inventory/network.yml
all:
  children:
    ios_devices:
      hosts:
        core-sw-01: { ansible_host: 10.0.0.1 }
        dist-sw-01: { ansible_host: 10.0.1.1 }
      vars:
        ansible_network_os: cisco.ios.ios
        ansible_connection: ansible.netcommon.network_cli
        ansible_become: true
        ansible_become_method: enable
        ansible_user: ansible
        ansible_password: "{{ vault_cisco_password }}"
    nxos_devices:
      hosts:
        dc-leaf-01: { ansible_host: 10.0.10.11 }
      vars:
        ansible_network_os: cisco.nxos.nxos
        ansible_connection: ansible.netcommon.network_cli
        ansible_user: admin
        ansible_password: "{{ vault_nxos_password }}"
    asa_firewalls:
      hosts:
        fw-01: { ansible_host: 10.0.0.250 }
      vars:
        ansible_network_os: cisco.asa.asa
        ansible_connection: ansible.netcommon.network_cli
        ansible_user: admin
        ansible_password: "{{ vault_asa_password }}"

Set ansible_network_os and ansible_connection: network_cli per group. ansible_become_method: enable handles the Cisco privileged-EXEC step, which is separate from the initial login on IOS and ASA but not on NX-OS. Store passwords in Ansible Vault, not plaintext group_vars:

ansible-vault create inventory/group_vars/vault.yml
ansible-vault encrypt_string 'S3cret' --name 'ansible_password'
ansible-playbook playbook.yml --ask-vault-pass

Read-only first: build confidence with facts

---
- name: Gather device facts
  hosts: ios_devices
  gather_facts: false
  tasks:
    - name: Collect facts
      cisco.ios.ios_facts:
        gather_subset: ["!config"]
    - name: Show hostname and model
      ansible.builtin.debug:
        msg: "{{ ansible_net_hostname }} {{ ansible_net_model }}"

Always set gather_facts: false for network devices. The default fact-gathering assumes a Python interpreter on the target and either fails or wastes minutes on every run; use ios_facts when you actually want facts.

Interfaces and VLANs with resource modules

---
- name: Baseline interfaces
  hosts: ios_devices
  gather_facts: false
  tasks:
    - name: Configure L3 interfaces
      cisco.ios.ios_l3_interfaces:
        config:
          - name: Loopback0
            ipv4: [{ address: "10.255.0.{{ host_id }}/32" }]
          - name: Vlan10
            ipv4: [{ address: "10.0.10.{{ host_id }}/24" }]
        state: merged

    - name: Configure interface attributes
      cisco.ios.ios_interfaces:
        config:
          - name: GigabitEthernet0/0
            description: "UPLINK to core-sw-01"
            enabled: true
            speed: "1000"
            duplex: full
        state: merged

Resource modules (ios_l3_interfaces, ios_l2_interfaces, ios_interfaces, ios_vlans, ios_acls) express intent rather than lines, which removes an entire class of bug: the line-based playbook that adds switchport access vlan 30 to a port that is also a trunk, because nothing in the YAML ever said "this port is an access port".

ACLs and interface application

    - name: Create management ACL
      cisco.ios.ios_acls:
        config:
          - afi: ipv4
            acls:
              - name: SERVERS_IN
                aces:
                  - sequence: 10
                    grant: permit
                    protocol: tcp
                    source: { address: 10.0.30.0, wildcard_bits: 0.0.0.255 }
                    destination: { address: 10.0.20.0, wildcard_bits: 0.0.0.255 }
                    destination_port: { eq: 443 }
                  - sequence: 100
                    grant: deny
                    protocol: ip
                    source: { any: true }
                    destination: { any: true }
                    log: true
        state: merged

    - name: Apply ACL to interface
      cisco.ios.ios_config:
        lines:
          - ip access-group SERVERS_IN in
        parents: ["interface Vlan30"]

Reach for a dedicated resource module first; fall back to ios_config with raw lines only when no resource module covers the feature, because raw lines are not idempotent by themselves.

Safety practices that decide whether this goes well

  • Run ansible-playbook --check first on every change window; it reports what would change without applying it.
  • Use --limit to target a single device for the first run of any new playbook.
  • Push raw lines last, with parents to pin the configuration context, and match: exact where you want strict comparison.
  • Keep a backup playbook that runs nightly and stores output in git — drift detection is the second-best reason to adopt Ansible after velocity.
  • Version the repository and require review; an unreviewed automation change is just a faster way to make the same mistake.

For a backup and drift-check playbook see Ansible Cisco IOS config backup and drift check, and for declarative VLAN automation on another platform see Ansible Arista EOS VLAN automation.

Check mode, blocks and rescue

ansible-playbook -i inventory/network.yml site.yml --check --diff

--check reports what would change without applying it, and --diff shows the configuration lines that would be added. On a first run against a new platform, combine check mode with --limit one-switch so the blast radius is a single device.

- name: Config push with rollback awareness
  hosts: ios_devices
  gather_facts: false
  tasks:
    - block:
        - name: Push baseline
          cisco.ios.ios_config:
            src: templates/baseline.j2
            save_when: changed
      rescue:
        - name: Report the failure
          ansible.builtin.debug:
            msg: "Push failed on {{ inventory_hostname }} - investigate before retry"
        - ansible.builtin.meta: end_host

block/rescue keeps one unreachable device from aborting a fleet-wide run, and save_when: changed writes to NVRAM only when something actually changed — important on devices where a bare write memory on every run slows down the window and pollutes configuration-change logs.

Module matrix across the three platforms

Task IOS NX-OS ASA
Show commands ios_command nxos_command asa_command
Facts ios_facts nxos_facts asa_facts
Raw config lines ios_config nxos_config asa_config
Interfaces / L3 ios_interfaces, ios_l3_interfaces nxos_interfaces, nxos_l3_interfaces via asa_config
ACLs ios_acls nxos_acls via asa_config

Prefer the dedicated resource module where one exists and fall back to the raw line module only for features without one. Schedule the backup playbook from cron or a scheduler and commit its output to git — that repository becomes both your drift report and your change history.

原文链接:https://ansiblepilot.com/articles/ansible-cisco-network-automation-ios-nxos-asa-complete-guide