Labs

Lab auto-01 - Build Your First Network Automation Host

In: Labs, CCNA

Every network automation tutorial on the internet starts the same way: "first, install Python on your laptop." This one does not. In this lab you build the automation host inside the network, on the same Alpine Linux node that ships with the PingLabz CCNA Automation Labs topology. By the end, a Python script running on HOST1 will SSH into R1 and pull live output from a router you can see booting in Cisco Modeling Labs. This is the first lab in the automation series, it is completely free, and everything below was captured from the real lab: Alpine 3.23, Python 3.12, Netmiko 4.7, and Cisco IOS XE 17.18.

Automation and Programmability is Domain 6 of the CCNA 200-301 blueprint, worth 10% of your exam. It is also the domain most CCNA candidates study entirely from flashcards. The difference between memorizing "REST APIs use HTTP verbs" and having actually pushed a command to a router from Python is the same difference the other 90% of the exam rewards: hands-on beats theory recall, every time.

The topology

This series runs on the PingLabz CCNA Automation Lab topology, a variant of the same five-node base used across the PingLabz lab library. It fits Cisco Modeling Labs Free: five counted nodes, plus an unmanaged switch and an external connector, neither of which counts toward the CML Free limit.

NodePlatformRoleManagement IP
R1iol-xe (IOS XE 17.18)LAN gateway10.20.0.1
R2iol-xeTransit router10.20.0.2
R3iol-xeRemote router10.30.30.2
SW1ioll2-xeManaged L2 switch10.20.0.10
HOST1Alpine LinuxThe automation host (you build it here)10.20.0.50
SW2unmanaged switchSpare broadcast domain (not counted)n/a
EXT-NATexternal connectorNAT internet for HOST1 (not counted)n/a

The external connector is the one piece you have not seen in the other PingLabz series. HOST1 needs to download packages from the Alpine mirrors, so its second interface (eth1) connects to an External Connector in NAT mode. CML gives that interface outbound internet through your computer's own connection, with no configuration on your side. Per the Cisco CML documentation, external connectors do not count toward the node license, so the lab still fits CML Free.

What you will learn

  • How to set up an Alpine Linux node in CML as a network automation host, the right way (let it boot with its default config first).
  • How to give a Linux host dual-homed networking: one interface for internet, one for the lab management network.
  • How to enable SSH version 2 on Cisco IOS XE with modern 3072-bit RSA keys, and why the old config-mode keygen command is deprecated.
  • How to install Netmiko in a Python virtual environment and make your first programmatic connection to a router.

Download the topology

Import the .yaml into CML (Import Lab in the dashboard), hit start, and give the nodes a minute to boot. The routers and switch boot with the standard PingLabz base configuration. Router login is pinglabz / PingLabz!23 with enable secret Cisco@123.

Step 1: Let HOST1 boot, then log in

One rule for Alpine nodes in CML, learned the hard way: let the node boot completely with its default configuration before you touch it. Do not attach a day-0 config, and do not start typing into the console while it is still booting. Give it a minute after the lab starts, then open the HOST1 console and log in with CML's Alpine defaults: username cisco, password cisco.

HOST1 login: cisco
Password:

HOST1:~$ whoami
cisco
HOST1:~$ cat /etc/alpine-release
3.23.3

The cisco user has passwordless sudo, which you will use for everything that touches the network stack or the package manager.

Step 2: Get internet on eth1

HOST1 has two interfaces. eth0 is wired to SW1 (the lab LAN); eth1 is wired to the EXT-NAT external connector. Bring eth1 up and ask for a DHCP lease:

HOST1:~$ sudo ip link set eth1 up
HOST1:~$ sudo udhcpc -i eth1 -n -q
udhcpc: started, v1.37.0
udhcpc: broadcasting discover
udhcpc: broadcasting select for 192.168.255.24, server 192.168.255.1
udhcpc: lease of 192.168.255.24 obtained from 192.168.255.1, lease time 3600

The -n flag makes udhcpc exit if no lease appears (instead of retrying forever in your console), and -q quits once the lease is bound. CML's NAT connector hands out addresses from its internal 192.168.255.0/24 pool and NATs everything outbound. Confirm you can reach the internet:

HOST1:~$ ping -c 2 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=42 time=12.324 ms
64 bytes from 8.8.8.8: seq=1 ttl=42 time=13.307 ms

Step 3: Address eth0 for the lab LAN

eth0 talks to the lab. Give it the standard PingLabz automation-host address, plus one static route so HOST1 can reach R3's side of the point-to-point link through R2:

HOST1:~$ sudo ip addr add 10.20.0.50/24 dev eth0
HOST1:~$ sudo ip route add 10.30.30.0/30 via 10.20.0.2
HOST1:~$ traceroute -n -m 4 10.30.30.2
traceroute to 10.30.30.2 (10.30.30.2), 4 hops max, 46 byte packets
 1  10.20.0.2  3.433 ms  2.823 ms  4.659 ms
 2  10.30.30.2  4.133 ms  *  5.199 ms

Two hops: HOST1 to R2, R2 across the P2P link to R3. The default route stays on eth1 (internet); only the lab prefixes use eth0. This is the same split-management pattern you will meet in production, where the automation host has one leg in the management VLAN and one leg toward the wider network.

Runtime ip commands do not survive a reboot. To make the addressing permanent, write it into Alpine's /etc/network/interfaces:

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 10.20.0.50/24
    post-up ip route add 10.30.30.0/30 via 10.20.0.2 || true

auto eth1
iface eth1 inet dhcp

Step 4: Install Python and Netmiko

Alpine ships lean: no Python on board. Two packages from the mirrors fix that:

HOST1:~$ sudo apk update
v3.23.4-359-g5b71e521e31 [https://dl-cdn.alpinelinux.org/alpine/v3.23/main]
v3.23.4-359-g5b71e521e31 [https://dl-cdn.alpinelinux.org/alpine/v3.23/community]
OK: 27629 distinct packages available

HOST1:~$ sudo apk add python3 py3-pip
(9/20) Installing python3 (3.12.13-r0)
...
OK: 138.1 MiB in 123 packages

HOST1:~$ python3 --version
Python 3.12.13

Install Netmiko inside a virtual environment. Modern Python distributions protect the system site-packages, and a venv keeps your automation dependencies isolated and reproducible (the same discipline you will want on a production jump host):

HOST1:~$ python3 -m venv ~/autolab
HOST1:~$ . ~/autolab/bin/activate
(autolab) HOST1:~$ pip install netmiko
(autolab) HOST1:~$ python3 -c "import netmiko; print('netmiko', netmiko.__version__)"
netmiko 4.7.0

Step 5: Enable SSH on R1, the modern way

Netmiko talks SSH, and the routers boot with SSH allowed on the VTY lines but no host keys generated. On the R1 console, generate the keys from privileged EXEC mode. Watch what IOS XE 17.x says if you try the old way first:

R1(config)# crypto key generate rsa modulus 2048
%This command is deprecated.  Use the command in exec mode instead
...
SECURITY WARNING - Module: SSH, Command: crypto key generate rsa ...,
Reason: SSH host key uses insufficient key length,
Description: SSH with insufficient key length,
Remediation: Use SSH RSA host key with a minimum length of 3072 bits for enhanced security

Two lessons in one capture: config-mode keygen is deprecated, and 2048-bit RSA now trips a security warning. Do it properly, from EXEC mode, at 3072 bits, then pin SSH to version 2:

R1# crypto key generate rsa modulus 3072
% The key modulus size is 3072 bits
% Generating crypto RSA keys in background ...

R1# configure terminal
R1(config)# ip ssh version 2

R1# show ip ssh
SSH Enabled - version 2.0
Authentication methods:publickey,keyboard-interactive,password
Encryption Algorithms:chacha20-poly1305@openssh.com,aes128-gcm@openssh.com,aes256-gcm@openssh.com, ...
Minimum expected Diffie Hellman key size : 2048 bits

Sanity-check the path from HOST1 with a plain SSH client before involving Python (sudo apk add openssh-client if you have not already):

HOST1:~$ ssh pinglabz@10.20.0.1 'show ip interface brief'
(pinglabz@10.20.0.1) Password:

*****************************************************
* PingLabz CCNA Automation Lab  - R1                *
* https://www.pinglabz.com/automation-labs/         *
* Authorized practice use only                      *
*****************************************************

Interface              IP-Address      OK? Method Status                Protocol
Ethernet0/0            10.20.0.1       YES TFTP   up                    up
Ethernet0/1            unassigned      YES unset  administratively down down
Ethernet0/2            unassigned      YES unset  administratively down down
Ethernet0/3            unassigned      YES unset  administratively down down
Loopback0              10.255.0.1      YES TFTP   up                    up

(The Method column says TFTP because CML injects the startup configuration at boot; on physical hardware you would normally see NVRAM or manual.)

Step 6: Your first Netmiko script

Now replace yourself with Python. Create hello_netmiko.py on HOST1:

from netmiko import ConnectHandler

r1 = {
    "device_type": "cisco_ios",
    "host": "10.20.0.1",
    "username": "pinglabz",
    "password": "PingLabz!23",
    "secret": "Cisco@123",
}

conn = ConnectHandler(**r1)
print(conn.find_prompt())
print(conn.send_command("show version | include uptime"))
conn.disconnect()

Run it:

(autolab) HOST1:~$ python3 hello_netmiko.py
R1#
R1 uptime is 17 minutes

That is the whole trick. ConnectHandler opens the SSH session and handles the prompt detection, find_prompt() proves you landed where you think you did, and send_command() sends one exec command and returns its output as a Python string. Everything else in network automation is this, repeated, with better error handling.

Troubleshooting matrix

SymptomLikely causeFix
Cannot log in to HOST1 consoleTouched the node before first boot completed, or a day-0 config replaced the defaultsWipe the node (not the lab) and let it boot untouched, then cisco/cisco
udhcpc: sendto: Network is downeth1 link is still downsudo ip link set eth1 up first, then udhcpc
socket: Operation not permittedRan a network command without rootPrefix with sudo
apk cannot reach mirrorseth1 has no lease, or the EXT-NAT connector is not in NAT modeVerify the lease with ip addr show eth1; check the connector's Config tab says NAT
Netmiko times out connectingSSH keys never generated on R1, or eth0 has no addressshow ip ssh on R1 must say Enabled; ip addr show eth0 on HOST1
Netmiko authentication failureTypo in username/password dictionaryCredentials are pinglabz / PingLabz!23, secret Cisco@123

Key takeaways

  • The automation host lives inside the topology: Alpine + venv + Netmiko, fed by a NAT external connector that does not count against CML Free's five nodes.
  • Let Alpine boot with its default config before touching it. Login is cisco/cisco with passwordless sudo.
  • IOS XE 17.x wants RSA keys generated in EXEC mode at 3072 bits minimum; the config-mode command is deprecated and 2048-bit keys trigger a security warning.
  • One Netmiko pattern (ConnectHandler, find_prompt, send_command) is the foundation for every script in the rest of this series.

Next up: Lab auto-02, Reading the Network with Netmiko, where one script interrogates all four devices and you stop opening consoles for routine checks. The full series index lives at /automation-labs/.

Written by
More from Ping Labz
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Ping Labz.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.