Ansible Network Examples: Facts, Backups and CLI - 夜莺博客

Ansible Network Examples: Facts, Backups and CLI

When you manage a mixed-vendor network with Ansible, the official examples are the fastest possible learning path: they are short, they run against real gear, and every line has been tested by the project. This article demonstrates the two scenarios that cover the majority of day-one automation work. First, using eos_facts, ios_facts and vyos_facts to collect facts from devices of several vendors and back their configurations up to the control node. Second, using the platform-independent ansible.netcommon.cli_command module to collapse a per-vendor task list into a single task that runs everywhere. Inventory variables and Ansible Vault password encryption are covered along the way.

Prerequisites: Controller, Collections and ansible.cfg

Network automation runs entirely on the control node. Switches and routers have no Python interpreter to install anything into, so Ansible connects over SSH, drives the CLI, and reads the prompt. Install Ansible plus the collections that supply the vendor modules, and check that they are visible before writing a playbook.

pip install ansible
ansible-galaxy collection install ansible.netcommon arista.eos cisco.ios vyos.vyos
ansible-galaxy collection list | grep -E "netcommon|eos|ios|vyos"

Add a project-local ansible.cfg so the playbook does not depend on flags typed at the shell. The two settings that matter most are gather_facts = False, which stops the setup module from trying and failing to log into a network device, and the persistent-connection timeouts, which govern how long Ansible waits for a command to return.

[defaults]
inventory = ./inventory
gather_facts = False
host_key_checking = False

[persistent_connection]
connect_timeout = 60
command_timeout = 120

Inventory Essentials

[all:vars]
ansible_connection = ansible.netcommon.network_cli
[switches:children]
eos
ios
vyos
[eos:vars]
ansible_network_os = arista.eos.eos
ansible_become = yes
ansible_become_method = enable

The key variables: ansible_connection must be network_cli (the devices have no Python), and ansible_become_method is fixed to enable for network devices. ansible_network_os selects the module set and the prompt-handling logic — arista.eos.eos, cisco.ios.ios, vyos.vyos.vyos. A useful side effect of the group structure is that hosts: switches in a playbook hits all three vendors while each child group keeps its own connection settings; adding a fourth platform means adding a group, not rewriting a task.

Keep the passwords out of the file itself. Reference vault variables in the inventory and store the real values encrypted:

# inventory
[eos:vars]
ansible_user = {{ vault_eos_user }}
ansible_password = {{ vault_eos_password }}
ansible_become_password = {{ vault_enable_password }}

# create and edit the encrypted variable file
ansible-vault create group_vars/all/vault.yml
ansible-vault edit group_vars/all/vault.yml

Two variables are all that is needed if you authenticate with keys instead: ansible_user and, for the enable jump, ansible_become_password. Everything else is derived from ansible_network_os.

Example 1: Collect Facts and Back Up Configurations

Inventory Highlights

[all:vars]
ansible_connection = ansible.netcommon.network_cli
[switches:children]
eos
ios
vyos
[eos:vars]
ansible_network_os = arista.eos.eos
ansible_become = yes
ansible_become_method = enable

Playbook: Gather and Back Up

- name: "Demonstrate connecting to switches"
  hosts: switches
  gather_facts: no
  tasks:
    - name: Gather facts (eos)
      arista.eos.eos_facts:
      when: ansible_network_os == 'arista.eos.eos'
    - name: Backup switch (eos)
      arista.eos.eos_config:
        backup: yes
      register: backup_eos_location
      when: ansible_network_os == 'arista.eos.eos'
    - name: Copy backup files
      copy:
        src: "{{ backup_eos_location.backup_path }}"
        dest: "/tmp/backups/{{ inventory_hostname }}/{{ inventory_hostname }}.bck"
ansible-playbook -i inventory facts-demo.yml

Three things are worth understanding in this play. The when clauses keep each vendor module from running against the wrong platform, which is why the same play can cover EOS, IOS and VyOS without error. The facts task fills ansible_net_hostname, ansible_net_model, ansible_net_version and ansible_net_serialnum for every device, so a single debug or template task can produce a hardware report. And the backup task returns backup_path, which is exactly the path the following copy task consumes — that chaining is the pattern you reuse everywhere.

Extend the same play to the other platforms and normalise the copy destination so the files land in one predictable tree:

    - name: Gather facts (ios)
      cisco.ios.ios_facts:
      when: ansible_network_os == 'cisco.ios.ios'

    - name: Backup switch (ios)
      cisco.ios.ios_config:
        backup: yes
      register: backup_ios_location
      when: ansible_network_os == 'cisco.ios.ios'

    - name: Copy every backup into the dated tree
      ansible.builtin.copy:
        src: "{{ item.path }}"
        dest: "/tmp/backups/{{ inventory_hostname }}/{{ lookup('pipe','date +%Y-%m-%d') }}-{{ inventory_hostname }}.bck"
      loop:
        - "{{ backup_eos_location }}"
        - "{{ backup_ios_location }}"
      when: item.path is defined

Turning Collected Facts Into a Fleet Inventory

Facts are only useful when they leave the console. Because every vendor facts module exposes the same variable names on the ansible_net_* prefix, one template renders an inventory CSV no matter which platform answered. The run_once plus delegate_to: localhost combination writes a single file rather than one per device, and hostvars lets the controller read every host's facts from a single task.

    - name: Render the fleet inventory
      ansible.builtin.copy:
        dest: "/tmp/backups/fleet-inventory.csv"
        content: |
          hostname,platform,model,version,serial
          {% for h in ansible_play_hosts_all %}
          {{ hostvars[h].ansible_net_hostname | default(h) }},{{ hostvars[h].ansible_network_os | default('unknown') }},{{ hostvars[h].ansible_net_model | default('n/a') }},{{ hostvars[h].ansible_net_version | default('n/a') }},{{ hostvars[h].ansible_net_serialnum | default('n/a') }}
          {% endfor %}
      delegate_to: localhost
      run_once: true

That file answers the questions a spreadsheet always fails to keep current: which devices run which software release, which serial numbers are approaching end of support, and which model is over-represented in the fleet. Feed it to your monitoring system, to a licence renewal report, or simply diff it monthly — a change in the file is a change on the network, and because the facts play also writes the running configuration, the same run produces the evidence for both.

Example 2: Platform-Independent Modules Simplify the Playbook

---
- hosts: network
  gather_facts: false
  connection: ansible.netcommon.network_cli
  tasks:
    - name: Run cli_command on Arista
      ansible.netcommon.cli_command:
        command: show ip int br
      register: result
      when: ansible_network_os == 'arista.eos.eos'
    - name: Run cli_command on Cisco IOS
      ansible.netcommon.cli_command:
        command: show ip int br
      register: result
      when: ansible_network_os == 'cisco.ios.ios'

cli_command also supports multi-prompt interaction (for example answering "New password" and "Retype new password" when changing credentials), and combined with loop it can handle scenarios such as configuration rollback:

    - name: Revert to the saved startup configuration
      ansible.netcommon.cli_command:
        command: "configure replace flash:{{ backup_name }} force"
      register: rollback
      when: ansible_network_os == 'cisco.ios.ios'
      loop: "{{ rollback_targets | default([]) }}"

Reading is only half of it. Once the write side uses the same connection, you can push a line of configuration without a vendor module — useful for one-off changes and for platforms whose collection lags behind the operating system:

    - name: Set the syslog server on every platform
      ansible.netcommon.cli_config:
        config: "logging host 10.0.0.50"

The trade-off is idempotency: cli_command and a blind cli_config always report changed, because they have no idea whether the device already matches. For anything that runs on a schedule, prefer eos_config, ios_config or the newer resource modules with a defined state, and keep cli_command for reads and genuinely one-shot writes.

Encrypting Passwords with Ansible Vault

Every credential in the inventory should come from a vault file. The workflow is small enough to adopt from day one: create the file, add the variables, reference them by name in the inventory and playbook, and pass the vault password at run time or via a password file with restrictive permissions.

ansible-vault create group_vars/all/vault.yml   # vault_eos_user, vault_eos_password, vault_enable_password
ansible-vault view   group_vars/all/vault.yml   # read without decrypting to disk
ansible-vault rekey  group_vars/all/vault.yml   # rotate the vault password

ansible-playbook -i inventory facts-demo.yml --ask-vault-pass
ansible-playbook -i inventory facts-demo.yml --vault-password-file ~/.vaultpass

Two details prevent most vault mistakes. First, the vault file only holds secrets; keep the variable names identical to the ones the inventory references so a typo fails loudly with an undefined-variable error rather than silently sending an empty password. Second, add no_log: true to any task that renders a secret, otherwise the plaintext appears in the run output and in any log you keep.

Verifying the Run and Scheduling Backups

ansible-playbook -i inventory facts-demo.yml -vv
ls -l /tmp/backups/eos01/
head -5 /tmp/backups/eos01/eos01.bck

The recap tells you whether the play worked: ok for tasks that ran without changing anything, changed for tasks that wrote files or configuration, and unreachable for devices that never answered over SSH. If a device is unreachable, every later task for that host is skipped, so check connectivity with a plain ssh and with ansible -m ansible.netcommon.cli_command -a "command='show version'" before blaming the playbook.

A facts-and-backup run is only valuable if it happens on a schedule. Because the playbook writes files, cron and CI both work:

# /etc/cron.d/netbackup
0 3 * * * ansible /srv/ansible/facts-demo.yml --vault-password-file /root/.vaultpass >> /var/log/netbackup.log 2>&1

Every night you get a fresh copy of each device's running configuration and a current hardware inventory, and diff between two days is a change log produced by the network itself rather than by a change ticket nobody filled in.

Troubleshooting

"Unable to open shell" means the connection never established: wrong ansible_network_os, SSH disabled on the device, or bad credentials. Verify by hand with ssh first. Privilege escalation timeouts come from a missing ansible_become_method=enable or an unset enable password. A when clause that never matches is nearly always a spelling difference in ansible_network_os — print it with debug: var=ansible_network_os rather than guessing. Backups land in the wrong place when backup_options is not set: the default directory sits beside the playbook, which is fine for a lab and confusing in a container. And the play hangs on a device whose output is longer than one screen — send terminal length 0 in an earlier task, or raise command_timeout in the persistent-connection section.

FAQ

Should facts come from the vendor modules or cli_command? Vendor facts modules: they return structured data you can index and compare. cli_command returns text you would have to parse yourself, which is exactly the work the vendor modules already do.

How often should backups run? Nightly is the usual baseline, plus on demand before any change playbook. If you have a configuration compliance requirement, run it hourly and keep the files in Git so history is immutable.

Can this replace a dedicated backup tool? For a few hundred devices, yes. Beyond that, a purpose-built collector that stores configurations with metadata and a diff UI pays for itself; the Ansible pattern above is ideal for getting started and for pulling a snapshot right before a change.

Further Reading

For a complete engineer-oriented walkthrough of Ansible on network gear, see Ansible for Network Engineers: Complete Guide; for your first playbook see Ansible Cisco IOS First Playbook; for the fuller facts/backup/CLI example set see Ansible Network Examples: Facts, Backups and cli_command; and for module-level detail on Cisco see Ansible network modules: ios_config, facts and config backup.

原文链接:https://docs.ansible.com/projects/ansible/latest/network/user_guide/network_best_practices_2.5.html