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
[core_switches]core-sw-01 ansible_host=10.0.1.1core-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.1fw-secondary ansible_host=10.0.0.2
[all:vars]ansible_network_os=iosansible_connection=network_cliansible_user=automationansible_ssh_private_key_file=~/.ssh/network_automationGroup Variables for Configuration Standards
---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:
---- 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.changedStandardizing NTP Configuration
Ensuring all devices use the correct time servers:
---- 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:
---- 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_linesCompliance Checking
Beyond configuration, Ansible can verify devices meet security standards:
---- 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:
---- 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:
---- 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 succeededIntegration with CI/CD
Network changes can flow through the same CI/CD pipelines as application code:
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: - mainLessons 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.