Automation & Lab-as-Code
Automation & Lab-as-Code
A lab you cannot rebuild is a rumor. This chapter automates the Containerlab lifecycle, config push patterns, and smoke verification so your networking skill compounds in git.
Learning goals
By the end of this chapter you can:
- Wrap deploy/destroy/verify in repeatable scripts
- Organize inventories for multi-node labs
- Push or render configs with light Ansible or shell
- Sketch CI that smoke-tests a topology
- Treat secrets and image pins correctly
Principles
- Topology and config in git
- Idempotent as practical — re-run without fear
- Verify automatically — exit codes matter
- No click-ops source of truth
- Small steps — shell first, Ansible when it pays off
Directory layout (automation-ready)
labs/ospf-tri/
topology.clab.yml
addressing.md
README.md
Makefile
verify.sh
up.sh
down.sh
config/
r1/frr.conf
r1/daemons
r2/...
ansible/
inventory.yml
playbooks/deploy-config.yml
Makefile pattern
TOPO := topology.clab.yml
.PHONY: up down verify smoke
up:
sudo containerlab deploy -t $(TOPO)
down:
sudo containerlab destroy -t $(TOPO) --cleanup
verify:
./verify.sh
smoke: up verify
@echo smoke okmake smoke
make downup.sh / down.sh
#!/usr/bin/env bash
# up.sh
set -euo pipefail
cd "$(dirname "$0")"
sudo containerlab deploy -t topology.clab.yml
./verify.sh#!/usr/bin/env bash
# down.sh
set -euo pipefail
cd "$(dirname "$0")"
sudo containerlab destroy -t topology.clab.yml --cleanupConfig strategies
| Strategy | Pros | Cons |
|---|---|---|
Bind-mount /etc/frr |
Simple, git-native | Image load quirks |
containerlab startup-config |
Kind-supported | Kind-specific |
| Ansible after deploy | Flexible templating | Extra tool |
| Render templates → bind | Pure git artifacts | Build step |
Prefer bind + pure files for FRR until pain appears.
Safe interactive explore → promote
docker exec -it clab-ospf-tri-r1 vtysh
# make change, show run
# copy running config back into config/r1/frr.conf
# redeploy clean to proveLight Ansible (optional)
ansible/inventory.yml:
all:
children:
routers:
hosts:
r1:
ansible_host: clab-ospf-tri-r1
ansible_connection: docker
r2:
ansible_host: clab-ospf-tri-r2
ansible_connection: docker
r3:
ansible_host: clab-ospf-tri-r3
ansible_connection: dockerPlaybook sketch:
- name: Push FRR config files
hosts: routers
gather_facts: false
tasks:
- name: Copy frr.conf
copy:
src: "{{ playbook_dir }}/../../config/{{ inventory_hostname }}/frr.conf"
dest: /etc/frr/frr.conf
- name: Reload FRR
shell: vtysh -c 'write' || true
# better: proper frr reload unit — image dependentansible-playbook -i ansible/inventory.yml ansible/playbooks/deploy-config.ymlPython with scrapli/netmiko is equally valid; pick one tool family and go deep later.
Templating addressing: DIY vs. Netlab
The DIY Approach (Jinja2 + custom scripts)
# addressing.yml
links:
r1_r2: { r1: 10.0.12.1/24, r2: 10.0.12.2/24 }
lans:
h1: { gw: 192.168.1.1/24, host: 192.168.1.10/24 }You can render frr.conf.j2 → config/r1/frr.conf with a custom Python/Jinja script or Ansible template module. Generated files can be committed for diff visibility or generated in CI.
The Netlab Declarative Approach
Writing custom Python Jinja compilers and manual IPAM spreadsheets becomes difficult to maintain when topologies exceed 3–4 nodes. Netlab provides a turnkey, declarative engine designed specifically for network labs:
# topology.yml
---
provider: clab
defaults.device: frr
module: [ ospf, bfd ]
nodes:
r1:
r2:
r3:
links:
- r1:
r2:
- r2:
r3:
- r3:
r1:
validate:
ospf_full:
description: "Verify all OSPF adjacencies reach Full"
nodes: [ r1, r2, r3 ]
plugin: ospf_neighbor(count=2, state='Full')Running netlab up calculates IPAM, renders Jinja2 templates for each device, stands up the Containerlab environment, and configures the routers. Running netlab validate executes the validation suite automatically.
Verification: verify.sh vs. netlab validate
Custom Shell Script (verify.sh)
#!/usr/bin/env bash
set -euo pipefail
fail() { echo "FAIL: $*" >&2; exit 1; }
need_full() {
local node=$1
docker exec "$node" vtysh -c 'show ip ospf neighbor' | grep -q Full \
|| fail "$node missing Full neighbor"
}
need_full clab-ospf-tri-r1
need_full clab-ospf-tri-r2
need_full clab-ospf-tri-r3
docker exec clab-ospf-tri-h1 ping -c 2 -W 1 192.168.2.10 || fail "h1->h2"
docker exec clab-ospf-tri-h2 ping -c 2 -W 1 192.168.1.10 || fail "h2->h1"
echo "OK $(date -Is)"Netlab Automated Validation
With Netlab, assertions are declared directly in YAML and executed with exit codes suitable for CI:
# Returns exit code 0 on pass, non-zero on failure
netlab validateCI smoke (GitHub Actions sketch)
Conceptual job (runner must support Docker + Containerlab—often self-hosted):
# conceptual — adapt to your runners
jobs:
lab-smoke:
runs-on: self-hosted-linux
steps:
- uses: actions/checkout@v4
- name: deploy+verify
working-directory: books/Networking/labs/ospf-tri
run: |
sudo containerlab deploy -t topology.clab.yml
./verify.sh
- name: cleanup
if: always()
working-directory: books/Networking/labs/ospf-tri
run: sudo containerlab destroy -t topology.clab.yml --cleanupThis book’s monorepo CI renders Quarto; lab smoke is optional local/self-hosted. Do not assume GitHub-hosted runners allow nested Containerlab without setup.
Image pins in automation
# .env or Makefile
FRR_IMAGE=quay.io/frrouting/frr:10.2.1
ALPINE_IMAGE=alpine:3.20Topology can be generated or documented to match. Record digests for serious CI.
Idempotency drills
make smoketwice withoutdown— should not corrupt
make down && make smoke— clean path
- Break config,
make smokefails exit ≠ 0
- Fix config, smoke passes
Secret hygiene
- No production passwords in lab repos
- Lab default creds documented as lab-only
- Use
.gitignorefor private env files
Graph and inventory export
sudo containerlab inspect -t topology.clab.yml --format json > inspect.json
sudo containerlab graph -t topology.clab.yml || trueFeed inspect outputs into your own tools later.
Predict → observe → fix
Predict: A peer clones the lab folder, runs make smoke, gets OK without chatting you.
Observe: Remove tribal knowledge; only README + scripts.
Fix: Missing apk packages, wrong container names, unpinned images.
Harden: Add make smoke badge or CI on self-hosted when ready.
Anti-patterns
| Anti-pattern | Replace with |
|---|---|
| Only screenshot proof | verify.sh |
| Snowflake node edits | bind + git |
latest tags |
pins |
| Huge mono playbook day one | Makefile smoke |
| CI without cleanup | always destroy |
Summary
- Lab-as-code = topology + config + verify in git
- Start with Makefile/scripts; add Ansible when templating hurts
- Exit codes turn labs into testable systems
- CI is optional but shaping; cleanup always
- Promote working state from nodes back into the repo
Next: capstone outline—projects that prove the whole journey.