Ansible Network Automation: Facts Collection and Config Backup - 夜莺博客

Ansible Network Automation: Facts Collection and Config Backup

Ansible is the standard tool for multi-vendor network automation because it separates data from logic: your inventory defines what each device is, the network modules handle the vendor-specific CLI work, and the same playbook runs against Arista, Cisco and VyOS switches. Based on the official Ansible Network Examples documentation, this guide builds a complete facts-collection and configuration-backup workflow — inventory with network_cli connection, per-platform group variables, facts modules, and the built-in backup: option of config modules.

Every serious network automation project starts from the same uncomfortable truth: the documentation is stale, the diagram is wrong, and nobody can tell you with confidence which devices are running which software release. Before you can safely automate a change, you have to automate discovery. This article walks through the two halves of that job — gathering facts from live devices and saving their configurations — using the platform modules that ship with the official Ansible network collections. The playbooks here run essentially unchanged against Arista EOS, Cisco IOS and VyOS, which is the entire reason to use Ansible instead of a folder full of vendor-specific shell scripts.

Why Facts First, Changes Second

Network modules fall into two broad families. Command modules such as cli_command run an arbitrary CLI string and hand the raw text back to you. Resource modules such as eos_config or ios_vlans understand a device model, compare your desired state against reality, and push only the difference. Facts modules sit underneath both: they read the device non-destructively and return structured, machine-readable data that the rest of your playbook can act on.

Collecting facts before every change buys you four things at once. You learn the real platform and software version, so branching logic stops being guesswork. You capture a baseline of interfaces, VLANs and routing instances to diff against later. You build a hardware inventory of models and serial numbers that support renewals and asset databases always ask for. And you end up with a stored snapshot that can feed a configuration-management database without anyone typing it in by hand.

A useful mental model: facts are the read side of an API, config modules are the write side, and the backup: option is the undo button. If you only ever learn one of the three, learn backup, because it is the difference between a bad afternoon and an unplanned job search.

Inventory with Network Variables

[all:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_user=ansible

[switches:children]
eos
ios
vyos

[eos]
veos01 ansible_host=veos-01.example.net

[eos:vars]
ansible_become=yes
ansible_become_method=enable
ansible_network_os=arista.eos.eos

Three variables matter most: ansible_connection must be a network connection plugin (network_cli), ansible_network_os selects the platform module set, and ansible_become_method=enable raises privileges where needed (EOS/IOS). Never store passwords in plain text — encrypt them with Ansible Vault.

That last sentence deserves emphasis. The moment an inventory file with a clear-text ansible_password lands in Git, you have created an incident waiting to be discovered. Two patterns are common in production. The first encrypts a single value with ansible-vault encrypt_string and references it inline as an !vault | block. The second keeps the entire group_vars file vaulted and lets the connection plugin decrypt it in memory at run time. For larger fleets, prefer SSH keys plus AAA on the device so that no per-device secret exists at all, and rotate TACACS or RADIUS accounts on a schedule.

Understanding the Connection Styles

Ansible talks to network devices differently from servers. There is no Python interpreter on a switch, no SSH session that can simply run the module remotely, and therefore no "push a script and execute it" model. Instead the control node runs the module locally and speaks the device's CLI over a persistent SSH session. That is what network_cli provides: one long-lived connection per host, with the platform's prompt, paging and privilege handling handled by the connection plugin rather than by your task.

Juniper and some modern platforms additionally support netconf as the connection type, which returns structured XML instead of scraped text and is generally more reliable for configuration work. The trade-off is that netconf requires the feature to be enabled on the device and depends on a schema, so many teams start with network_cli everywhere for consistency and migrate device families to netconf later.

Collecting Facts with Platform Modules

- name: Gather facts (eos)
  arista.eos.eos_facts:
  when: ansible_network_os == 'arista.eos.eos'

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

- name: Gather facts (vyos)
  vyos.vyos.vyos_facts:
  when: ansible_network_os == 'vyos.vyos.vyos'

- name: Display some facts
  debug:
    msg: "Hostname {{ ansible_net_hostname }} OS {{ ansible_net_version }}"

Note the when guard on each task. Ansible cannot dispatch the correct facts module automatically because the fact modules register under different variable namespaces, so the conventional pattern is to run every platform module and let the condition decide which one fires. An alternative that scales better is to name the module dynamically with include_role or a small dispatch task, but the explicit guard is easiest to read when you are learning.

Notice also that facts land as ansible_net_* variables. Once collected, they are available to every later task, template and Jinja2 expression in the play.

Working with the Returned Fact Data

- name: Save facts to a local report
  copy:
    content: "{{ ansible_net_facts | to_nice_json }}"
    dest: "/tmp/reports/{{ inventory_hostname }}-facts.json"

- name: Build a simple interface report
  debug:
    msg: >-
      {{ inventory_hostname }} has {{ ansible_net_interfaces | length }}
      interfaces and is running {{ ansible_net_version }}

Turning facts into files is where the value actually lands. A nightly job that writes one JSON document per device and commits the results to Git gives you a free audit trail: you can query it with jq, chart interface counts over time, and prove to an auditor exactly when a version changed. Teams that already run a configuration backup tool such as Oxidized for network configuration backup often let Ansible handle the structured facts while Oxidized handles the raw config text.

Backing Up Running Configurations

Config modules such as arista.eos.eos_config and vyos.vyos.vyos_config have a backup: option that saves a full copy of the running config before any change. Register the result and move it to a backup directory:

- name: Backup switch (eos)
  arista.eos.eos_config:
    backup: yes
  register: backup_eos_location
  when: ansible_network_os == 'arista.eos.eos'

- name: Create backup dir
  file:
    path: "/tmp/backups/{{ inventory_hostname }}"
    state: directory

- name: Copy backup files
  copy:
    src: "{{ backup_eos_location.backup_path }}"
    dest: "/tmp/backups/{{ inventory_hostname }}/{{ inventory_hostname }}.bck"

The backup: keyword is deceptively simple. Every time the module runs, it writes the current running configuration to a timestamped file on the control node, and exposes the path as backup_path in the registered result. That means the backup reflects the state of the device immediately before your change, which is exactly the artifact you want when a change goes wrong at three in the morning.

Because the path is timestamped, the same device accumulates a history of files rather than a single overwritten copy. That history is gold for change review: you can diff yesterday against today and see precisely what drifted.

Storing, Naming and Rotating Backups

- name: Copy with a dated, predictable name
  copy:
    src: "{{ backup_eos_location.backup_path }}"
    dest: "/srv/backups/{{ inventory_hostname }}/{{ inventory_hostname }}-{{ ansible_date_time.iso8601_basic_short }}.cfg"

- name: Keep only the 30 most recent backups per device
  shell: "ls -1t /srv/backups/{{ inventory_hostname }}/*.cfg | tail -n +31 | xargs -r rm --"
  changed_when: false

Two conventions make backups usable months later. Encode the hostname and a sortable timestamp in the filename, so a directory listing is automatically chronological. And never let the backup directory grow without bound — a rotation task that keeps the last thirty copies per device costs nothing and saves you from filling a disk. If you already ship logs and configs to version control, point the destination at a checked-out directory and let a post-run hook commit the changes, which gives you line-level history and blame for free.

Diffing Configurations to Detect Drift

- name: Fetch the running config
  ansible.netcommon.cli_command:
    command: show running-config
  register: running

- name: Compare against the last known-good file
  copy:
    content: "{{ running.stdout[0] }}"
    dest: "/srv/current/{{ inventory_hostname }}.cfg"

- name: Show the diff
  command: "diff -u /srv/baseline/{{ inventory_hostname }}.cfg /srv/current/{{ inventory_hostname }}.cfg"
  register: cfgdiff
  changed_when: false
  failed_when: false

- name: Alert when drift is detected
  debug:
    msg: "{{ inventory_hostname }} has drifted: {{ cfgdiff.stdout_lines }}"
  when: cfgdiff.stdout | length > 0

Drift detection is the natural companion to backup. A nightly playbook that fetches every running config and diffs it against a known-good baseline turns "someone edited a device by hand six months ago" into a same-day alert. Combine this with backup: on your change playbooks and you have both halves: a baseline to compare against and a pre-change copy to restore from.

Running Backups on a Schedule

Backups that depend on a human remembering to run a playbook are not backups. Wire the playbook into cron on the control node, into a CI pipeline, or into AWX / Red Hat Ansible Automation Platform as a scheduled template. A minimal crontab entry runs the playbook nightly and mails the output:

# /etc/cron.d/net-backup
30 2 * * * ansible ansible-playbook -i inventory/hostfile.ini backup.yml >> /var/log/net-backup.log 2>&1

Whatever the scheduler, make the run idempotent and make failures loud. A silent cron job that has been failing for three weeks is worse than no job, because it creates false confidence in a backup that does not exist.

Using Check Mode Before Any Change

ansible-playbook -i inventory/hostfile.ini site.yml --check --diff

--check runs the playbook in dry-run mode: modules report what they would change without touching the device. --diff prints the exact configuration lines that would be added or removed. For config modules on network platforms this combination is the single most valuable safety net you have, because it lets you review a change as a text diff before it reaches a production device. Always run a change playbook with --check --diff first, especially on a fleet of more than one switch.

Troubleshooting Common Failures

  • Connection timeouts: usually a missing ansible_network_os, which makes Ansible fall back to the default SSH plugin and try to run Python on the device. Set it explicitly per host or platform group.
  • Privilege errors: the device rejected a command because enable mode was never entered. Confirm ansible_become: yes and the correct ansible_become_method for the platform.
  • Hanging on a long show command: the device is waiting at a pager prompt. Network connection plugins disable paging automatically, but a custom prompt or banner can defeat that; check for unusual login banners.
  • Facts missing after collection: a when guard evaluated false, so the module was skipped. Verify the inventory's ansible_network_os value matches the string in your condition exactly.

Platform-Independent Commands

For simple show commands across many platforms, ansible.netcommon.cli_command replaces per-vendor modules:

- name: Run show ip int br
  ansible.netcommon.cli_command:
    command: show ip int br
  register: result

Parallel to it, ansible.netcommon.cli_config pushes a raw configuration block. These two modules are the escape hatch when a platform has no dedicated resource module yet, and they are also how you reach commands that the resource modules deliberately do not model. Use them sparingly: raw command modules are not idempotent, so they will report a change on every run and they carry none of the safety logic that resource modules provide.

More Automation Content on This Site

See Ansible network automation inventory and playbook examples, multi-vendor automation with Ansible and Jinja2 and Netmiko Python automation. For the deeper module-level walkthrough of ios_config, facts and backup together, read our Ansible network modules reference.

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