Contents

Clabernetes: Running ContainerLab Topologies in Kubernetes

Clabernetes: Running ContainerLab Topologies in Kubernetes

clabernetes takes containerlab — the popular network topology emulation tool — and brings it into Kubernetes. Instead of running labs on a single host, topologies become Kubernetes resources managed by a custom operator.

Why Clabernetes

ContainerLab is excellent for spinning up network labs locally, but it’s tied to a single machine. Clabernetes lifts this constraint by:

  • Distributing lab nodes across a Kubernetes cluster
  • Using CRDs to declare topologies as native Kubernetes resources
  • Enabling multi-user lab environments with namespace isolation
  • Persisting lab state through Kubernetes mechanisms

Custom Resource Definition

A containerlab topology becomes a Topology CRD:

apiVersion: clabernetes.containerlab.dev/v1alpha1
kind: Topology
metadata:
  name: srl-spine-leaf
  namespace: network-labs
spec:
  definition:
    nodes:
      spine1:
        kind: srl
        image: ghcr.io/nokia/srlinux:latest
      spine2:
        kind: srl
        image: ghcr.io/nokia/srlinux:latest
      leaf1:
        kind: srl
        image: ghcr.io/nokia/srlinux:latest
      leaf2:
        kind: srl
        image: ghcr.io/nokia/srlinux:latest
    links:
    - endpoints: ["spine1:e1-1", "leaf1:e1-49"]
    - endpoints: ["spine1:e1-2", "leaf2:e1-49"]
    - endpoints: ["spine2:e1-1", "leaf1:e1-50"]
    - endpoints: ["spine2:e1-2", "leaf2:e1-50"]

Operator Architecture

The clabernetes operator watches Topology resources and reconciles the desired state:

func (r *TopologyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    topology := &clabernetesv1alpha1.Topology{}
    if err := r.Get(ctx, req.NamespacedName, topology); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    if err := r.reconcileNodes(ctx, topology); err != nil {
        return ctrl.Result{}, err
    }

    if err := r.reconcileLinks(ctx, topology); err != nil {
        return ctrl.Result{}, err
    }

    return ctrl.Result{}, r.updateStatus(ctx, topology)
}

func (r *TopologyReconciler) reconcileNodes(ctx context.Context, topology *clabernetesv1alpha1.Topology) error {
    for nodeName, nodeDef := range topology.Spec.Definition.Nodes {
        pod := r.buildNodePod(topology, nodeName, nodeDef)
        if err := r.applyPod(ctx, pod); err != nil {
            return fmt.Errorf("reconciling node %s: %w", nodeName, err)
        }
    }
    return nil
}

Node Pod Generation

Each containerlab node becomes a Kubernetes Pod with appropriate privileges for network emulation:

func (r *TopologyReconciler) buildNodePod(
    topology *clabernetesv1alpha1.Topology,
    nodeName string,
    node clabernetesv1alpha1.NodeDefinition,
) *corev1.Pod {
    return &corev1.Pod{
        ObjectMeta: metav1.ObjectMeta{
            Name:      fmt.Sprintf("%s-%s", topology.Name, nodeName),
            Namespace: topology.Namespace,
            Labels: map[string]string{
                "clabernetes/topology": topology.Name,
                "clabernetes/node":     nodeName,
            },
        },
        Spec: corev1.PodSpec{
            Containers: []corev1.Container{{
                Name:  nodeName,
                Image: node.Image,
                SecurityContext: &corev1.SecurityContext{
                    Privileged: boolPtr(true),
                },
                Resources: corev1.ResourceRequirements{
                    Requests: corev1.ResourceList{
                        corev1.ResourceCPU:    resource.MustParse("500m"),
                        corev1.ResourceMemory: resource.MustParse("512Mi"),
                    },
                },
            }},
        },
    }
}

Inter-node links are implemented as veth pairs between pods, wired up by the operator:

type LinkManager struct {
    client client.Client
}

func (lm *LinkManager) CreateLink(ctx context.Context, link TopologyLink) error {
    srcPod, err := lm.getPod(ctx, link.SrcNode)
    if err != nil {
        return err
    }
    dstPod, err := lm.getPod(ctx, link.DstNode)
    if err != nil {
        return err
    }

    return lm.createVethPair(ctx, srcPod, link.SrcInterface, dstPod, link.DstInterface)
}

CLI Usage

# Install the operator
kubectl apply -f https://github.com/bayars/clabernetes/releases/latest/install.yaml

# Deploy a topology
kubectl apply -f spine-leaf.yaml

# Check status
kubectl get topology -n network-labs
# NAME            NODES   LINKS   STATUS
# srl-spine-leaf  4       4       Running

# Access a node
kubectl exec -it -n network-labs srl-spine-leaf-spine1 -- sr_cli

Comparison with Standalone ContainerLab

FeatureContainerLabClabernetes
ScaleSingle hostMulti-node cluster
IsolationProcess/network NSKubernetes namespaces
State managementFilesKubernetes resources
Multi-userLimitedNative via RBAC
PersistenceManualKubernetes-native

Conclusion

Clabernetes extends containerlab’s reach into Kubernetes, enabling scalable, multi-user network labs that are declared and managed as native cluster resources. It’s a powerful tool for anyone running SR Linux, SROS, or other containerized network operating systems at scale.

Source: GitHub