Skip to content
Thomas Mangin edited this page Aug 16, 2026 · 2 revisions

Pre-Alpha. This page describes behavior that may change.

netlab builds a lab from a short YAML topology and starts each node under containerlab. Ze runs as one of those nodes. You write the topology, netlab renders a configuration for every node and starts the containers, and netlab validate reads each node back through its CLI. The ze side of the integration lives in contrib/netlab/ in the main repository.

Two integration tiers

netlab integrates a network operating system at one of two tiers. Ze uses the second one.

Tier What it needs Runs under
Device Ansible task lists, one per module, and a Vagrant box containerlab or Vagrant
Daemon One YAML file (netsim/daemons/<name>.yml) and one directory of Jinja2 templates containerlab only

BIRD, dnsmasq, VPP, and netscaler are daemons. Ze is a daemon too. The tier costs less to write and to keep current, and it removes the Vagrant box from the list of artifacts to publish.

What you need

Component Why
netlab 26.08 Renders the topology and calls containerlab
containerlab and Docker Start the nodes
netlab/ze:latest The lab image, built from the ze tree

Build the lab image

netlab daemons usually build their own image. Ze does not: contrib/netlab/ze.yml sets clab.build: False and image: netlab/ze:latest, so netlab starts an image you build first.

make ze-docker-lab-build

This is a second image, not a replacement for the deployment image. docker/Dockerfile stays a static binary on a scratch base. The lab image adds a shell and iproute2, because containerlab and netlab drive a node from the outside: they run sh and ip inside the container to assign the addresses the topology declares. Both images derive their feature tags from feature-gates.txt, so a defect cannot reproduce in a lab and disappear in production. See Docker for the two images.

ZE_LAB_IMAGE and ZE_LAB_TAG change the name and the tag. Change them and change image: in ze.yml to match. No image is published, so there is nothing to pull.

Give netlab the daemon definition

There are two routes. Copy the daemon definition and the templates into the netlab package:

cp contrib/netlab/ze.yml  <netlab>/netsim/daemons/ze.yml
cp -R contrib/netlab/ze   <netlab>/netsim/daemons/ze

Or leave the netlab install alone and let a topology carry them. netlab reads a topology-defaults.yml beside the topology, with the contents of ze.yml under a daemons: ze: key, and a templates/ze/ directory beside it. This second route is what make ze-netlab-render-check uses.

Run the reference topology

netlab up -t contrib/netlab/topology.yml

contrib/netlab/topology.yml has three ze nodes:

r1 ---- internal ---- r2      AS 65001, iBGP + OSPF + IS-IS + BFD
 \
  ---- external ---- r3       AS 65002, eBGP only

netlab runs an IGP on internal links only, so the eBGP peer sits on its own link. r1 carries two static routes, one with a next hop and one discard. r3 covers what the template renders for a node whose modules are a subset of the declared set.

One config file, five ignore files

netlab decides which templates a node needs from the daemon_config map in ze.yml, one entry per module. A module a node uses with no entry fails netlab create with Cannot find <module> configuration template.

Ze has no include directive, so it cannot read one file per module. ze/ze.j2 renders the whole running configuration into /etc/ze/ze.conf, and every other key points at an .ignore file that nothing reads. This is the dnsmasq pattern, and it is required, not stylistic.

Key File Read by ze
ze /etc/ze/ze.conf Yes, the whole configuration
bgp /etc/ze/bgp.ignore No
ospf /etc/ze/ospf.ignore No
isis /etc/ze/isis.ignore No
bfd /etc/ze/bfd.ignore No
routing /etc/ze/routing.ignore No

containerlab bind-mounts the rendered file into the node, and the node starts with ze start /etc/ze/ze.conf. netlab assigns the interface addresses with ip inside the container, so the rendered configuration carries no interface block.

Ze re-reads its configuration on SIGHUP only. netlab sends no SIGHUP, and ze.yml says so with features.initial.reload: false. A configuration change in a running lab needs kill -HUP on the ze process inside the node, or a node restart.

netlab validate reads the daemon through the CLI. ze.yml declares the show command as ze cli -c "show $@ | json compact" and netlab runs it with docker exec, so the output is JSON that netlab parses.

The lab login

The rendered configuration declares one user, netlab, with the password netlab, and ze.yml puts the same pair in the container environment. This is a well-known credential in a throwaway container. Nothing in ze defaults to it and no image carries it. Override both with netlab_lab_user and netlab_lab_password in the topology, and change clab.node.env in ze.yml to match.

The template writes the password as plaintext-password. Ze hashes that leaf when it loads the file, so the running tree holds a bcrypt hash. The daemon warns once, naming the file, because the file still holds the secret.

Declared modules

ze.yml declares five modules plus the initial block: bgp, ospf, isis, bfd, and routing (static routes). netlab tests whatever a daemon declares, so the file declares only what the template emits.

Daemon or device Feature keys
Ze 6
BIRD 10
FRR 18
Arista EOS 19

Counts measured against netlab 26.08. Ze declares isis and BIRD does not. FRR declares twelve keys ze does not: evpn, gateway, lag, mpls, ripv2, sr, srv6, stp, tunnel, vlan, vrf, and vxlan.

What is proven, and what is not

The lab has never been started. The machine that wrote this integration has no containerlab, so netlab up and netlab validate were not run. Only BGP has evidence past "renders and parses": a functional test starts a daemon from the BGP-only render, logs in, and reads the peer list back as JSON. It establishes no session, and OSPF, IS-IS, BFD, and static routes need interfaces an unprivileged test host does not have.

Statement Evidence
netlab accepts the daemon definition and finds a template for each module netlab create exits 0 on the reference topology
The render is valid ze configuration ze config validate exits 0 on each file under contrib/netlab/golden/
A daemon runs one of those renders and answers the show command with JSON test/plugin/netlab-lab-profile.ci
Routes reach the FIB of a running lab, and a ping validation passes Not run
Each declared module passes netlab's integration test for it Not run

Ze sends and receives no LLDP frame, so a netlab validation that reads an LLDP table cannot pass. containerlab needs no LLDP, because its links are veths the topology names.

Keeping the templates from drifting

make ze-netlab-render-check

It renders the templates with a real netlab, compares the result against contrib/netlab/golden/, and runs ze config validate on each golden file. A missing netlab is an error, never a skip. ARGS=--update rewrites the golden files, and you review the diff.

test/plugin/netlab-lab-profile.ci is the other half and needs no netlab. It starts a daemon from a golden file, logs in as the user the render declared, and parses the output of the show command netlab runs. The render check proves the templates emit what ze accepts. The functional test proves ze runs what they emitted.

See also

Adapted from main/docs/guide/netlab.md.

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally