LAB 1 — Network Device Automation with SSH and Netmiko
Readings and Videos to Prepare
These readings and videos will introduce you to important concepts and details which will help you complete this lab activity:
| You do not need to follow-along or complete any activities from these resources but you should watch/read them to prepare for the remainder of this lab. |
Learning Objectives
By the end of this lab, you will:
-
Understand how SSH can be used for programmatic device configuration
-
Install and use the Netmiko Python library for network automation
-
Connect to Cisco IOS devices via SSH using Python
-
Send configuration commands to network devices programmatically
-
Retrieve show command output and parse it in Python
-
Handle multiple device connections efficiently
-
Use Python loops to configure multiple devices
-
Implement error handling for network automation scripts
-
Save and backup device configurations programmatically
-
Compare manual CLI configuration to automated Python scripts
Purpose
|
Foundation for the Automation Series: This lab introduces network automation by leveraging what you already know—the Cisco CLI. Instead of typing commands manually, you’ll write Python scripts to send those same commands via SSH. Lab Progression:
|
You already know how to configure Cisco routers and switches via CLI from your CCNA training. This lab teaches you to automate those same configurations using Python and SSH instead of typing commands manually.
Why automate with SSH?
-
Configure dozens of devices in the time it takes to configure one manually
-
Ensure consistency—same configuration applied identically to all devices
-
Reduce human error—no typos, no forgotten commands
-
Save time on repetitive tasks
-
Create audit trails—scripts document what was configured
-
Works with any device that supports SSH (universal compatibility)
Real-world scenarios you’ll learn:
-
Configure hostname, domain, and user accounts across multiple devices
-
Deploy interface configurations (IP addresses, descriptions)
-
Backup configurations from all devices automatically
-
Verify configurations programmatically (show commands)
-
Apply security policies (SSH, disable telnet, password policies)
-
Perform operational tasks like ping tests and TFTP file transfers
SSH/Netmiko Automation: Advantages and Trade-offs
|
Why Start with SSH Automation? SSH/Netmiko is the most accessible entry point to network automation because you’re using the exact same CLI commands you already know from CCNA training. There’s no new syntax to learn for device interaction—just Python to send those familiar commands. |
Advantages of SSH/Netmiko Automation
1. Leverages Existing CLI Knowledge
-
Zero learning curve for device commands — You already know
interface GigabitEthernet0/0/1,ip address,show ip interface brief -
Same commands you type manually, now sent via Python
-
No need to learn new APIs, data formats, or protocols (yet!)
-
Immediate productivity for network engineers with CCNA background
2. Universal Device Compatibility
-
Works with any device that supports SSH — old, new, any vendor
-
Doesn’t require modern IOS-XE features (RESTCONF/NETCONF support)
-
Works with legacy devices that will never have API support
-
Single approach works across Cisco, Juniper, Arista, HP, and more (with appropriate
device_type)
3. Can Automate ANY CLI Command
-
Not limited to configuration — can automate operational commands too:
-
ping 8.8.8.8— Test network connectivity programmatically -
copy tftp:— Download IOS images or config files from TFTP/FTP servers -
traceroute— Troubleshoot routing issues -
show tech-support— Collect diagnostic data -
Interactive commands like password changes, file deletions, reloads
-
-
RESTCONF/NETCONF focus on configuration data, not operational commands
-
Some operations (like TFTP transfers, pings, reload commands) have no API equivalent
4. Troubleshooting and Debugging
-
Output looks exactly like manual CLI — easy to read and understand
-
Can copy commands from scripts and paste into CLI for verification
-
Familiar error messages
-
Easy to test commands manually before scripting
5. Quick Prototyping
-
Fastest way to automate existing procedures
-
Take your documented CLI procedures and wrap them in Python loops
-
Minimal code changes needed to scale from 1 device to 100 devices
Disadvantages and Limitations of SSH/Netmiko
1. Fragile Text Parsing (MAJOR ISSUE)
-
Output format changes break automation — This is the biggest drawback
-
Example of brittleness:
# IOS Version 15.x output: GigabitEthernet0/0/1 10.10.20.10 YES manual up up # IOS Version 17.x might show: GigabitEthernet0/0/1 10.10.20.10 YES manual up up # Your parsing script that expects 'up up' breaks! -
Even minor IOS upgrades can break your scripts
-
Cisco might add columns, reorder fields, change spacing in any IOS update
-
Commands you’ve relied on for years can suddenly format differently
2. Unstructured Data Requires Complex Parsing
-
show ip interface briefreturns text, not structured data -
Must write parsing logic: split lines, extract columns, handle edge cases
-
Different commands have different formats — no consistent structure
-
Error-prone: What if an interface name is unexpectedly long? What if output wraps?
3. No Data Validation
-
Devices accept any command syntax — no schema validation
-
Typos in commands might be accepted but do nothing:
ip adressvsip address -
Must verify every change worked (round-trip verification required)
-
No built-in rollback capability
4. Limited Error Handling
-
Error messages are text strings:
% Invalid input detected at '^' marker. -
Must parse error text to understand what went wrong
-
Different error messages for same problem across IOS versions
-
Hard to programmatically distinguish error types
5. No Transactional Support
-
Changes apply immediately — no "test first, then commit" workflow
-
If script fails halfway through, device is in partial config state
-
Must manually back out changes if something goes wrong
-
No built-in rollback like NETCONF candidate datastore
6. Sequential Execution Performance
-
Commands execute one at a time over SSH
-
Slow for large data retrieval (full running configs on many devices)
-
Each command has network round-trip latency
-
Can parallelize with threading, but adds complexity
When to Use SSH/Netmiko (vs RESTCONF/NETCONF)
Choose SSH/Netmiko when:
-
Working with older/legacy devices without API support
-
Need to automate operational commands (ping, traceroute, TFTP)
-
Team is comfortable with CLI but not ready for APIs
-
Quick one-off tasks or emergency fixes
-
Devices from multiple vendors (universal approach)
-
Prototyping automation before investing in API approach
Choose RESTCONF/NETCONF (Labs 3-4) when:
-
Devices support these protocols (modern IOS-XE, NX-OS, IOS-XR)
-
Need structured, parsable data (JSON/XML) instead of text
-
Want resilience to IOS version changes (YANG models provide consistency)
-
Need transactional/rollback capability
-
Building long-term, maintainable automation
-
Require data validation before applying configs
|
Best Practice for Production: Many organizations use a hybrid approach:
You’ll explore this hybrid strategy in Lab 5! |
Prerequisites
You should be comfortable with:
-
CCNA-level Cisco IOS CLI: Router and switch configuration,
showcommands, configuration modes -
Basic Python: Variables, strings, lists, loops, conditionals, functions (Python Essentials 1 level)
-
SSH concepts: How SSH authentication works, SSH port 22
-
Basic networking: IP addressing, default gateways, VLANs
You will need:
-
Python 3.7 or later installed on your workstation
-
Console access to Cisco 4331 routers and/or 3650 switches
-
Network connectivity to device management interfaces
-
Understanding of
enable,configure terminal, and configuration commands
|
Python Skills Required: This is the first Python automation lab. We assume you’ve completed Python Essentials 1 or equivalent. You should know:
If you need a Python refresher, review Python Essentials 1 content before proceeding. |
Lab Environment & Device Access
For this lab, you will work with live Cisco network devices:
-
Cisco 4331 Routers — You have access to at least 2 routers
-
Cisco 3650 Switches — You have access to at least 2 switches
Initial Setup:
All devices start with basic connectivity configured. You’ll connect via console initially to set up SSH access, then switch to SSH automation.
Network Topology (Example):
Management Network: 10.10.20.0/24
[Your Workstation]
10.10.20.5/24
|
[Network]
|
+---+---+---+---+
| | | | |
[R1] [R2] [SW1][SW2]
.10 .20 .30 .40
Device Addressing (adjust based on your lab):
-
R1: GigabitEthernet0/0/1 -
10.10.20.10/24 -
R2: GigabitEthernet0/0/1 -
10.10.20.20/24 -
SW1: VLAN 1 -
10.10.20.30/24 -
SW2: VLAN 1 -
10.10.20.40/24 -
Your Workstation:
10.10.20.5/24 -
Default Gateway:
10.10.20.1(provided by lab infrastructure)
Section 1 — Prepare Devices for SSH Access
Before Python automation can work, devices must have SSH enabled and be reachable via network.
|
CCNA Review: These are standard commands you learned in CCNA. The difference is that after this initial setup, you’ll automate everything else via Python instead of CLI. |
Step 1: Console Connection and Basic Setup
Connect to each device via console cable. Configure basic SSH requirements.
Router Configuration Example (R1)
! Console in and enter privileged mode
Router> enable
Router# configure terminal
! Set hostname
Router(config)# hostname R1
! Configure domain name (required for SSH key generation)
Router(config)# ip domain-name lab.local
! Disable DNS lookups (prevents CLI delays on typos)
Router(config)# no ip domain-lookup
! Create local user account for SSH authentication
! Username: netadmin, Privilege: 15 (full access), Password: NetAuto123!
Router(config)# username netadmin privilege 15 secret NetAuto123!
! Generate RSA keys for SSH (2048-bit keys recommended)
Router(config)# crypto key generate rsa modulus 2048
! Type "yes" when prompted about replacing existing keys
! Enable SSH version 2 only (more secure)
Router(config)# ip ssh version 2
! Configure VTY lines for SSH access
Router(config)# line vty 0 15
Router(config-line)# transport input ssh ! SSH only, disable Telnet
Router(config-line)# login local ! Use local username/password
Router(config-line)# exec-timeout 30 0 ! 30-minute timeout
Router(config-line)# exit
! Configure management interface with IP address
Router(config)# interface GigabitEthernet0/0/1
Router(config-if)# description Management Interface
Router(config-if)# ip address 10.10.20.10 255.255.255.0
Router(config-if)# no shutdown
Router(config-if)# exit
! Set default route for management traffic (if needed)
Router(config)# ip route 0.0.0.0 0.0.0.0 10.10.20.1
! Save configuration
Router(config)# end
Router# write memory
Verify SSH is working:
R1# show ip ssh
SSH Enabled - version 2.0
Authentication timeout: 120 secs; Authentication retries: 3
R1# show ip interface brief | include GigabitEthernet0/0/1
GigabitEthernet0/0/1 10.10.20.10 YES manual up up
Switch Configuration Example (SW1)
! Console in and configure similar to router
Switch> enable
Switch# configure terminal
Switch(config)# hostname SW1
! Basic SSH configuration (same as router)
Switch(config)# ip domain-name lab.local
Switch(config)# no ip domain-lookup
Switch(config)# username netadmin privilege 15 secret NetAuto123!
Switch(config)# crypto key generate rsa modulus 2048
Switch(config)# ip ssh version 2
! VTY lines
Switch(config)# line vty 0 15
Switch(config-line)# transport input ssh
Switch(config-line)# login local
Switch(config-line)# exec-timeout 30 0
Switch(config-line)# exit
! Configure management VLAN interface (VLAN 1 by default)
Switch(config)# interface vlan 1
Switch(config-if)# description Management Interface
Switch(config-if)# ip address 10.10.20.30 255.255.255.0
Switch(config-if)# no shutdown
Switch(config-if)# exit
! Default gateway (switches need this to reach other networks)
Switch(config)# ip default-gateway 10.10.20.1
! Save configuration
Switch(config)# end
Switch# write memory
Step 2: Test SSH Access Manually
From your workstation, test SSH connection before trying Python:
# Test SSH to R1
ssh netadmin@10.10.20.10
# Enter password: NetAuto123!
# You should reach R1 CLI prompt: R1>
Type exit to disconnect.
Repeat for all devices (R2, SW1, SW2) to verify SSH is working.
|
Troubleshooting SSH Issues:
|
Step 3: Install Netmiko on Your Workstation
Netmiko is a Python library that simplifies SSH connections to network devices. It handles SSH session management, expects prompts, and simplifies sending commands.
Install Netmiko:
pip install netmiko
Verify installation:
python -c "import netmiko; print(netmiko.__version__)"
Expected output: 4.x.x (version number)
|
What is Netmiko? Netmiko is built on top of Paramiko (Python SSH library) and is specifically designed for network devices. It:
Documentation: https://github.com/ktbyers/netmiko |
Step 4: Create Device Inventory File
Create a Python file to store device connection information:
# device_inventory.py
# Device connection parameters for SSH automation
# Dictionary of devices with connection details
devices = {
'R1': {
'device_type': 'cisco_ios', # Netmiko device type
'host': '10.10.20.10', # Management IP address
'username': 'netadmin', # SSH username
'password': 'NetAuto123!', # SSH password
'secret': 'NetAuto123!', # Enable password (if different)
'port': 22, # SSH port
},
'R2': {
'device_type': 'cisco_ios',
'host': '10.10.20.20',
'username': 'netadmin',
'password': 'NetAuto123!',
'secret': 'NetAuto123!',
'port': 22,
},
'SW1': {
'device_type': 'cisco_ios',
'host': '10.10.20.30',
'username': 'netadmin',
'password': 'NetAuto123!',
'secret': 'NetAuto123!',
'port': 22,
},
'SW2': {
'device_type': 'cisco_ios',
'host': '10.10.20.40',
'username': 'netadmin',
'password': 'NetAuto123!',
'secret': 'NetAuto123!',
'port': 22,
}
}
# Convenience lists for iteration
routers = ['R1', 'R2']
switches = ['SW1', 'SW2']
all_devices = ['R1', 'R2', 'SW1', 'SW2']
Save this file as device_inventory.py in your working directory.
Step 5: Test Netmiko Connection
Create a simple script to test SSH connectivity via Netmiko:
# test_netmiko_connection.py
# Test Netmiko SSH connection to R1
from netmiko import ConnectHandler
from device_inventory import devices
# Select device to test
device = devices['R1']
# Connect to device via SSH
print(f"Connecting to {device['host']}...")
try:
# ConnectHandler establishes SSH connection
connection = ConnectHandler(**device)
print("Connected successfully!")
# Get the device prompt (hostname)
prompt = connection.find_prompt()
print(f"Device prompt: {prompt}")
# Send a simple show command
output = connection.send_command("show version | include Software")
print(f"\nDevice Software Version:")
print(output)
# Disconnect
connection.disconnect()
print("\nDisconnected")
except Exception as e:
print(f"Connection failed: {e}")
Run the script:
python test_netmiko_connection.py
Expected output:
Connecting to 10.10.20.10...
Connected successfully!
Device prompt: R1#
Device Software Version:
Cisco IOS Software, ISR Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.3.4...
Disconnected
|
Understanding the Script:
The
|
Section 1 Complete! Your devices are accessible via SSH and Netmiko is working. You’re ready to start automating!
Section 2 — Retrieving Show Command Output
Now you’ll use Netmiko to retrieve information from devices—the same information you’d get from show commands, but captured in Python for processing.
|
Connection to Later Labs: In Lab 3 (RESTCONF) and Lab 4 (NETCONF), you’ll retrieve this same data as structured JSON/XML. Here, you’re getting text output that requires parsing. This is the simplest approach but requires more work to extract specific fields. |
2.1 Basic Show Commands
# show_interface_status.py
# Retrieve interface status from R1
from netmiko import ConnectHandler
from device_inventory import devices
# Connect to R1
device = devices['R1']
connection = ConnectHandler(**device)
print(f"Connected to {connection.find_prompt()}\n")
# Send show command and capture output
output = connection.send_command("show ip interface brief")
# Print the output
print("Interface Status:")
print("=" * 80)
print(output)
print("=" * 80)
# Disconnect
connection.disconnect()
Run the script:
python show_interface_status.py
Expected output:
Connected to R1#
Interface Status:
================================================================================
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0/0 unassigned YES unset administratively down down
GigabitEthernet0/0/1 10.10.20.10 YES manual up up
GigabitEthernet0/1/0 unassigned YES unset administratively down down
...
================================================================================
|
Text Parsing Fragility Demonstrated Notice the output above is just unstructured text. If you wanted to extract just the IP addresses programmatically, you’d need to parse this by:
The critical problem: This output format is NOT guaranteed to stay consistent across IOS versions. Real-world example of SSH automation breaking:
What happens after IOS upgrade to 17.x:
This is why Labs 3-4 teach RESTCONF and NETCONF, which use YANG data models that provide consistent structure even across IOS versions. But for one-off tasks or legacy devices, text parsing is still useful—just understand the maintenance cost! Mitigation strategies for this lab:
|
2.2 Multiple Show Commands
Try running this script to obtain information from multiple show commands all at once.
# collect_device_info.py
# Collect multiple pieces of information from R1
from netmiko import ConnectHandler
from device_inventory import devices
from datetime import datetime
# Connect to device
device = devices['R1']
connection = ConnectHandler(**device)
print(f"Device Information Report: {connection.find_prompt()}")
print(f"Collected: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 80)
# List of show commands to run
commands = [
"show version | include Software",
"show ip interface brief",
"show ip route | include Gateway",
"show running-config | include hostname"
]
# Execute each command and display results
for command in commands:
print(f"\n### Command: {command}")
print("-" * 80)
output = connection.send_command(command)
print(output)
print("-" * 80)
# Disconnect
connection.disconnect()
print("\nReport complete")
2.3 Saving Output to File
Try tunning this command to same the output of the running-config to a local file on your management host.
# save_show_output.py
# Save show command output to a file for later analysis
from netmiko import ConnectHandler
from device_inventory import devices
from datetime import datetime
device = devices['R1']
connection = ConnectHandler(**device)
# Get running configuration
print("Retrieving running configuration...")
config_output = connection.send_command("show running-config")
# Create filename with timestamp
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"R1_running_config_{timestamp}.txt"
# Save to file
with open(filename, 'w') as file:
file.write(f"Configuration Backup for R1\n")
file.write(f"Collected: {datetime.now()}\n")
file.write("=" * 80 + "\n\n")
file.write(config_output)
print(f"Configuration saved to: {filename}")
connection.disconnect()
|
Why save output to files?
|
2.4 Collecting from Multiple Devices
Try running this script to collect information from all the network devices instead of just from one.
# collect_from_all_devices.py
# Collect interface status from all devices
from netmiko import ConnectHandler
from device_inventory import devices, all_devices
print("Collecting interface status from all devices...")
print("=" * 80)
# Loop through each device
for device_name in all_devices:
print(f"\n### {device_name} ###")
# Get device connection parameters
device = devices[device_name]
try:
# Connect to device
connection = ConnectHandler(**device)
# Get interface status
output = connection.send_command("show ip interface brief")
print(output)
# Disconnect
connection.disconnect()
except Exception as e:
print(f"Error connecting to {device_name}: {e}")
continue # Continue to next device even if one fails
print("\n" + "=" * 80)
print("Collection complete")
Key concept: The loop connects to each device sequentially, executes the command, and moves to the next. This is the foundation for multi-device automation.
2.5 Practice Exercises
Write scripts to:
-
Retrieve VLAN information from both switches:
-
Command:
show vlan brief -
Print output from SW1 and SW2
-
Compare: Are VLANs consistent across switches?
-
-
Check routing table on both routers:
-
Command:
show ip route -
Save output to separate files per router
-
Identify the default gateway on each
-
-
Interface error checking:
-
Command:
show interfaces | include errors -
Identify any interfaces with errors
-
Print only lines containing non-zero error counts
-
Section 3 — Sending Configuration Commands
Now you’ll send configuration commands to devices via Python—automating what you’d normally type in configuration mode.
|
Comparison to Later Labs: Here, you’re sending CLI commands as strings. In Labs 3-4 (RESTCONF/NETCONF), you’ll send structured data (JSON/XML) based on YANG models. CLI commands are simpler to start with because you already know them from CCNA. |
3.1 Single Configuration Command
Try running this script to set the interface description on G0/0/0 of R1.
# configure_description.py
# Add description to an interface on R1
from netmiko import ConnectHandler
from device_inventory import devices
# Connect to R1
device = devices['R1']
connection = ConnectHandler(**device)
print(f"Connected to {connection.find_prompt()}")
# Configuration commands as a list
config_commands = [
'interface GigabitEthernet0/0/0',
'description Configured by Python Script',
'exit'
]
print(f"\nSending configuration commands...")
print(f"Commands: {config_commands}")
# send_config_set() enters config mode, sends commands, exits config mode
output = connection.send_config_set(config_commands)
print("\nDevice Response:")
print(output)
# Verify the change
print("\nVerifying configuration...")
verify_output = connection.send_command("show running-config interface GigabitEthernet0/0/0")
print(verify_output)
# Save configuration
print("\nSaving configuration...")
save_output = connection.send_command("write memory")
print(save_output)
connection.disconnect()
print("\n Configuration complete")
Run and observe:
python configure_description.py
Then verify on device CLI:
R1# show running-config interface GigabitEthernet0/0/0
Building configuration...
Current configuration : 123 bytes
!
interface GigabitEthernet0/0/0
description Configured by Python Script
no ip address
shutdown
end
|
Understanding send_config_set():
This is much simpler than manually sending |
3.2 Configuring IP Addresses
Try running this scrtipt to create a new interface on R1, Loopback99, and set an IP address and description on the interface.
# configure_loopback.py
# Create loopback interface with IP address on R1
from netmiko import ConnectHandler
from device_inventory import devices
device = devices['R1']
connection = ConnectHandler(**device)
print("Creating Loopback99 on R1...")
# Commands to create loopback and assign IP
config_commands = [
'interface Loopback99',
'description Created via Netmiko Python',
'ip address 192.0.2.99 255.255.255.0',
'no shutdown',
'exit'
]
# Send configuration
output = connection.send_config_set(config_commands)
print("Configuration applied:")
print(output)
# Verify interface was created
print("\nVerifying interface...")
verify = connection.send_command("show ip interface brief | include Loopback99")
print(verify)
# Save config
connection.send_command("write memory")
connection.disconnect()
print("\nLoopback99 created successfully")
Expected output:
Creating Loopback99 on R1...
Configuration applied:
configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
R1(config)#interface Loopback99
R1(config-if)#description Created via Netmiko Python
R1(config-if)#ip address 192.0.2.99 255.255.255.0
R1(config-if)#no shutdown
R1(config-if)#exit
R1(config)#end
R1#
Verifying interface...
Loopback99 192.0.2.99 YES manual up up
Loopback99 created successfully
A script like this is not actually verifying that Loopback99 was created successfully. It is just sending the configuration commands and then providing the output of the show ip interface brief | include Loopback99 command which the user needs to manually check. In order to actually verify the interface was created and working properly you would need to parse the response from the show ip interface brief | include Loopback99 and verify it looks correct. Of course, parsing the response is one of the most fragile parts of the automation which is a key disadvantage of the Netmiko method of automation.
|
3.3 Bulk Configuration Across Multiple Devices
Try running this script to update some setting across all devices.
# configure_all_devices.py
# Apply consistent configuration to all devices
from netmiko import ConnectHandler
from device_inventory import devices, all_devices
# Configuration to apply to ALL devices
# These commands work on both routers and switches
common_config = [
'banner login # Authorized Access Only - Managed by Automation #',
'service timestamps debug datetime msec',
'service timestamps log datetime msec',
'no ip domain-lookup'
]
print("Applying common configuration to all devices...")
print(f"Configuration commands: {common_config}\n")
print("=" * 80)
# Track success/failure
results = {'success': [], 'failed': []}
# Loop through all devices
for device_name in all_devices:
print(f"\n### Configuring {device_name} ###")
device = devices[device_name]
try:
# Connect
connection = ConnectHandler(**device)
print(f"Connected to {connection.find_prompt()}")
# Send configuration
output = connection.send_config_set(common_config)
print("Configuration applied")
# Save configuration
connection.send_command("write memory")
print("Configuration saved")
# Disconnect
connection.disconnect()
# Track success
results['success'].append(device_name)
except Exception as e:
print(f"Error: {e}")
results['failed'].append(device_name)
# Print summary
print("\n" + "=" * 80)
print("Summary:")
print(f" Successful: {len(results['success'])} devices - {results['success']}")
print(f" Failed: {len(results['failed'])} devices - {results['failed']}")
print("=" * 80)
This demonstrates the power of automation: applying consistent configuration to multiple devices in seconds that would take minutes per device manually.
3.4 Configuration from File (Templates)
Try running this script to create a interface_template.txt file with some commands to run and then to apply all of those configuration commands to R1. Something like this would be helpful if another engineer had already documented the configuration commands to run on devices in a text file and you just wanted to execute those commands.
# configure_from_template.py
# Read configuration commands from file and apply to device
from netmiko import ConnectHandler
from device_inventory import devices
# Create a template file first
template_content = """interface Loopback100
description Template-Generated Interface
ip address 10.100.100.1 255.255.255.0
no shutdown
!
interface Loopback101
description Another Template Interface
ip address 10.101.101.1 255.255.255.0
no shutdown
!
"""
# Save template to file
with open('interface_template.txt', 'w') as f:
f.write(template_content)
# Now apply template to R1
device = devices['R1']
connection = ConnectHandler(**device)
print("Applying configuration from template file...")
# send_config_from_file() reads file and sends commands
output = connection.send_config_from_file('interface_template.txt')
print("Configuration output:")
print(output)
# Verify interfaces created
print("\nVerifying interfaces...")
verify = connection.send_command("show ip interface brief | include Loopback10")
print(verify)
# Save
connection.send_command("write memory")
connection.disconnect()
print("\nTemplate applied successfully")
|
Configuration Templates: In real environments, you’d use templates with variables (e.g., Jinja2 templates) to customize configurations per device. THis would allow you to make slight modifications to the template for each different device (such as a differnt IP address) but the same basic command structure. For example:
We’ll keep it simple for Lab 1, but this is a preview of advanced automation techniques. |
3.5 Practice Exercises
Write scripts to:
-
Configure NTP server on all devices:
-
Command:
ntp server 0.pool.ntp.org -
Apply to all 4 devices
-
Verify with:
show ntp status
-
-
Create VLANs on both switches:
-
VLAN 10: Engineering
-
VLAN 20: Sales
-
VLAN 30: Guest
-
Verify with:
show vlan brief
-
-
Configure syslog server:
-
Command:
logging host 10.10.20.100 -
Apply to all devices
-
Set logging source interface
-
-
Disable unused interfaces on R2:
-
Interfaces: GigabitEthernet0/0/0, GigabitEthernet0/1/0-3
-
Apply description: "UNUSED - Disabled by Policy"
-
Put in shutdown state
-
Section 4 — Error Handling and Verification
Professional automation scripts need robust error handling to deal with connection failures, command errors, and unexpected situations.
4.1 Basic Error Handling
Try running this script which shows how useful errors can be generated when:
-
There is an error with the IP address of the device and you are unable to connect to the specified IP
-
There is a problem with logging into the device (likely bad username or password).
-
There is an error running one of the commands on the device.
# error_handling_example.py
# Demonstrate error handling in network automation
from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException
from device_inventory import devices
device = devices['R1']
try:
print("Attempting connection...")
connection = ConnectHandler(**device)
print("Connected successfully")
# Try to send an invalid command
try:
output = connection.send_command("show invalid-command")
print(output)
except Exception as cmd_error:
print(f"Command error: {cmd_error}")
connection.disconnect()
except NetmikoTimeoutException:
print("Connection timeout - device not reachable")
print(" Check: IP address, network connectivity, firewall rules")
except NetmikoAuthenticationException:
print("Authentication failed - wrong username or password")
print(" Check: credentials in device_inventory.py")
except Exception as e:
print(f"Unexpected error: {e}")
4.2 Configuration with Verification
Try running this script which demonstrates what it takes to include checks that a simple configuration command (creating a new interface and setting the IP) actually happens correctly through Netmiko.
# configure_with_verification.py
# Configure interface and verify change was applied correctly
from netmiko import ConnectHandler
from device_inventory import devices
device = devices['R1']
connection = ConnectHandler(**device)
# Define what we want to configure
interface_name = "Loopback200"
desired_ip = "10.200.200.1"
desired_mask = "255.255.255.0"
print(f"Configuring {interface_name} with IP {desired_ip}/{desired_mask}")
# Step 1: Apply configuration
config_commands = [
f'interface {interface_name}',
f'description Configured with Verification',
f'ip address {desired_ip} {desired_mask}',
'no shutdown'
]
print("\n[1/4] Applying configuration...")
connection.send_config_set(config_commands)
print("Configuration commands sent")
# Step 2: Verify interface exists
print("\n[2/4] Verifying interface exists...")
verify_output = connection.send_command(f"show ip interface brief | include {interface_name}")
if interface_name in verify_output:
print(f"Interface {interface_name} found")
print(f" Status: {verify_output.strip()}")
else:
print(f"Interface {interface_name} not found!")
connection.disconnect()
exit(1)
# Step 3: Verify IP address is correct
print("\n[3/4] Verifying IP address...")
ip_check = connection.send_command(f"show running-config interface {interface_name} | include ip address")
if desired_ip in ip_check and desired_mask in ip_check:
print(f"IP address configured correctly")
print(f" Config: {ip_check.strip()}")
else:
print(f"IP address mismatch!")
print(f" Expected: {desired_ip} {desired_mask}")
print(f" Found: {ip_check.strip()}")
# Step 4: Save configuration
print("\n[4/4] Saving configuration...")
save_output = connection.send_command("write memory")
if "OK" in save_output or "[OK]" in save_output:
print("Configuration saved successfully")
else:
print("Save may have failed - check device")
connection.disconnect()
print("\nConfiguration and verification complete")
This script demonstrates best practices:
-
Apply configuration
-
Verify configuration was applied
-
Check specific values are correct
-
Save configuration
-
Provide clear status messages
4.3 Device Reachability Check
Run this script which checks to see if several network devices are accessible through SSH.
# check_device_status.py
# Check which devices are reachable before attempting configuration
from netmiko import ConnectHandler
from device_inventory import devices, all_devices
import socket
def check_port_open(host, port=22, timeout=3):
"""Check if TCP port is open on host"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
try:
result = sock.connect_ex((host, port))
sock.close()
return result == 0 # 0 means port is open
except:
return False
print("Checking device reachability...")
print("=" * 80)
reachable = []
unreachable = []
for device_name in all_devices:
device = devices[device_name]
host = device['host']
print(f"\nChecking {device_name} ({host})...", end=" ")
# Check if SSH port is open
if check_port_open(host):
print("Reachable")
# Try to connect via SSH
try:
connection = ConnectHandler(**device)
prompt = connection.find_prompt()
connection.disconnect()
print(f" SSH authentication successful ({prompt})")
reachable.append(device_name)
except Exception as e:
print(f" SSH failed: {e}")
unreachable.append(device_name)
else:
print("Unreachable (port 22 closed or filtered)")
unreachable.append(device_name)
# Summary
print("\n" + "=" * 80)
print("Device Status Summary:")
print(f" Reachable: {len(reachable)} devices - {reachable}")
print(f" Unreachable: {len(unreachable)} devices - {unreachable}")
if unreachable:
print("\nSome devices are unreachable. Check:")
print(" - Network connectivity (ping)")
print(" - SSH service enabled on device")
print(" - Credentials correct")
print(" - Firewall rules")
4.4 Practice Exercises
-
Create a script that attempts to configure all devices but continues even if one fails:
-
Track which devices succeeded
-
Track which devices failed and why
-
Generate a final report
-
-
Configuration rollback script:
-
Save current config to file before making changes
-
Make configuration changes
-
If verification fails, restore from backup file
-
-
Connection retry logic:
-
If connection fails, retry 3 times with 5-second delay
-
Only give up after all retries exhausted
-
Section 5 — Practical Automation Scenarios
Real-world examples combining everything you’ve learned.
5.1 Configuration Backup Script
Try running this script which will backup the running configuration from every device in the inventory and save it to the management host.
# backup_all_configs.py
# Backup running configurations from all devices
from netmiko import ConnectHandler
from device_inventory import devices, all_devices
from datetime import datetime
import os
# Create backup directory with timestamp
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_dir = f"config_backups_{timestamp}"
os.makedirs(backup_dir, exist_ok=True)
print(f"Backing up configurations to: {backup_dir}/")
print("=" * 80)
backup_count = 0
for device_name in all_devices:
print(f"\nBacking up {device_name}...", end=" ")
device = devices[device_name]
try:
# Connect to device
connection = ConnectHandler(**device)
# Get running configuration
config = connection.send_command("show running-config")
# Save to file
filename = f"{backup_dir}/{device_name}_config_{timestamp}.txt"
with open(filename, 'w') as f:
f.write(f"! Configuration backup for {device_name}\n")
f.write(f"! Backup date: {datetime.now()}\n")
f.write(f"! Device: {device['host']}\n\n")
f.write(config)
# Get file size
file_size = os.path.getsize(filename)
print(f"Saved ({file_size} bytes)")
backup_count += 1
connection.disconnect()
except Exception as e:
print(f"Failed: {e}")
print("\n" + "=" * 80)
print(f"Backup Summary: {backup_count}/{len(all_devices)} devices backed up successfully")
print(f"Backup location: {backup_dir}/")
5.2 Deploy Standard Security Configuration
Try running this script which will apply a base security configuration to every device.
# deploy_security_config.py
# Apply security best practices to all devices
from netmiko import ConnectHandler
from device_inventory import devices, all_devices
# Security configuration commands
security_config = [
# Disable unused services
'no ip http server',
'no ip http secure-server',
'no ip bootp server',
'no service finger',
'no service pad',
# Enable security features
'service password-encryption',
'service tcp-keepalives-in',
'service tcp-keepalives-out',
'no service dhcp',
# Login security
'login block-for 300 attempts 3 within 120',
'login on-failure log',
'login on-success log',
# VTY security
'line vty 0 15',
' exec-timeout 10 0',
' transport input ssh',
' logging synchronous',
' exit',
# Console security
'line console 0',
' exec-timeout 10 0',
' logging synchronous',
' exit',
# Enable secret
'enable algorithm-type scrypt secret SecureEnable123!',
# Logging
'logging buffered 51200 warnings',
]
print("Deploying security configuration to all devices...")
print("=" * 80)
for device_name in all_devices:
print(f"\n### {device_name} ###")
print(f"Connecting...", end=" ")
device = devices[device_name]
try:
connection = ConnectHandler(**device)
print(" DONE")
print("Applying security configuration...", end=" ")
output = connection.send_config_set(security_config)
print(" DONE")
print("Saving configuration...", end=" ")
connection.send_command("write memory")
print(" DONE")
connection.disconnect()
print(f"{device_name} security configuration complete")
except Exception as e:
print(f"\nError on {device_name}: {e}")
print("\n" + "=" * 80)
print("Security deployment complete")
5.3 Practice Scenarios
Create scripts for these real-world scenarios:
-
Interface Audit:
-
Collect all interface configurations from all devices
-
Identify interfaces without descriptions
-
Generate report of non-compliant interfaces
-
-
Firmware Version Check:
-
Collect IOS version from all devices
-
Compare to approved version list
-
Generate report of devices needing upgrade
-
Common Errors & Troubleshooting
| Error | Possible Cause | Solution |
|---|---|---|
|
Device not reachable on network |
Check ping, verify IP address, check routing |
|
SSH not responding |
Verify SSH enabled: |
|
Wrong username/password |
Verify credentials on device: |
|
SSH not enabled or wrong port |
Enable SSH: |
|
Unexpected prompt format |
Specify prompt pattern in ConnectHandler |
|
Wrong command syntax |
Verify command works manually in CLI first |
|
Forgot to run |
Always send |
Script hangs on send_command |
Command waiting for more input |
Use |
Permission denied |
User lacks privilege 15 |
Verify: |
StrictHostKeyChecking prompt |
SSH host key not in known_hosts |
Use |
|
Output format changed between IOS versions |
Use TextFSM (built into Netmiko), or use APIs (Labs 3-4) for structured data that won’t break |
|
IOS Version Dependency Issue (Critical Limitation of SSH Automation) The MOST COMMON long-term problem with SSH automation: IOS software upgrades that change output formats break your scripts. Example:
What breaks:
Why this matters:
Solutions:
This is THE major reason modern network automation prefers API-based approaches! |
Debugging Tips
-
Test commands manually first:
-
SSH to device manually
-
Type commands to verify syntax
-
Note exact output format
-
-
Enable Netmiko logging:
```python import logging logging.basicConfig(filename='netmiko_session.log', level=logging.DEBUG) logger = logging.getLogger("netmiko") ``` -
Print all output:
```python output = connection.send_config_set(commands) print(output) # See what device returned ```
-
Use send_command_timing() for interactive commands:
```python output = connection.send_command_timing("reload") # For commands that prompt for confirmation ``` -
Check connection parameters:
```python print(device) # Verify dictionary has correct values ```
-
Test connectivity before automation:
-
ping <device_ip> -
ssh netadmin@<device_ip> -
Verify manually before running script
-
-
Start simple, add complexity:
-
Get one device working first
-
Then expand to multiple devices
-
Add error handling last
-
Reflection Questions
After completing this lab activity you should be able to answer these questions:
-
Automation Value: You applied security configuration to 4 devices with one script. How much time did this save compared to doing it manually? If you had 50 devices instead of 4, how much more valuable would automation be?
-
Error Prevention: When configuring manually via CLI, what types of errors can occur (typos, forgotten commands, etc.)? How does automation reduce these errors?
-
Consistency: Why is it important that all devices have identical security configurations? How does automation ensure consistency better than manual configuration?
-
CLI Command Familiarity (MAJOR ADVANTAGE): The Python script sends the exact same CLI commands you already know from CCNA training. What are the advantages of this approach? Consider: learning curve, device compatibility, ability to automate operational commands like
ping,traceroute,copy tftp:,debug,reload, etc. Do you think RESTCONF/NETCONF APIs can automate these operational tasks? -
Show Command Parsing (MAJOR DISADVANTAGE): When you run
show ip interface brief, the output is unstructured text. Suppose Cisco releases a new IOS version that changes the spacing or adds a new column to this output. What would happen to a Python script that parses this output by expecting specific column positions? How does this make SSH automation fragile compared to structured data formats like JSON/XML? -
Verification Importance: Why is it important to verify configurations after applying them? Give an example where a command might succeed but not do what you intended.
-
IOS Version Fragility: Imagine you write a script that parses
show versionto extract the IOS version number. The script works perfectly on IOS 15.x devices. Two years later, your company upgrades to IOS 17.x, and the format ofshow versionoutput changes slightly. Your script breaks. How is this different from using APIs with standardized YANG data models? Why might structured data (JSON/XML) be more resilient to software upgrades? -
Security Considerations: Your
device_inventory.pyfile contains passwords in plaintext. In a production environment, how should credentials be stored more securely? -
Device Types: Netmiko supports many device types (
cisco_ios,cisco_nxos,arista_eos,juniper_junos, etc.). Why do you think different device_types are needed? What might be different about CLI interactions across vendors? -
Scale Considerations: Your scripts connect to devices sequentially (one at a time). If you had 100 devices, this would take a long time. Research Python’s
threadingormultiprocessingmodules. How could these speed up automation? -
Configuration Management: You created a backup script that saves configs to files. In enterprise environments, how might these backups be used? (Think version control, compliance auditing, disaster recovery)
-
Operational Commands Without APIs: Some network operations like
ping <destination>,copy tftp:,traceroute, andreloadare operational commands, not configuration commands. These may not have equivalents in RESTCONF/NETCONF APIs (which focus on configuration data). Why is SSH/Netmiko still essential even in modern networks with API support? When would you need to fall back to SSH? -
Preparing for Lab 5: In Lab 5, you’ll compare SSH/Netmiko with RESTCONF and NETCONF. Based on your experience in this lab, what do you think are the biggest strengths of SSH-based automation (think: CLI familiarity, operational commands, universal compatibility)? What are the biggest weaknesses (think: text parsing, IOS version changes, unstructured data)?
Additional Resources
-
Netmiko GitHub & Documentation: https://github.com/ktbyers/netmiko
-
Kirk Byers' Network Automation Course: https://pynet.twb-tech.com/
-
Netmiko Tutorial Series: https://pynet.twb-tech.com/blog/netmiko.html
-
TextFSM Templates for Parsing: https://github.com/networktocode/ntc-templates
-
Python for Network Engineers: https://www.packetcoders.io/
-
Cisco DevNet Learning Labs: https://developer.cisco.com/learning/
-
Automate the Boring Stuff with Python: https://automatetheboringstuff.com/
What’s Next?
After mastering SSH automation with Netmiko, the course progression continues:
Lab 2 — REST API Fundamentals: * Learn HTTP methods, JSON parsing * Practice with public REST APIs * Foundation for RESTCONF (Lab 3)
Lab 3 — RESTCONF + YANG Models: * Use REST APIs built into Cisco devices * Work with JSON instead of unstructured CLI text — solves the text parsing problem! * Structured data from YANG models (consistent across IOS versions) * Addresses the IOS fragility issue you experienced with show commands
Lab 4 — NETCONF + ncclient: * XML-based protocol for network automation * Structured data resilient to IOS version changes * Transaction support and rollback capability * More advanced than RESTCONF
Lab 5 — Multi-Device Automation Comparison: * Configure 4-device network using all three methods * Direct comparison: Same task via SSH vs RESTCONF vs NETCONF * Discover when SSH is still the right choice (operational commands, legacy devices) * Discover when APIs are superior (structured data, version resilience, transactions) * Real-world hybrid automation strategy
Lab 1 Complete! You now know how to automate Cisco devices via SSH and Netmiko. This is the foundation that makes Labs 3-5 easier to understand—you’ll see the same tasks done with different protocols and compare their strengths and weaknesses.
|
Why Learn Other Methods? You’ve experienced SSH automation’s key limitation: parsing unstructured text output that changes between IOS versions. Labs 3-4 teach API-based methods that return structured data (JSON/XML) with consistent formats defined by YANG models—making your automation resilient to IOS upgrades. But SSH still has its place! Operational commands ( |
The CLI commands you automated here translate directly to API operations in later labs. For example:
* Lab 1: show ip interface brief via SSH → raw text output (fragile, version-dependent)
* Lab 3: GET request to RESTCONF → structured JSON output (consistent, parsable)
* Lab 4: get-config via NETCONF → structured XML output (transactional, rollback support)
Same data, different methods—different trade-offs! Understanding SSH automation first makes the other methods easier to grasp, and helps you appreciate why structured API data matters.