Architecture
How Clabernetes controllers, launchers, resources, and connectivity work together.
Overview
clabernetes is a collection of kubernetes custom resources and controllers that reconcile those resources. The ultimate goal of the controllers is to render a network topology in the cluster: one launcher pod per (containerlab) node, network node interfaces stitched together across pods, and the management interfaces of the nodes exposed.
The model in one picture:
Everything below the "PRIMARY API" line works identically whether the custom resources were written by a person, by tooling, or by the Topology compiler.
The Primary API: Nodes and Links
A Node custom resource represents a single containerlab node -- its spec is simply what a
human would write for that node in a containerlab topology file (flat, verbatim containerlab
vocabulary). A Link custom resource represents a single wire between two nodes. That's the
whole authoritative api: apply Node and Link objects and the controllers render a running,
wired lab -- no Topology object required.
The object name of a Node is the containerlab node name: the launcher pod hostname and the
node's services derive from it, which also means the namespace is the topology boundary.
Everything operational is controller-stamped into the statuses: expose port allocations and
readiness on Nodes, tunnel id allocations on Links. Deployment policy -- expose behavior,
image pull config, launcher resources, scheduling, privileges -- lives on LauncherProfile.
Each Node can explicitly reference one same-namespace profile through
spec.launcherProfileRef; an omitted reference uses global Config defaults, while a missing
explicit reference prevents realization instead of silently falling back.
The primitive objects are bounded: a Node grows only with its own definition and payload, a Link contains one wire, and each launcher watches only Links terminating on its own Nodes (server-side field selectors require Kubernetes 1.31+). This removes the single authoritative aggregate-object ceiling; it does not imply arbitrary runtime scale because API-server capacity, controller throughput, and total object count remain finite.
Topology remains a supported, backward-compatible auxiliary resource. Its existing
connectivity, expose, deployment, imagePull, and statusProbes fields are accepted and
compiled into primitive resources. Because a Topology still embeds the whole source definition,
large labs should use clabverter's direct primitive output instead of persisting a Topology.
Components
Controllers & Custom Resource Definitions
The "brains" of clabernetes is the manager deployment which runs three cooperating controllers:
- the node controller turns every (launcher) Node into a deployment, a per-node "fabric"
service (
<name>-vx, the tunnel termination point), an expose service (<name>), and an optional PVC -- and stamps readiness/allocations into the Node status. Grouped nodes (containerlab'snetwork-mode: container:<primary>) share their primary's pod. - the link controller validates Links and allocates tunnel ids into their statuses.
- the topology controller is the optional convenience layer: it compiles a Topology (a containerlab file plus knobs) into LauncherProfile/Link/Node objects -- expanding topology defaults/kinds into each node so every emitted Node is self contained -- prunes emitted objects that fall out of the definition, protects them from drift, and aggregates fixed-size counts and readiness back into the Topology status. Profiles and Links are reconciled before Nodes.
The compiler normally emits one shared LauncherProfile and places its explicit reference on every Node. A distinct per-node launcher resource policy produces a complete dedicated profile, not an inheritance chain. Shared containerlab management-network settings temporarily remain in every generated LauncherProfile required by the Topology's Nodes; this is a compatibility bridge, not the final ownership model.
Compilation is permissive for lossy fields that can be omitted: the controller emits the supported
resources and logs warnings with source locations. It fails before resource creation when the
definition cannot identify realizable c9s resources, including pseudo-nodes, special or unresolved
endpoints, unsupported explicit link types other than veth, and invalid launcher groups. Explicit
veth links may use brief node:interface endpoints or equivalent structured node/interface
mappings; empty or malformed endpoint values fail before Link resources are emitted. Strict
diagnostics are available to library callers; clabverter does not currently expose a strict-mode
flag.
Launchers
Each Node gets a Deployment running a single launcher container -- a Debian image with the clabernetes launcher binary and a full docker installation (not docker-in-docker: no docker sock mounting, just an independent docker inside the pod, free of the cluster's CRI/CNI).
On startup the launcher fetches its own Node object (and those of any grouped nodes), lists the Links terminating on them via field selectors, and verifies that link view against a digest annotation the node controller stamped on the pod. From that it materializes a containerlab topology file locally and runs plain containerlab.
Containerlab normally creates one same-named nested container for a Node. A component-based node,
such as a distributed SR-SIM chassis, is different: containerlab expands the logical node into card
containers, labels them with one root-node identity, builds the internal fabric, and puts the cards
in one network namespace. Clabernetes keeps that expansion inside one launcher Pod and treats the
whole set as one logical Node; it does not reproduce containerlab's device-specific component or
fabric logic. The launcher discovers the expanded set only when no same-named container exists,
requires unique component names and exactly one namespace owner, and rejects external or cyclic
container:<target> references.
When status probes are enabled, the launcher checks every nested container in a group or expanded
component set. Each must be running and not paused, restarting, or dead; an image-defined Docker
healthcheck must also be healthy. The shared launcher status is therefore atomic across the full
set. For an expanded component node, optional TCP and SSH checks address the validated namespace
owner; for explicit network-mode: container:<primary> groups, they remain scoped to the primary
Node. A running image without a healthcheck remains only process-level readiness.
Grouped Nodes also share the launcher's filesystem. If several members carry a shared payload such as an SR-SIM license at the same normalized destination, the Deployment renders that destination once only when the ConfigMap, key, and mode agree. Conflicting attachments stop reconciliation instead of silently selecting the first source.
Inter-Node Connectivity
Cross-pod wires are realized as vxlan (or, experimentally, slurpeeth) tunnels between the
per-node fabric services. Connectivity is declared on each Link and consumed by both endpoints;
it is not a LauncherProfile setting. The tunnel destination is derived from the link spec alone
(<remote node>-vx.<namespace>...), and the launcher keeps watching its links: moving a
wire's far end ("rewiring") re-targets the tunnel live without restarting anything, while
changing the set of interfaces attached to a node rolls just that node's pod.
Exposing Nodes
Ports listed in a node definition (plus a sensible default set unless auto-expose is disabled) are allocated into the Node status and exposed through a per-node service -- LoadBalancer flavored by default. The assigned address is reflected back into the Node status.
Clabverter
While the goal of clabernetes is to take a containerlab topology and "directly" translate it into a running clabernetes topology in your cluster, there are a few things that cannot be translated directly. Chief among those is startup configurations or any other type of file that you would like to mount to some path on one of your nodes. Containerlab solves this problem by letting you run binaries on your local machine and then mounting/copying files relative to where you ran the command into their appropriate location(s).
As clabernetes is not running on your machine, and you only interact with it via the kubernetes api, we don't have any way to automagically copy or mount any files from your machine.
To work around this, the "clabverter" tool was created -- this is a very simple cli tool that can be pointed at a containerlab topology (either locally or at a URL). This tool determines if any files would be mounted when using this topology file, and if so renders kubernetes configmaps containing the file contents and a Topology that appropriately mounts them into the pods.
clabverter can also skip the Topology object entirely: --emit-crs renders the primitive
LauncherProfile/Link/Node manifests directly, using the same compile and render pipeline as the
in-cluster compiler (without Topology owner references). This is the preferred path when the
aggregate source Topology would be too large to persist.