Network Automation — NETCONF/YANG, Ansible, Python Netmiko, Intent-Based Networking & CI/CD
Network automation replaces manual SSH sessions to network devices with programmable, version-controlled, and testable workflows — instead of typing configure terminal on every router, you push validated configurations through NETCONF, Ansible, or Python scripts that guarantee consistency across thousands of devices.
What You’ll Learn
- NETCONF and YANG for structured network data modeling and operations
- RESTCONF for RESTful network device access
- Ansible network automation with ios_config, vyos, and junos modules
- Python automation using Netmiko and NAPALM for multi-vendor support
- Intent-based networking (IBN) concepts
- Network configuration testing with Batfish
- CI/CD pipelines with GitOps for network config management
- Monitoring integration for closed-loop remediation
Why Network Automation Matters
Manual network configuration is the leading cause of outages — a single typo in a BGP configuration or ACL rule can bring down an entire data center. Network automation eliminates human error, enforces consistent configurations, and enables rapid change at scale. Modern networks at companies like Google, Meta, and Netflix operate with fully automated change pipelines.
Doda Browser uses network automation-inspired patterns for managing its proxy configuration across multiple regions. Durga Antivirus Pro applies intent-based checking to validate that security rules are correctly enforced across distributed networks.
Learning Path
flowchart LR
A["Network Protocols"] --> B["Network Automation<br/>You are here"]
B --> C["NETCONF/YANG"]
B --> D["Ansible for Network"]
B --> E["Python Netmiko/NAPALM"]
C --> F["CI/CD & GitOps"]
D --> F
E --> F
style B fill:#f90,color:#fff
NETCONF and YANG
NETCONF is an IETF protocol (RFC 6241) for installing, manipulating, and deleting network device configurations. YANG (RFC 7950) is the data modeling language that defines the structure of NETCONF data.
NETCONF Operations
| Operation | Description |
|---|---|
<get> | Retrieve running config and state data |
<get-config> | Retrieve specified configuration datastore |
<edit-config> | Edit configuration (merge, replace, create, delete) |
<copy-config> | Copy configuration between datastores |
<delete-config> | Delete a configuration datastore |
<lock> / <unlock> | Lock configuration to prevent concurrent changes |
<commit> | Commit candidate configuration to running |
<discard-changes> | Discard uncommitted candidate changes |
YANG Data Model Example
module example-vlan {
namespace "urn:example:vlan";
prefix vlan;
container vlans {
list vlan {
key "vlan-id";
leaf vlan-id {
type uint16 {
range "1..4094";
}
}
leaf name {
type string;
}
leaf status {
type enumeration {
enum active;
enum suspended;
}
default active;
}
}
}
}NETCONF RPC Example
<!-- Get all VLANs from device -->
<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<vlans xmlns="urn:example:vlan">
<vlan>
<vlan-id/>
<name/>
<status/>
</vlan>
</vlans>
</filter>
</get>
</rpc>RESTCONF
RESTCONF (RFC 8040) provides a RESTful HTTP interface to NETCONF datastores. Use standard HTTP methods to manipulate YANG-defined data.
GET /restconf/data/example-vlan:vlans HTTP/1.1
Host: 192.168.1.1
Accept: application/yang-data+json
POST /restconf/data/example-vlan:vlans HTTP/1.1
Content-Type: application/yang-data+json
{
"vlan": [
{
"vlan-id": 100,
"name": "users",
"status": "active"
}
]
}import requests
import json
url = "https://192.168.1.1/restconf/data/example-vlan:vlans"
headers = {
"Accept": "application/yang-data+json",
"Content-Type": "application/yang-data+json"
}
auth = ("admin", "password")
response = requests.get(url, headers=headers, auth=auth, verify=False)
print(json.dumps(response.json(), indent=2))Expected output:
{
"example-vlan:vlans": {
"vlan": [
{"vlan-id": 100, "name": "users", "status": "active"},
{"vlan-id": 200, "name": "servers", "status": "active"}
]
}
}Ansible for Network Automation
Ansible is agentless — it connects to network devices via SSH or API and pushes configuration using specialized network modules.
Inventory File
# inventory/networks.yml
routers:
hosts:
router01:
ansible_host: 192.168.1.1
ansible_network_os: ios
router02:
ansible_host: 192.168.1.2
ansible_network_os: vyos
switches:
hosts:
switch01:
ansible_host: 192.168.1.10
ansible_network_os: junosPlaybook — Cisco IOS Configuration
---
- name: Configure Cisco VLANs
hosts: routers
gather_facts: false
tasks:
- name: Configure VLAN 100
cisco.ios.ios_config:
lines:
- vlan 100
- name users
parents: "interface GigabitEthernet0/1"
- name: Configure OSPF
cisco.ios.ios_config:
lines:
- router ospf 1
- network 10.0.0.0 0.255.255.255 area 0
save_when: modified
- name: Verify configuration
cisco.ios.ios_command:
commands:
- show ip ospf neighbor
- show vlan brief
register: output
- name: Display OSPF neighbors
debug:
var: output.stdout_linesExpected output:
TASK [Display OSPF neighbors] **********************
ok: [router01] => {
"output.stdout_lines": [
["Neighbor ID Pri State"],
["10.0.0.2 1 FULL/BDR"],
["10.0.0.3 1 FULL/DR"]
]
}Playbook — VyOS Configuration
- name: Configure VyOS router
hosts: router02
tasks:
- name: Set interface address
vyos.vyos.vyos_config:
lines:
- set interfaces ethernet eth0 address 10.0.1.1/24
- set service ssh port 22
- name: Configure BGP
vyos.vyos.vyos_config:
src: bgp_config.j2Playbook — Juniper Configuration
- name: Configure Juniper switch
hosts: switches
tasks:
- name: Configure interfaces
junipernetworks.junos.junos_config:
lines:
- set interfaces ge-0/0/0 unit 0 family inet address 10.0.0.1/24
- set vlans users vlan-id 100
comment: "Automated config push via Ansible"Python Network Automation with Netmiko
Netmiko simplifies SSH connections to network devices. It handles authentication, SSH keys, and output parsing consistently across vendors.
from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoTimeoutException, NetmikoAuthenticationException
device = {
"device_type": "cisco_ios",
"host": "192.168.1.1",
"username": "admin",
"password": "password",
"port": 22,
}
try:
connection = ConnectHandler(**device)
connection.enable()
# Send commands
output = connection.send_command("show ip interface brief")
print("=== Interface Brief ===")
print(output)
# Configure VLAN
config_commands = [
"vlan 100",
"name users",
"interface GigabitEthernet0/1",
"switchport access vlan 100",
]
output = connection.send_config_set(config_commands)
print("\n=== Config Output ===")
print(output)
# Save config
connection.save_config()
connection.disconnect()
except NetmikoTimeoutException:
print("ERROR: Device unreachable")
except NetmikoAuthenticationException:
print("ERROR: Authentication failed")Expected output:
=== Interface Brief ===
Interface IP-Address OK? Method Status
GigabitEthernet0/0 192.168.1.1 YES NVRAM up
GigabitEthernet0/1 unassigned YES NVRAM up
=== Config Output ===
config term
Enter configuration commands, one per line.
router(config)#vlan 100
router(config-vlan)#name users
router(config-vlan)#interface GigabitEthernet0/1
router(config-if)#switchport access vlan 100NAPALM — Multi-Vendor Abstraction
NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) provides a unified API across vendors.
from napalm import get_network_driver
driver = get_network_driver("ios")
device = driver("192.168.1.1", "admin", "password")
device.open()
# Get facts
facts = device.get_facts()
print(f"Model: {facts['model']}")
print(f"Serial: {facts['serial_number']}")
print(f"OS Version: {facts['os_version']}")
# Get interfaces
interfaces = device.get_interfaces()
for iface, details in interfaces.items():
print(f"{iface}: {'UP' if details['is_up'] else 'DOWN'} "
f"({details['mac_address']})")
# Get BGP neighbors
bgp = device.get_bgp_neighbors()
print(f"\nBGP Neighbors: {len(bgp['global']['peers'])}")
device.close()Intent-Based Networking
IBN means you declare what the network should do, not how to configure it.
# intent-policy.yaml
policy_name: "isolate-pci-segment"
description: "All PCI traffic must be isolated from general traffic"
rules:
- match:
source_tags: ["pci-server"]
destination: "any"
action: "permit"
constraints:
- vrf: "pci-vrf"
- encrypted: true
- match:
source: "any"
destination_tags: ["pci-server"]
action: "deny"
unless:
- source_tags: ["pci-app"]
- protocol: "https"IBN tools continuously validate that the actual network state matches the intent policy.
Testing with Batfish
Batfish statically analyzes network configurations to detect errors before deployment — no live devices needed.
from pybatfish.client.session import Session
bf = Session(host="localhost")
bf.set_network("production")
bf.set_snapshot("snapshots/latest")
# Check for BGP session issues
bgp_issues = bf.q.bgpSessionStatus().answer().frame()
print("BGP sessions not established:")
print(bgp_issues[bgp_issues["Status"] != "ESTABLISHED"])
# Detect unreachable routes
unreachable = bf.q.routes().answer().frame()
unreachable = unreachable[unreachable["Ambiguous"]]
print(f"\nAmbiguous routes: {len(unreachable)}")
# Check ACL reachability
acl_result = bf.q.searchFilters(
path="acl_line",
filters="deny any any"
).answer().frame()Expected output:
BGP sessions not established:
Node Interface VRF Status
0 router01 GigabitEthernet0/1 default IDLE
Ambiguous routes: 0CI/CD for Network Configs (GitOps)
Network GitOps treats configuration as code — all changes go through pull requests with automated validation.
# .github/workflows/network-ci.yml
name: Network Config Validation
on:
pull_request:
paths:
- 'configs/**'
- 'ansible/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Syntax check Ansible playbooks
run: ansible-playbook --syntax-check site.yml
- name: Validate with Batfish
run: |
docker run --rm -d -p 9997:9997 batfish/batfish
python scripts/batfish_validate.py
- name: Validate YANG models
run: |
pip install pyang
pyang --lint models/*.yang
- name: Check VLAN consistency
run: python scripts/check_vlan_overlap.pyMonitoring Integration
Network automation closes the loop — monitoring detects issues, automation remediates them.
# Simulate closed-loop remediation
def check_and_fix_bgp(device_ip):
from netmiko import ConnectHandler
device = {"device_type": "cisco_ios", "host": device_ip,
"username": "monitor", "password": "secret"}
conn = ConnectHandler(**device)
bgp_status = conn.send_command("show ip bgp summary")
if "down" in bgp_status.lower():
print(f"⚠ BGP neighbor down on {device_ip}, attempting reset...")
conn.send_command("clear ip bgp *")
print(f"✓ BGP reset on {device_ip}")
# Log to monitoring system
return {"action": "bgp_reset", "device": device_ip, "status": "completed"}
return {"action": "none", "device": device_ip, "status": "healthy"}
result = check_and_fix_bgp("192.168.1.1")
print(f"Remediation: {result['action']}")Common Errors
1. NETCONF connection refused — port 830
The device doesn’t have NETCONF enabled. Enable it: netconf-yang set server enable or ip ssh netconf depending on vendor.
2. YANG model not installed on device
You defined a model but the device doesn’t support it. Check with show yang models | include example-vlan. You may need to install the model via RPM/JAM package.
3. Ansible “invalid module” for network operations
The cisco.ios.ios_config module requires the cisco.ios collection. Install it: ansible-galaxy collection install cisco.ios. Similar for vyos.vyos and junipernetworks.junos.
4. Netmiko authentication failure with SSH key
Netmiko defaults to password auth. Use use_keys=True and provide key_file= for SSH key authentication.
5. Batfish snapshot parsing errors
Config files must be in the correct directory structure: snapshots/<name>/configs/. Use the correct filename convention (e.g., router01.cfg).
6. CI/CD pipeline push causes outage
Automated pushes can break routing. Implement pre-checks (ping tests, BGP status checks) before applying config and post-checks after. Use canary deployments.
7. RESTCONF HTTPS certificate validation
Self-signed certificates on lab devices cause SSL errors. Use requests.packages.urllib3.disable_warnings() in dev, but never skip verification in production.
Practice Questions
What is the difference between NETCONF and RESTCONF? NETCONF uses XML-based RPCs over SSH port 830 with transaction support (candidate/commit). RESTCONF uses HTTP methods (GET/POST/PUT/DELETE) over HTTPS port 443 with JSON or XML and is simpler to use from web applications.
Why use NAPALM instead of direct Netmiko? NAPALM provides a vendor-agnostic API — the same
get_facts()call works on Cisco, Juniper, Arista, and more. Netmiko is vendor-specific but gives more granular control over SSH sessions.How does Batfish help prevent network outages? Batfish analyzes configuration files offline to detect issues like BGP session mismatches, unreachable routes, ACL gaps, and VLAN conflicts before any change is deployed.
What is intent-based networking? IBN replaces imperative configuration (set this IP, enable OSPF) with declarative policies (this segment must be isolated and encrypted). The network translates intent into device configs and continuously validates compliance.
How does GitOps apply to network automation? Network configs are stored in Git. Changes require pull requests with automated validation (syntax check, Batfish analysis, VLAN overlap check). Merged changes are automatically deployed to devices.
Challenge: Design a GitOps-based network automation pipeline for a three-tier data center network (spine, leaf, border). Include: (1) directory structure for configs per device type, (2) Ansible playbook structure for each layer, (3) Batfish validation step, (4) GitHub Actions workflow with pre-post deployment checks, (5) rollback strategy if connectivity is lost after deployment.
FAQ
Try It Yourself
Simulate a multi-vendor network inventory check with Python:
# Simulate network device inventory automation
devices = [
{"hostname": "core-01", "vendor": "cisco", "model": "ASR1001", "os": "IOS-XE 17.3"},
{"hostname": "edge-01", "vendor": "juniper", "model": "MX204", "os": "Junos 21.2"},
{"hostname": "spine-01", "vendor": "arista", "model": "DCS-7280", "os": "EOS 4.28"},
]
def check_version(device):
os_min_versions = {
"cisco": "17.0",
"juniper": "20.0",
"arista": "4.25",
}
min_ver = os_min_versions.get(device["vendor"], "0.0")
device_ver = device["os"].split(" ")[-1]
compliant = device_ver >= min_ver
return compliant, min_ver
print("Network Device Inventory Report")
print("=" * 50)
for device in devices:
compliant, min_ver = check_version(device)
status = "✓ COMPLIANT" if compliant else "✗ NON-COMPLIANT"
print(f"{device['hostname']:15} {device['vendor']:10} "
f"v{device['os'].split()[-1]:8} {status}")Expected output:
Network Device Inventory Report
==================================================
core-01 cisco v17.3 ✓ COMPLIANT
edge-01 juniper v21.2 ✓ COMPLIANT
spine-01 arista v4.28 ✓ COMPLIANTWhat’s Next
| Tutorial | What You’ll Learn |
|---|---|
| VoIP Basics Guide | Voice network fundamentals |
| RESTful APIs | API design patterns for network automation |
| Docker Fundamentals | Containerized network tools |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro