c9s
Core Concepts

Topology

The supported higher-level compiler from containerlab definitions to primitive resources.

Topology is a supported auxiliary resource for authors who want to submit a complete containerlab lab as one object. Its controller compiles that source into the primary c9s resources:

Topology
   └── NodeProfile
   └── Node (one per network node)
   └── Link (one per wire)

The generated resources are reconciled by the same controllers as directly authored Nodes and Links.

Example

apiVersion: c9s.run/v1alpha1
kind: Topology
metadata:
  name: two-nodes
spec:
  definition:
    containerlab: |
      name: two-nodes
      topology:
        nodes:
          srl1:
            kind: nokia_srlinux
            image: ghcr.io/nokia/srlinux:26.3
          client:
            kind: linux
            image: ghcr.io/srl-labs/network-multitool:latest
        links:
          - endpoints:
              - srl1:e1-1
              - client:eth1

Existing Topology settings for exposure, deployment, image pulling, and status probes are translated into the generated primitive resources and NodeProfile policy.

Native definition vocabulary

The embedded containerlab definition remains native containerlab YAML, and compilation is fail-closed: a field c9s does not implement fails compilation with a structured diagnostic naming the field and its source line. Diagnostics are collected and sorted, so one compile reports every problem at once, and no resource is created until all of them are resolved. Deliberately rejected fields each fail with a diagnostic stating why, and only constructs whose loss cannot change lab behavior inside the cluster are accepted with a warning instead; see Differences from containerlab for the full list. Malformed YAML and recognized fields with invalid value types also fail compilation.

Containerlab node labels have a Kubernetes-native destination:

topology:
  nodes:
    srl1:
      kind: nokia_srlinux
      labels:
        owner: roman

The compiler carries these labels onto the generated Node's metadata.labels, then the Node controller copies them to the device Deployment and its Pods. They inherit from defaults and kinds like env, so Pods can be selected with kubectl get pods -l owner=roman. There is no Node.spec.labels; labels in the embedded definition are converted to Kubernetes metadata. Invalid Kubernetes labels and c9s-owned namespaces or identity/selector keys fail compilation before any resource is emitted. Two reserved source directives are consumed into Node intent instead of becoming metadata:

  • c9s.run/exposePorts becomes Node.spec.ports. It requests c9s Service reachability for internal destination ports without using native Containerlab ports bindings, which would publish ports on the local Docker host; see portable topologies.
  • c9s.run/appProtocols becomes Node.spec.appProtocols. It replaces or suppresses application hints on selected expose Service ports without selecting additional ports or changing the device plan.

For example, a Node label can describe gNMI configured for cleartext HTTP/2 and suppress the default HTTPS hint:

labels:
  c9s.run/appProtocols: "57400/TCP=kubernetes.io/h2c,443/tcp="

The compiler normalizes this to entries keyed by 57400/tcp and 443/tcp, preserving the empty value as suppression. The directive inherits through defaults, kinds, groups, and Node labels; the more specific label replaces the entire inherited directive. Malformed entries, invalid names, and duplicate ports after normalization fail compilation. Neither directive is copied to Kubernetes metadata, and NodeProfile policy still determines exposure.

See application-protocol hints for the complete default mapping, direct Node syntax, IANA and Kubernetes naming rules, and TLS pass-through guidance. Port 22 remains ssh when used for SSH File Transfer Protocol (SFTP); application hints do not configure TLS or add a separate SFTP endpoint.

Reconciliation lifecycle

The compiler:

  1. validates and expands the source definition
  2. reconciles NodeProfiles and Links before Nodes
  3. corrects drift on generated resources
  4. prunes generated resources removed from the source
  5. aggregates bounded readiness and resource counts into Topology status

status.observedGeneration records the Topology generation whose compiled resources were last applied, so the reported readiness refers to the current definition only once it equals metadata.generation. status.topologyState is deploying, running, degraded, or deployfailed.

Child-resource name conflicts

Generated Node, Link, and NodeProfile names are namespace-scoped and are not automatically prefixed with the Topology name. Before creating children, the controller checks that each desired name is available. If another resource already uses one of those names, the Topology remains unmaterialized and reports the conflicts in status.error, for example:

duplicate resources found in the lab namespace: link/frr1-eth1-frr2-eth1, node/frr1
create the topology in a different namespace or disambiguate node names.

Create the Topology in a different namespace or rename the conflicting nodes. Existing children owned by the same Topology are compatible and do not trigger this error.

When to use direct resources

A Topology still embeds the entire source lab in one Kubernetes object. For large or generated labs, create NodeProfile, Node, and Link manifests directly without persisting an aggregate Topology. See Nodes and Links for an example.

See the Topology reference for all fields.

On this page