Network Automation with Ansible: From Manual CLI to Infrastructure as Code

After years of manually configuring network devices through CLI, I made the switch to network automation. The transformation wasn’t just about saving time — it fundamentally changed how we approach network operations. Here’s what I learned implementing Ansible-based automation across enterprise networks.

Why Network Automation Matters

The traditional approach to network management has serious limitations:

  • Configuration drift: Manual changes accumulate inconsistencies
  • Human error: Typos in CLI commands cause outages
  • Audit challenges: No clear record of who changed what and when
  • Slow disaster recovery: Rebuilding configurations from scratch takes hours

Automation addresses all of these while enabling practices that were previously impractical.

Starting with Ansible for Networks

Ansible works differently for network devices than for servers. Most network gear doesn’t run Python, so Ansible connects via SSH and sends commands directly. This makes it accessible even for legacy equipment.

Basic Inventory Structure

inventory/hosts.ini
[core_switches]
core-sw-01 ansible_host=10.0.1.1
core-sw-02 ansible_host=10.0.1.2
[access_switches]
access-sw-[01:24] ansible_host=10.0.2.{{ item }}
[firewalls]
fw-primary ansible_host=10.0.0.1
fw-secondary ansible_host=10.0.0.2
[all:vars]
ansible_network_os=ios
ansible_connection=network_cli
ansible_user=automation
ansible_ssh_private_key_file=~/.ssh/network_automation

Group Variables for Configuration Standards

group_vars/all.yml
---
ntp_servers:
- 10.0.0.10
- 10.0.0.11
dns_servers:
- 10.0.0.20
- 10.0.0.21
syslog_servers:
- 10.0.0.30
snmp_community: "{{ vault_snmp_community }}"
banner_motd: |
********************************************
* AUTHORIZED ACCESS ONLY *
* All activity is monitored and logged *
********************************************

Practical Playbook Examples

Configuration Backup

This is often the first automation task — backing up all device configurations nightly:

playbooks/backup_configs.yml
---
- name: Backup Network Device Configurations
hosts: all
gather_facts: no
vars:
backup_root: "/backup/network/{{ inventory_hostname }}"
tasks:
- name: Create backup directory
delegate_to: localhost
file:
path: "{{ backup_root }}"
state: directory
- name: Get current configuration
ios_command:
commands: show running-config
register: config_output
- name: Save configuration to file
delegate_to: localhost
copy:
content: "{{ config_output.stdout[0] }}"
dest: "{{ backup_root }}/{{ inventory_hostname }}_{{ ansible_date_time.date }}.cfg"
- name: Compare with previous backup
delegate_to: localhost
shell: |
latest=$(ls -t {{ backup_root }}/*.cfg | head -2 | tail -1)
current={{ backup_root }}/{{ inventory_hostname }}_{{ ansible_date_time.date }}.cfg
if [ -f "$latest" ] && [ "$latest" != "$current" ]; then
diff -u "$latest" "$current" || true
fi
register: config_diff
changed_when: config_diff.stdout != ""
- name: Send notification if config changed
delegate_to: localhost
mail:
to: netops@company.com
subject: "Config change detected: {{ inventory_hostname }}"
body: "{{ config_diff.stdout }}"
when: config_diff.changed

Standardizing NTP Configuration

Ensuring all devices use the correct time servers:

playbooks/configure_ntp.yml
---
- name: Standardize NTP Configuration
hosts: all
gather_facts: no
tasks:
- name: Remove existing NTP servers
ios_config:
lines:
- no ntp server {{ item }}
loop: "{{ existing_ntp_servers | default([]) }}"
when: existing_ntp_servers is defined
- name: Configure standard NTP servers
ios_config:
lines:
- ntp server {{ item }} prefer
loop: "{{ ntp_servers }}"
- name: Set timezone
ios_config:
lines:
- clock timezone UTC 0
- name: Verify NTP synchronization
ios_command:
commands: show ntp status
register: ntp_status
- name: Report NTP status
debug:
msg: "NTP synchronized: {{ 'Clock is synchronized' in ntp_status.stdout[0] }}"

VLAN Deployment Across Multiple Switches

When adding a new VLAN to multiple devices:

playbooks/deploy_vlan.yml
---
- name: Deploy VLAN to Access Switches
hosts: access_switches
gather_facts: no
vars_prompt:
- name: vlan_id
prompt: "Enter VLAN ID"
private: no
- name: vlan_name
prompt: "Enter VLAN name"
private: no
tasks:
- name: Create VLAN
ios_vlans:
config:
- vlan_id: "{{ vlan_id | int }}"
name: "{{ vlan_name }}"
state: active
state: merged
- name: Verify VLAN creation
ios_command:
commands: "show vlan id {{ vlan_id }}"
register: vlan_check
- name: Display result
debug:
var: vlan_check.stdout_lines

Compliance Checking

Beyond configuration, Ansible can verify devices meet security standards:

playbooks/compliance_check.yml
---
- name: Security Compliance Audit
hosts: all
gather_facts: no
vars:
compliance_report: []
tasks:
- name: Check SSH version 2
ios_command:
commands: show ip ssh
register: ssh_config
- name: Verify SSHv2 enabled
set_fact:
compliance_report: "{{ compliance_report + [{'check': 'SSH Version', 'status': 'PASS' if 'SSH version 2' in ssh_config.stdout[0] else 'FAIL'}] }}"
- name: Check password encryption
ios_command:
commands: show running-config | include service password
register: password_config
- name: Verify password encryption
set_fact:
compliance_report: "{{ compliance_report + [{'check': 'Password Encryption', 'status': 'PASS' if 'service password-encryption' in password_config.stdout[0] else 'FAIL'}] }}"
- name: Check login banner
ios_command:
commands: show running-config | section banner
register: banner_config
- name: Verify login banner exists
set_fact:
compliance_report: "{{ compliance_report + [{'check': 'Login Banner', 'status': 'PASS' if banner_config.stdout[0] | length > 10 else 'FAIL'}] }}"
- name: Check unused ports disabled
ios_command:
commands: show interfaces status | include notconnect
register: unused_ports
- name: Generate compliance report
delegate_to: localhost
template:
src: compliance_report.j2
dest: "/reports/compliance/{{ inventory_hostname }}_{{ ansible_date_time.date }}.html"

Handling Different Vendors

Real networks have equipment from multiple vendors. Ansible handles this with platform-specific modules:

playbooks/multi_vendor_backup.yml
---
- name: Multi-Vendor Configuration Backup
hosts: all
gather_facts: no
tasks:
- name: Backup Cisco IOS
ios_command:
commands: show running-config
register: config
when: ansible_network_os == 'ios'
- name: Backup Cisco NX-OS
nxos_command:
commands: show running-config
register: config
when: ansible_network_os == 'nxos'
- name: Backup Juniper JunOS
junos_command:
commands: show configuration
register: config
when: ansible_network_os == 'junos'
- name: Backup Arista EOS
eos_command:
commands: show running-config
register: config
when: ansible_network_os == 'eos'
- name: Save configuration
delegate_to: localhost
copy:
content: "{{ config.stdout[0] }}"
dest: "/backup/{{ inventory_hostname }}.cfg"

Error Handling and Rollback

Network changes need careful error handling:

playbooks/safe_config_change.yml
---
- name: Safe Configuration Change with Rollback
hosts: "{{ target_device }}"
gather_facts: no
serial: 1
tasks:
- name: Backup current config
ios_command:
commands: show running-config
register: backup_config
- name: Save backup locally
delegate_to: localhost
copy:
content: "{{ backup_config.stdout[0] }}"
dest: "/tmp/{{ inventory_hostname }}_rollback.cfg"
- name: Apply configuration changes
ios_config:
src: "{{ config_template }}"
save_when: never
register: config_result
- name: Verify connectivity after change
wait_for:
host: "{{ ansible_host }}"
port: 22
timeout: 30
delegate_to: localhost
register: connectivity
ignore_errors: yes
- name: Rollback if connectivity lost
block:
- name: Wait for device recovery
wait_for:
host: "{{ ansible_host }}"
port: 22
timeout: 300
delegate_to: localhost
- name: Restore previous configuration
ios_config:
src: "/tmp/{{ inventory_hostname }}_rollback.cfg"
- name: Notify about rollback
mail:
to: netops@company.com
subject: "ROLLBACK: {{ inventory_hostname }}"
body: "Configuration change failed and was rolled back"
delegate_to: localhost
when: connectivity is failed
- name: Save configuration if successful
ios_command:
commands: write memory
when: connectivity is succeeded

Integration with CI/CD

Network changes can flow through the same CI/CD pipelines as application code:

.gitlab-ci.yml
stages:
- validate
- test
- deploy
validate_syntax:
stage: validate
script:
- ansible-playbook --syntax-check playbooks/*.yml
- ansible-lint playbooks/
test_in_lab:
stage: test
script:
- ansible-playbook -i inventory/lab playbooks/deploy_changes.yml
- ansible-playbook -i inventory/lab playbooks/run_tests.yml
environment:
name: lab
deploy_production:
stage: deploy
script:
- ansible-playbook -i inventory/production playbooks/deploy_changes.yml
environment:
name: production
when: manual
only:
- main

Lessons Learned

After implementing automation across several networks:

Start small: Begin with read-only tasks like backups and compliance checks. Build confidence before making changes.

Version control everything: Playbooks, inventory, variables — all in Git. This provides audit trail and enables code review for network changes.

Test in lab first: Even simple playbooks can have unexpected effects. A lab environment (even virtual) is essential.

Use check mode: Always run with --check first in production to see what would change.

Document the manual fallback: Automation will fail eventually. Document how to perform critical tasks manually.

Monitor playbook execution: Log all runs, track success rates, alert on failures.

The Bigger Picture

Network automation isn’t just about efficiency — it’s about treating network infrastructure with the same rigor as application code. When configurations are versioned, tested, and deployed through pipelines, networks become more reliable and easier to manage.

The shift from “network engineer who uses CLI” to “network engineer who writes code” isn’t always comfortable, but it’s increasingly necessary. The skills transfer — understanding of networking fundamentals remains essential, you’re just expressing that knowledge differently.

Start with backups. Move to compliance checking. Then tackle configuration standardization. Each step builds confidence for the next. Before long, you’ll wonder how you managed without it.