Ansible Network Resource Modules: Idempotent Config - 夜莺博客

Ansible Network Resource Modules: Idempotent Config

The old way to automate a switch with Ansible was to render a Jinja template of the whole configuration and push it - which means every run rewrites everything, a single typo can wipe an interface, and the diff is unreadable. Resource modules change that: you declare the desired state of one resource (an interface, a VLAN, an L3 interface, a BGP neighbour) and Ansible converges just that resource, idempotently.

Why Resource Modules Beat Template Push

Template push Resource modules
Scope of change Entire configuration Only the resource you name
Idempotency Poor - always rewrites Real: 'changed' only when state differs
Diff quality Whole-config diff Per-resource before/after in --diff
Blast radius of a bug Everything the template covers The one resource
Works with Any device Platforms with resource module support (Cisco IOS/IOS XR, Arista EOS, Juniper, VyOS and more)

The Workflow

  1. Gather. Read current state into facts - the source of truth for the diff.
  2. Declare. Write the desired state as module parameters, from a variable file or group_vars.
  3. Check. Run in check mode to see what would change.
  4. Apply. Converge, then verify from a second source (a show command or monitoring).
  5. Record. Keep the variable files in Git and treat them as the intended configuration.

A Working Playbook

- name: Configure access interfaces
  hosts: access_switches
  gather_facts: false
  vars:
    access_ports:
      - name: GigabitEthernet1/0/1
        description: users-a1
        vlan: 20
      - name: GigabitEthernet1/0/2
        description: users-a2
        vlan: 20
  tasks:
    - name: Gather current interface state
      cisco.ios.ios_interfaces:
        state: gathered
      register: interfaces_before

    - name: Apply access port intent
      cisco.ios.ios_l2_interfaces:
        config:
          - name: "{{ item.name }}"
            access:
              vlan: "{{ item.vlan }}"
        state: overridden
      loop: "{{ access_ports }}"
      notify: save config

  handlers:
    - name: save config
      cisco.ios.ios_config:
        save_when: modified

Two details that make the difference between a demo and production:

  • state: merged vs overridden vs replaced. Merged adds what you declare and leaves the rest alone - safe for incremental work. Overridden makes the resource match your declaration exactly and removes anything else - correct for full intent, and dangerous if your declaration is incomplete. Replaced is per-resource removal rather than per-device. Choose deliberately.
  • Save behaviour. Nothing persists until you write the config. Either use a handler as above, or set save_when: modified, and verify the device actually saved it.

Facts as the Diff Engine

ansible-playbook access_ports.yml --check --diff
ansible-playbook access_ports.yml --limit sw-a1
ansible-doc cisco.ios.ios_l2_interfaces

Check mode plus diff is where the model pays off: you get a human-readable per-interface statement of what will change, which is exactly what a change-approval process needs. Save that output into the change ticket and you have both evidence and rollback context.

Where It Stops Working

Resource modules do not cover every feature. When a platform or a feature is missing, do not abandon the model - isolate the gap in a small, clearly-named task that uses *_config with explicit lines, and keep everything else declarative. Also mind the two operational risks: overlapping ownership (automation and a human editing the same device) and partial runs against a subset of the inventory, which leave the fleet in different states until the next full run.

Related reading: Ansible for Juniper Junos for the same pattern on Junos, GitOps for network configuration for the review pipeline around it, and Scrapli when you need something lighter than a full framework.

原文链接:https://docs.ansible.com/ansible/latest/network/getting_started/network_resources.html