Learn Ansible - Network Automation with Ansible
Episode 22 of 31

Learn Ansible - Network Automation with Ansible

Automating Cisco, Arista, and Juniper network devices with Ansible: getting to know network collections, network_cli/httpapi/netconf connections, running-config backup, and deploying VLANs and ACLs at scale.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Introduction

After episode 21, where we covered advanced inventory management and dynamic inventory — from cloud provider plugins to CMDB integration like NetBox and ServiceNow — in this episode we'll point Ansible at one of the most overlooked infrastructure segments in the automation world: network devices.

You might think, "automation is for servers, not for switches and routers." In reality, this is exactly where automation delivers the biggest and most quickly felt impact. Imagine a network engineering team having to add a new VLAN to 40 access switches simultaneously. Manually SSHing into 40 devices, typing the same commands over and over, while hoping none of them has a typo — that could take half a workday, or even longer if there are configuration differences between devices. One typo on device 23 could take a network segment down and bring emergency calls in the middle of the night.

Ansible turns that job into a single playbook run once, spreading across all devices in parallel. Combined with the idempotency concept we've known since the beginning, you can push the same configuration repeatedly without side effects. In this episode we'll cover why Ansible fits network automation, the network collections and modules available, the three connection types (network_cli, httpapi, netconf), and real-world case studies: automatic running-config backup, VLAN and ACL deployment, and templated configuration for many switches at once.

Main Discussion

Why Does Ansible Fit Network Automation?

There are strong reasons why Ansible has become the de facto standard for network automation:

  • Agentless — same as for servers, Ansible doesn't need to install an agent on network devices. SSH or API access that already exists is enough. This is important because most network devices can't have agents arbitrarily installed on them.
  • One language, many vendors — the same YAML playbook can be configured for Cisco, Arista, Juniper, and others just by swapping the collection and ansible_network_os.
  • Idempotentios_config only sends commands if the configuration isn't there yet, so there's no config bloat and no unnecessary changes.
  • Parallelforks (episode 15) lets Ansible manage dozens to hundreds of devices in parallel.
  • Inventory integration — the dynamic inventory from episode 21 (NetBox, ServiceNow, or cloud) can directly feed the network device list.

The analogy is simple: if servers are "city residents" that need to be handled one by one, then network devices are the "bridges and highways" connecting them all. Server automation without network automation is like building a new city but still opening and closing bridges manually.

Supported Network OS and Network Collections

Ansible supports almost all major network operating systems through the platform modules model. Each vendor has its own official collection:

PlatformCollectionansible_network_osExample Modules
Cisco IOScisco.iosiosios_config, ios_facts, ios_vlan, ios_acl_interfaces
Cisco NX-OScisco.nxosnxosnxos_config, nxos_facts, nxos_vlan
Cisco IOS-XRcisco.iosxriosxriosxr_config, iosxr_facts
Arista EOSarista.eoseoseos_config, eos_facts, eos_vlans
Juniper Junosjunipernetworks.junosjunosjunos_config, junos_facts, junos_interfaces
VyOScommunity.vyosvyosvyos_config, vyos_facts
Palo Alto PAN-OSpaloaltonetworks.panospanospanos_config, panos_commit
F5 BIG-IPf5networks.f5_bigipbigipbigip_virtual_server, bigip_command

Beyond vendor collections, the ansible.netcommon collection provides common network functionality — helpers, filters, and basic connections used across platforms. This collection is a dependency of almost every network collection, so make sure it's always installed:

Install collection jaringan
ansible-galaxy collection install ansible.netcommon cisco.ios arista.eos junipernetworks.junos

Note

The example file names in this article use a collective convention as placeholders. To follow along, adjust to the platform you use: ios_config for Cisco IOS, nxos_config for NX-OS, eos_config for Arista EOS, and junos_config for Juniper Junos.

Connection Types: network_cli, httpapi, and netconf

This is the key concept that distinguishes network automation from server automation. When dealing with network devices, you can't just use a regular ssh connection with the command module — devices have different ways of interacting. Ansible provides three dedicated connection plugins:

ConnectionProtocolSuitable ForDescription
network_cliSSH (CLI over SSH)Cisco IOS/NX-OS, Arista EOS, VyOSMost common; Ansible "talks" directly to the CLI like an engineer typing manually
httpapiREST API / HTTP(S)Devices with RESTCONF/API (e.g. Cisco IOS-XE, Palo Alto, F5)Faster and structured; auth via token or basic
netconfNETCONF (XML over SSH)Juniper Junos, Cisco IOS-XE/NX-OSIndustry standard for model-driven with transactional config support

The differences between the three can be seen in a single ansible.cfg configuration comparison:

ansible.cfg
[defaults]
# network_cli: default untuk mayoritas platform
[network_cli:ios]
ansible_connection: network_cli
 
[network_cli:junos]
ansible_connection: netconf
 
[device_httpapi]
ansible_connection: httpapi
ansible_httpapi_use_ssl: true
ansible_httpapi_validate_certs: false

Usually the connection is determined at the host/group inventory level, not in ansible.cfg. For example:

inventory/network.yml
all:
  children:
    core_switches:
      hosts:
        sw-core-01:
          ansible_host: 192.168.1.10
          ansible_user: admin
          ansible_password: "{{ vault_admin_password }}"
          ansible_network_os: ios
          ansible_connection: network_cli
          ansible_become: true
          ansible_become_method: enable
    edge_routers:
      hosts:
        rtr-edge-01:
          ansible_host: 192.168.2.1
          ansible_network_os: iosxe
          ansible_connection: httpapi
          ansible_httpapi_use_ssl: true
          ansible_httpapi_validate_certs: false

Notice the variables ansible_network_os and ansible_become_method: enable. For most network devices, privileged EXEC mode is accessed with the enable command, not sudo like on Linux. That's why ansible_become_method is set to enable.

Important

The ansible_password variable above shouldn't be written directly. Use Ansible Vault (episode 14) or the credential store on AWX/AAP (episode 20) to store device passwords. Network device credentials are highly sensitive assets — access to a switch means access to the entire network segment.

Gathering Device Facts with *_facts

Just like servers have facts (ansible_facts), network devices have their own facts collected by modules like ios_facts, nxos_facts, or eos_facts. These facts become the basis for adaptive configuration — for example, knowing the IOS version before deciding which commands are safe to use.

The following playbook gathers facts from all Cisco devices in the all group:

gather-ios-facts.yml
- name: Kumpulkan facts dari seluruh perangkat Cisco
  hosts: all
  gather_facts: false
  tasks:
    - name: Ambil facts IOS
      cisco.ios.ios_facts:
        gather_subset: all
 
    - name: Tampilkan ringkasan facts
      ansible.builtin.debug:
        msg: >
          Hostname: {{ ansible_net_hostname }}
          Platform: {{ ansible_net_version }}
          Serial: {{ ansible_net_serialnum }}
          Model: {{ ansible_net_model }}

The concise output appearing per device looks roughly like this:

bash
TASK [Tampilkan ringkasan facts]
ok: [sw-core-01] => {
    "msg": "Hostname: sw-core-01 Platform: 15.2(4)E9 Serial: FCW2139G0XW Model: WS-C2960X-48TS"
}
ok: [sw-access-07] => {
    "msg": "Hostname: sw-access-07 Platform: 15.2(2)E5 Serial: FOC1750Z2GD Model: WS-C2960+48TC"
}
Output task debug

Tip

Use gather_facts: false in network playbooks. Network devices don't run the regular setup module; facts are collected via *_facts modules. Keeping gather_facts: false speeds up execution and avoids unnecessary errors.

Deploying Configuration with ios_config

The *_config module is the heart of network configuration management. This module accepts a list of commands, little by little (called lines), and places them under a parent context. What makes it unique compared to just sending raw commands is the idempotency mechanism: Ansible only sends commands that don't already exist on the device.

A classic example: adding a VLAN to several switches at once.

deploy-vlans.yml
- name: Deploy VLAN ke seluruh switch akses
  hosts: switches
  gather_facts: false
  tasks:
    - name: Buat VLAN 100 dan 200
      cisco.ios.ios_config:
        lines:
          - vlan 100
          - name VLAN_DATA
          - vlan 200
          - name VLAN_VOIP
        save_when: modified

After the configuration is successfully changed, save_when: modified writes the config to NVRAM so the changes survive a reboot — equivalent to typing write memory in the CLI.

Managing ACLs is also easy. The following example applies an inbound ACL on the GigabitEthernet0/1 interface to restrict SSH access to only the management network:

deploy-acl.yml
- name: Terapkan ACL untuk interface management
  hosts: core_switches
  gather_facts: false
  tasks:
    - name: Buat ACL 101
      cisco.ios.ios_config:
        lines:
          - permit tcp host 10.10.10.10 any eq 22
          - permit tcp host 10.10.10.11 any eq 22
          - deny ip any any log
        parents: ip access-list extended MGMT-SSH
 
    - name: Terapkan ACL di interface
      cisco.ios.ios_config:
        lines:
          - ip access-group MGMT-SSH in
        parents: interface GigabitEthernet0/1
        before: no ip access-group MGMT-SSH in
        save_when: modified

Notice the keyword before: no ip access-group MGMT-SSH in. This prevents the ACL from stacking twice when the command is run repeatedly. The "remove first, then apply" pattern is a very useful idempotency trick for commands that can accept duplicates.

Templating Configuration for Many Devices

One of the greatest strengths of network automation is templating: a single Jinja2 template file (recall episode 8) filled with different values per device. Imagine you have 40 access switches and each switch has an almost identical trunk VLAN number, only differing in a few details. With a template, you only define it once:

templates/vlan_trunk.j2
!
interface {{ interface }}
  description {{ interface_desc }}
  switchport mode trunk
  switchport trunk allowed vlan {{ trunk_vlans }}
  switchport trunk native vlan {{ native_vlan }}

Then the following playbook renders the template and pushes it to each switch, with variable values from each device's host_vars:

deploy-trunk-template.yml
- name: Deploy konfigurasi trunk berbasis template
  hosts: switches
  gather_facts: false
  tasks:
    - name: Render template dan terapkan ke perangkat
      cisco.ios.ios_config:
        src: templates/vlan_trunk.j2
        save_when: modified

The variable values interface, trunk_vlans, and native_vlan come from each switch's host_vars. Result: one playbook, one template, 40 devices with consistent yet unique configuration per need.

Automatic Running-Config Backup

Losing a network device's configuration without a backup is a nightmare — a new device that's misconfigured alone can cut the network, let alone having to restore from zero. Ansible can automate backing up each device's running-config to the control node. The *_config module provides the backup parameter, which saves the current configuration to a timestamped file:

backup-running-config.yml
- name: Backup running-config seluruh perangkat
  hosts: all
  gather_facts: false
  tasks:
    - name: Ambil backup running-config
      cisco.ios.ios_config:
        backup: true
        backup_options:
          filename: "{{ ansible_host }}.cfg"
          dir_path: backups/

Running this playbook produces a backup file structure on the control node:

Linuxbash
backups/
├── 2026-08-02T14-30-05/
   ├── 192.168.1.10.cfg
   ├── 192.168.1.11.cfg
   └── 192.168.2.1.cfg
Struktur folder backup

Tip

Combine automatic backups with scheduling on AWX/AAP (episode 20) or a cron job so running-config snapshots are taken every night without human intervention. A backup that never runs is a backup that doesn't exist. Bonus: commit the backup results to Git so they can be diffed across versions and easily audited.

Routing Management

Routing configuration can also be fully automated. The following example adds static routes and enables OSPF on Cisco devices:

deploy-routing.yml
- name: Konfigurasi routing
  hosts: edge_routers
  gather_facts: false
  tasks:
    - name: Tambah route statis
      cisco.ios.ios_config:
        lines:
          - ip route 10.20.0.0 255.255.0.0 192.168.2.254
        save_when: modified
 
    - name: Aktifkan OSPF di area 0
      cisco.ios.ios_config:
        lines:
          - network 192.168.2.0 0.0.0.255 area 0
          - network 192.168.1.0 0.0.0.255 area 0
        parents: router ospf 1
        save_when: modified

The *_config module with parents lets you penetrate multiple context levels at once — exactly like typing configure terminal then router ospf 1 manually, but automatic and safe.

Common Pitfalls

1. Using a regular ssh connection and the command module

This is the most fatal mistake. Network devices don't follow how the ansible.builtin.command module works, which sends a command and reads an exit code. Use the dedicated modules (ios_config, ios_facts, etc.) and the network_cli/httpapi/netconf connections.

2. Forgetting ansible_become_method: enable

On Cisco devices, privileged EXEC commands need the enable command. Without ansible_become: true and ansible_become_method: enable, tasks will fail because commands are rejected by user EXEC mode.

3. Not using save_when: modified

Configuration successfully applied in memory will be lost when the device reboots if it isn't saved. Always use save_when: modified so changes persist to NVRAM (unless that's not desired).

4. Setting the wrong ansible_network_os

Each platform must use the correct ansible_network_os (ios for Cisco IOS, nxos for NX-OS, junos for Juniper, etc.). A wrong value makes Ansible use the wrong module and potentially send commands the device doesn't understand.

5. Overwriting configuration without parents or before

Sending commands without the correct context can accidentally replace an entire interface configuration. Use parents to target context, and before for idempotent operations like duplicate removal.

6. Neglecting backups

Never deploy large configuration without backing up first. Make backup: true a habit, or run a backup playbook as the first step in a network change management pipeline.

Conclusion

In this episode we've covered network automation with Ansible thoroughly: why Ansible is the top choice for network devices, collections and modules for various vendors (Cisco IOS/NX-OS, Arista EOS, Juniper Junos), the three connection types network_cli, httpapi, and netconf, VLAN, ACL, and routing management, template-based configuration deployment to many switches at once, and automatic running-config backup. We also identified common mistakes that often make network playbooks fail.

With this capability, you've completed one more important dimension of infrastructure automation: from Linux servers and cloud to the network devices that form the backbone of data communication. A rare skill that's highly valued in the industry, because engineers who can bridge DevOps and networking are still scarce.

In episode 23, we'll enter the modern application era: Kubernetes & Container Orchestration — managing containers and Kubernetes clusters using the kubernetes.core and community.docker collections, from the k8s module for Deployments and Services, Helm chart deployment, to programmatic Docker container and image management. Keep your enthusiasm up!

Learn Ansible - Network Automation with Ansible | Learn Ansible