Network Automation
Overview
Ansible provides extensive support for network automation across major vendors including Cisco, Juniper, Arista, F5, and more. This enables infrastructure-as-code for network configurations, compliance enforcement, and automated deployments.
Cisco IOS/IOS-XE Automation
Basic Interface Configuration
---
- name: Configure Cisco IOS devices
hosts: cisco_routers
gather_facts: no
connection: network_cli
tasks:
- name: Configure interfaces
cisco.ios.ios_config:
lines:
- description Uplink to Core Switch
- ip address 192.168.1.1 255.255.255.0
- no shutdown
parents: interface GigabitEthernet0/1
- name: Configure multiple VLANs
cisco.ios.ios_vlan:
vlan_id: "{{ item.id }}"
name: "{{ item.name }}"
state: present
loop:
- { id: 10, name: 'DATA' }
- { id: 20, name: 'VOICE' }
- { id: 30, name: 'GUEST' }
- name: Save configuration
cisco.ios.ios_config:
save_when: always
Advanced Routing Configuration
---
- name: Configure OSPF routing
hosts: cisco_routers
gather_facts: no
tasks:
- name: Configure OSPF
cisco.ios.ios_config:
lines:
- router-id 1.1.1.1
- network 10.0.0.0 0.0.0.255 area 0
- network 192.168.1.0 0.0.0.255 area 0
- passive-interface default
- no passive-interface GigabitEthernet0/1
parents: router ospf 1
- name: Configure BGP
cisco.ios.ios_config:
lines:
- bgp router-id 1.1.1.1
- neighbor 10.0.0.2 remote-as 65001
- neighbor 10.0.0.2 description ISP_PRIMARY
- address-family ipv4 unicast
- network 192.168.1.0 mask 255.255.255.0
parents: router bgp 65000
Access Control Lists
---
- name: Configure ACLs
hosts: cisco_routers
tasks:
- name: Create extended ACL
cisco.ios.ios_acls:
config:
- afi: ipv4
acls:
- name: PERMIT_WEB
acl_type: extended
aces:
- sequence: 10
grant: permit
protocol: tcp
source:
any: true
destination:
host: 192.168.1.100
port_protocol:
eq: www
- sequence: 20
grant: permit
protocol: tcp
source:
any: true
destination:
host: 192.168.1.100
port_protocol:
eq: 443
- sequence: 30
grant: deny
protocol: ip
source:
any: true
destination:
any: true
- name: Apply ACL to interface
cisco.ios.ios_config:
lines:
- ip access-group PERMIT_WEB in
parents: interface GigabitEthernet0/2
Juniper Junos Automation
Interface and Routing Configuration
---
- name: Configure Juniper devices
hosts: juniper_routers
gather_facts: no
connection: netconf
tasks:
- name: Configure interfaces
junipernetworks.junos.junos_config:
lines:
- set interfaces ge-0/0/1 description "Uplink to Core"
- set interfaces ge-0/0/1 unit 0 family inet address 192.168.1.1/24
- set interfaces ge-0/0/2 description "LAN"
- set interfaces ge-0/0/2 unit 0 family inet address 10.0.0.1/24
commit: yes
- name: Configure OSPF
junipernetworks.junos.junos_config:
lines:
- set protocols ospf area 0.0.0.0 interface ge-0/0/1.0
- set protocols ospf area 0.0.0.0 interface ge-0/0/2.0
- set protocols ospf area 0.0.0.0 interface lo0.0 passive
commit: yes
- name: Configure firewall filter
junipernetworks.junos.junos_config:
lines:
- set firewall family inet filter PROTECT-RE term accept-established from protocol tcp
- set firewall family inet filter PROTECT-RE term accept-established from tcp-established
- set firewall family inet filter PROTECT-RE term accept-established then accept
- set firewall family inet filter PROTECT-RE term accept-ssh from protocol tcp
- set firewall family inet filter PROTECT-RE term accept-ssh from port ssh
- set firewall family inet filter PROTECT-RE term accept-ssh then accept
- set firewall family inet filter PROTECT-RE term default-deny then discard
commit: yes
Arista EOS Automation
VXLAN and EVPN Configuration
---
- name: Configure Arista leaf switches
hosts: arista_leaf
gather_facts: no
connection: network_cli
tasks:
- name: Configure VLANs
arista.eos.eos_vlans:
config:
- vlan_id: 10
name: WEB
- vlan_id: 20
name: APP
- vlan_id: 30
name: DB
- name: Configure VXLAN
arista.eos.eos_config:
lines:
- source-interface Loopback1
- vlan 10 vni 10010
- vlan 20 vni 10020
- vlan 30 vni 10030
parents: interface Vxlan1
- name: Configure BGP EVPN
arista.eos.eos_config:
lines:
- router-id {{ router_id }}
- neighbor SPINE peer group
- neighbor SPINE remote-as 65000
- neighbor SPINE send-community extended
- neighbor 10.0.1.1 peer group SPINE
- neighbor 10.0.1.2 peer group SPINE
- address-family evpn
- neighbor SPINE activate
parents: router bgp {{ asn }}
- name: Configure MLAG
arista.eos.eos_config:
lines:
- local-interface Vlan4094
- peer-address 192.168.0.2
- peer-link Port-Channel1
- domain-id MLAG1
parents: mlag configuration
F5 BIG-IP Automation
Load Balancer Configuration
---
- name: Configure F5 BIG-IP
hosts: f5_devices
gather_facts: no
connection: local
tasks:
- name: Create pool
f5networks.f5_modules.bigip_pool:
provider:
server: "{{ ansible_host }}"
user: "{{ bigip_user }}"
password: "{{ bigip_password }}"
validate_certs: no
name: web_pool
lb_method: round-robin
monitors:
- http
monitor_type: and_list
- name: Add pool members
f5networks.f5_modules.bigip_pool_member:
provider:
server: "{{ ansible_host }}"
user: "{{ bigip_user }}"
password: "{{ bigip_password }}"
validate_certs: no
pool: web_pool
host: "{{ item.ip }}"
port: "{{ item.port }}"
state: present
loop:
- { ip: '10.0.1.10', port: 80 }
- { ip: '10.0.1.11', port: 80 }
- { ip: '10.0.1.12', port: 80 }
- name: Create virtual server
f5networks.f5_modules.bigip_virtual_server:
provider:
server: "{{ ansible_host }}"
user: "{{ bigip_user }}"
password: "{{ bigip_password }}"
validate_certs: no
name: web_vs
destination: 203.0.113.10
port: 443
pool: web_pool
snat: automap
profiles:
- http
- clientssl
state: present
- name: Create iRule
f5networks.f5_modules.bigip_irule:
provider:
server: "{{ ansible_host }}"
user: "{{ bigip_user }}"
password: "{{ bigip_password }}"
validate_certs: no
name: redirect_to_https
content: |
when HTTP_REQUEST {
HTTP::redirect https://[HTTP::host][HTTP::uri]
}
Network Backup and Restore
Automated Configuration Backup
---
- name: Backup network device configurations
hosts: network_devices
gather_facts: no
vars:
backup_dir: "./backups/{{ inventory_hostname }}"
tasks:
- name: Create backup directory
file:
path: "{{ backup_dir }}"
state: directory
delegate_to: localhost
run_once: no
- name: Backup Cisco IOS config
cisco.ios.ios_config:
backup: yes
backup_options:
filename: "{{ inventory_hostname }}_{{ ansible_date_time.date }}.cfg"
dir_path: "{{ backup_dir }}"
when: ansible_network_os == 'ios'
- name: Backup Juniper config
junipernetworks.junos.junos_config:
backup: yes
backup_options:
filename: "{{ inventory_hostname }}_{{ ansible_date_time.date }}.conf"
dir_path: "{{ backup_dir }}"
when: ansible_network_os == 'junos'
- name: Store backup in Git
shell: |
cd {{ backup_dir }}
git add .
git commit -m "Backup {{ inventory_hostname }} - {{ ansible_date_time.iso8601 }}"
git push
delegate_to: localhost
when: backup_to_git | default(false)
Network Validation and Testing
Pre/Post Change Validation
---
- name: Network change with validation
hosts: cisco_routers
gather_facts: no
tasks:
# Pre-change validation
- name: Get pre-change state
cisco.ios.ios_command:
commands:
- show ip interface brief
- show ip route summary
- show ip bgp summary
register: pre_change_state
- name: Save pre-change state
copy:
content: "{{ pre_change_state.stdout | to_nice_json }}"
dest: "./validation/{{ inventory_hostname }}_pre.json"
delegate_to: localhost
# Make changes
- name: Apply configuration changes
cisco.ios.ios_config:
src: "configs/{{ inventory_hostname }}.cfg"
replace: block
# Post-change validation
- name: Wait for device to stabilize
wait_for:
timeout: 30
delegate_to: localhost
- name: Get post-change state
cisco.ios.ios_command:
commands:
- show ip interface brief
- show ip route summary
- show ip bgp summary
register: post_change_state
- name: Validate routing protocols
cisco.ios.ios_command:
commands:
- show ip ospf neighbor
register: ospf_neighbors
failed_when: "'FULL' not in ospf_neighbors.stdout[0]"
- name: Validate BGP peers
cisco.ios.ios_command:
commands:
- show ip bgp summary
register: bgp_summary
failed_when: "'Established' not in bgp_summary.stdout[0]"
- name: Compare states
assert:
that:
- pre_change_state.stdout[0] | regex_findall('up.*up') | length == post_change_state.stdout[0] | regex_findall('up.*up') | length
fail_msg: "Interface count changed after configuration"
success_msg: "All interfaces remain operational"
Multi-Vendor Network Inventory
# inventory/network.yml
all:
children:
cisco:
hosts:
core-sw-01:
ansible_host: 10.0.1.10
dist-sw-01:
ansible_host: 10.0.1.11
vars:
ansible_connection: network_cli
ansible_network_os: ios
ansible_become: yes
ansible_become_method: enable
juniper:
hosts:
edge-rtr-01:
ansible_host: 10.0.2.10
vars:
ansible_connection: netconf
ansible_network_os: junos
arista:
hosts:
leaf-01:
ansible_host: 10.0.3.10
leaf-02:
ansible_host: 10.0.3.11
vars:
ansible_connection: network_cli
ansible_network_os: eos
f5:
hosts:
lb-01:
ansible_host: 10.0.4.10
vars:
ansible_connection: local
vars:
ansible_user: admin
ansible_password: "{{ vault_network_password }}"
ansible_python_interpreter: /usr/bin/python3
Network Compliance and Auditing
Configuration Compliance Check
---
- name: Audit network configuration compliance
hosts: cisco_routers
gather_facts: no
vars:
compliance_rules:
- name: "NTP servers configured"
command: "show run | include ntp server"
expected: "ntp server"
- name: "Logging configured"
command: "show run | include logging"
expected: "logging host"
- name: "AAA enabled"
command: "show run | include aaa"
expected: "aaa new-model"
tasks:
- name: Run compliance checks
cisco.ios.ios_command:
commands: "{{ item.command }}"
register: compliance_result
loop: "{{ compliance_rules }}"
failed_when: item.expected not in compliance_result.stdout[0]
- name: Generate compliance report
template:
src: compliance_report.j2
dest: "./reports/{{ inventory_hostname }}_compliance.html"
delegate_to: localhost
- name: Check for forbidden commands
cisco.ios.ios_command:
commands:
- show run | include ip http server
register: http_check
failed_when: "'ip http server' in http_check.stdout[0] and 'ip http secure-server' not in http_check.stdout[0]"
Network Monitoring Integration
---
- name: Configure SNMP monitoring
hosts: network_devices
tasks:
- name: Configure SNMP on Cisco
cisco.ios.ios_config:
lines:
- snmp-server community {{ snmp_community }} RO
- snmp-server location {{ site_location }}
- snmp-server contact {{ site_contact }}
- snmp-server host {{ monitoring_server }} version 2c {{ snmp_community }}
- snmp-server enable traps snmp linkdown linkup
- snmp-server enable traps config
when: ansible_network_os == 'ios'
- name: Register device with monitoring system
uri:
url: "https://{{ monitoring_server }}/api/devices"
method: POST
body_format: json
body:
hostname: "{{ inventory_hostname }}"
ip_address: "{{ ansible_host }}"
snmp_community: "{{ snmp_community }}"
device_type: "{{ ansible_network_os }}"
status_code: 201
delegate_to: localhost
Network Automation Pipeline
Complete Change Management Workflow
---
- name: Network change management workflow
hosts: network_devices
gather_facts: no
serial: 1 # Change one device at a time
tasks:
- name: Step 1 - Create change ticket
servicenow.servicenow.snow_record:
username: "{{ snow_user }}"
password: "{{ snow_password }}"
instance: "{{ snow_instance }}"
state: present
table: change_request
data:
short_description: "Network configuration change - {{ inventory_hostname }}"
description: "Automated network configuration deployment"
assignment_group: Network Operations
register: change_ticket
delegate_to: localhost
- name: Step 2 - Backup current configuration
include_tasks: backup_config.yml
- name: Step 3 - Run pre-change validation
include_tasks: validate_network.yml
vars:
validation_type: pre
- name: Step 4 - Apply configuration
cisco.ios.ios_config:
src: "configs/{{ inventory_hostname }}.cfg"
save_when: modified
- name: Step 5 - Run post-change validation
include_tasks: validate_network.yml
vars:
validation_type: post
- name: Step 6 - Update change ticket
servicenow.servicenow.snow_record:
username: "{{ snow_user }}"
password: "{{ snow_password }}"
instance: "{{ snow_instance }}"
state: present
table: change_request
number: "{{ change_ticket.record.number }}"
data:
work_notes: "Configuration successfully applied to {{ inventory_hostname }}"
state: Review
delegate_to: localhost
rescue:
- name: Rollback on failure
cisco.ios.ios_config:
src: "{{ backup_dir }}/{{ inventory_hostname }}_latest.cfg"
replace: config
- name: Update change ticket with failure
servicenow.servicenow.snow_record:
username: "{{ snow_user }}"
password: "{{ snow_password }}"
instance: "{{ snow_instance }}"
state: present
table: change_request
number: "{{ change_ticket.record.number }}"
data:
work_notes: "Configuration failed - rolled back"
state: Failed
delegate_to: localhost
Best Practices Summary
Network Automation Best Practices
- Testing: Always test in lab environment before production deployment
- Validation: Use check mode and pre/post-change validation
- Backups: Automated configuration backups before every change
- Version Control: Store network configurations in Git
- Rollback: Implement automated rollback procedures
- Serialization: Use serial execution to limit blast radius
- Monitoring: Integrate with monitoring systems for alerting
- Compliance: Regular compliance audits against security baselines
- Documentation: Auto-generate network documentation from configs
- Change Management: Integrate with ITSM tools (ServiceNow, Jira)
- Idempotency: Ensure network playbooks are idempotent
- Multi-Vendor: Use platform-agnostic patterns where possible