Concepts
This page explains the core ideas behind KubeNDT. They map directly onto the API Reference, so understanding them makes the endpoints self-explanatory.
Topology
A topology is a declarative JSON description of a virtual network:
- Nodes - each node is a container image (a host, router, switch, or any image you like) deployed as a Kubernetes StatefulSet, optionally with multiple replicas.
- Links - point-to-point connections between node interfaces, realized as L2/L3 overlays via the Meshnet CNI.
An abbreviated topology looks like this:
{
"nodes": [
{"name": "pc", "image": "alpine", "type": "host", "replicas": 4},
{
"name": "r1", "image": "quay.io/frrouting/frr:10.7.1",
"type": "router", "driver": "FRRRouterDriver"
}
],
"links": [
{
"node": "pc-0", "localIntf": "eth1",
"localIp": ["10.0.1.11/24", "fd00:1::11/64"],
"peerNode": "r1-0", "peerIntf": "eth1",
"delay": "10ms", "bandwidth": "50mbit"
}
]
}
A link can carry delay, jitter, loss and bandwidth (tc units, delay
one-way per direction), which the platform applies on both ends once the link
is wired. See the reference topologies
for complete, runnable examples.
Addresses are IPv4 or IPv6, and both ends of a link share a family. localIp
and peerIp take one address or an array, for dual stack or a second address:
the first is wired with the link and the platform sets the rest. NAT stays
IPv4 only.
Every link gets the same MTU once it is wired, 1450 by default
(KUBENDT_LINK_MTU on the backend), so throughput and OSPF adjacencies don't
depend on whether Meshnet built a veth or a VXLAN. A link can declare its own
mtu. Cluster setup explains the default and
how to go higher.
Topologies are reproducible: you can save and re-import the JSON to recreate the exact same network.
The Meshnet dependency
Links are wired by the Meshnet CNI, so KubeNDT tracks whether its dataplane
is actually running and reports it as ok, degraded, missing or unknown -
cluster-wide and per Kubernetes node, both in the dashboard and on the
cluster and pods API responses.
Because a missing dataplane means topology objects get created but never wired -
a silent failure - deploying or modifying a topology returns 412 Precondition Failed (reason: meshnet_missing) instead of leaving pods unwired. Only
missing blocks: degraded and unknown pass through. Clearing a topology and
deleting a namespace are always allowed, and ?force=true overrides the check.
Pod placement and link realization
A node can pin its pods with nodeSelector (labels a worker must carry) or
nodeName (one worker, by name) in the topology JSON. Both are validated
against the cluster before anything is created, so a typo fails fast with a
400 instead of pods left Pending. Placement applies to every replica of the
node: with nodeSelector the scheduler still spreads them over the matching
workers, with nodeName they all land on that worker. To pin instances to
different workers, declare them as separate nodes.
Where the two ends of a link land decides how Meshnet realizes it: a veth
pair when both pods share a worker, a VXLAN tunnel across workers. The link
panel in the dashboard shows it, and GET /network/links/{namespace} lists
every link with the worker behind each endpoint and its realization.
Nodes also accept terminationGracePeriodSeconds. The platform default is 2 s
instead of the Kubernetes 30 s, since emulated nodes are stateless and a shell
or a sleep infinity as the main process ignores SIGTERM. Raise it per node for
workloads that need an orderly shutdown.
Namespaces
Every topology lives in a Kubernetes namespace labelled
kubendt/enabled=true. Namespaces provide isolation: you can run multiple
independent topologies side by side, each with its own nodes, links, and
configuration. The API exposes namespace lifecycle (create / list / delete) and
an aggregated operational summary per namespace.
Drivers and capabilities
KubeNDT abstracts network configuration behind a driver and capability system. Instead of issuing image-specific commands, you declare what you want (assign this IP, add this route, enable OSPF, set up NAT…) and the driver for that node type translates it into the right commands.
- A driver knows how to operate a particular node type (e.g. a plain Linux host, an FRRouting router, or a VyOS appliance).
- A capability is a declarative action a driver supports (L2 interface ops, L3 addressing/routing, routing protocols, NAT…).
This is why the same network/configure request works across heterogeneous
nodes - the declarative intent is constant; the driver fills in the details.
Every action is validated before it runs: a missing or malformed field fails
the action with the field named, and nothing reaches the pod. GET /drivers
lists each driver's actions with their parameters. Traffic control and
custom commands are not tied to a driver and work on every node.
A driver tied to an image can also prepare it: an FRR router on the stock
image needs no startup commands, since its driver enables the daemons behind
its capabilities (OSPFv2, OSPFv3, RIP) and starts FRR.
To support a new node type, see writing a driver.
Traffic control and throughput
Any interface of any node can be shaped, whether or not its image ships tc:
KubeNDT falls back to a toolbox container injected into the pod. A shaping
action takes a netem (delay, jitter, loss with an optional bursty model,
duplicate, corrupt, reorder, rate), a tbf, or an htb class tree that splits
a link rate into classes with their own matches and leaf queue. A links
section in the configure request shapes a link by one of its ends, mirrored on
the peer or not, and GET /pods/tc/{namespace}/{pod} reads the state back.
To measure the result, GET /iperf/run/{namespace}/{pod} runs an iperf3 test
from a node toward an address in the namespace, TCP or UDP, with the server
started on the destination node or one the lab already runs. The dashboard
runs the same test from the node menu and plots one bar per second. Both the
throughput test and the traceroute take IPv6 targets, except TCP traceroute,
which stays IPv4. The
all-features example
walks through shaping a link and watching the bandwidth drop.
Configuration and persistence
Operations applied through the configuration API are persisted per pod and replayed whenever KubeNDT recreates that pod: the Restart action, a modify that restarts a neighbour, or the heal pass that repairs interfaces after an operation. Meshnet also recreates the interfaces on the pod's neighbours, so their persisted operations on those interfaces (bridge membership, addresses, routes, qdiscs) are re-applied as well.
The history keeps the last word on each piece of state, so a set, its undo and
the same set again all run. An action sent with options.persist_history: false
runs once and is never replayed. The link MTU and the extra addresses of a link
are not operations at all: they belong to the topology and the platform writes
them again after every wiring.
Replay only covers recreations that KubeNDT drives. A pod recreated behind its
back (kubectl delete pod, an eviction, a worker failure) comes back with the
CRD-declared addresses only: trigger a Restart on it to run the full recovery.
The history is dropped when the namespace, the topology or the node is deleted.
The configuration applied in a namespace can be exported as a configure request
(GET /network/export-conf/{namespace}, or Export network conf in the
dashboard), ready to load into another namespace. It does not carry the MTU or
the extra addresses, which travel with the topology export.
Manual changes made directly inside a pod (ad-hoc shell commands not sent through the KubeNDT API) are intentionally not tracked for replay. Persist state by applying changes through the API, mounted config files, or startup scripts.
See Operations for what survives a backend or cluster failure and what stays manual.
Files and mounts
Each namespace has a file manager: upload or write text files there and
mount them into nodes at a given path. Files mount read-only as ConfigMaps, or
as Secrets when flagged sensitive, and pods need a restart to see edits. Size
cap is 1 MiB, UTF-8 text only. Full model, API surface and caveats in the
file manager guide.
Authentication
The dashboard and API are gated by a password login (which opens a browser session) plus revocable API tokens for scripts and CI. Auth can be disabled on trusted networks. See Getting started to set it up.
The dashboard and the API
Everything you can do in the React dashboard is backed by the REST API. The dashboard is one client; your scripts and CI can be another. The API Reference documents every endpoint, grouped by area:
auth · cluster · namespaces · pods · network · drivers ·
files · shell · capture · trace · iperf · kubeconfig · system