Build artifact 1: ADD wires a netns, DEL unwires it, and cnitool is the judge¶
Artifact — build/07-cni-academy/: a CNI plugin in Go whose ADD creates a veth pair, moves one end into the sandbox netns, addresses it, sets a default route through the node bridge, and returns a spec-conformant result; whose DEL removes all of that and is idempotent; and whose CHECK says whether the wiring it claims is actually present. Build artifact 1 of the phase's two.
Rests on — exercise 7 for the harness (which must already be green against the reference bridge plugin), exercise 3 for the input contract, and exercise 1's table for the list of steps. Every command in that table's what a CNI must do column becomes a function here; nothing in it is re-derived.
Topology — none. All of this is forge and a netns. pair is up and idle, and stays untouched until exercise 10.
Build
build/07-cni-academy/
cmd/academy/main.go skel.PluginMain wiring, version support, and nothing else
pkg/conf/conf.go the NetConf struct, its JSON decode, and its defaults
pkg/wire/bridge.go the node bridge: create-if-absent, up, gateway address
pkg/wire/veth.go the pair, the move into the netns, address, routes
pkg/wire/result.go assembling types100.Result from what was actually configured
The IPAM half is exercise 9 and is deliberately not in this list; for now ADD may take the address from a "testAddress" field in the config so that the wiring can be finished and judged on its own.
What it must satisfy, as an interface rather than an implementation:
-
skel.PluginMainwith all four commands and a version list.ADD,DEL,CHECKandGCare dispatched by the library; the version list is whatcnitoolnegotiates against, and a plugin that advertises a version it does not implement fails in a way that looks like a config error. -
The config is read from stdin, once, and never from a file. A plugin that opens
/etc/cni/net.ditself is a plugin that behaves differently undercnitooland under the kubelet, which destroys the only fast loop this artifact has. -
The netns is entered exactly twice and never held. Create the pair in the host namespace, move one end in, then do all in-namespace work inside a single
ns.WithNetNSPathclosure. Holding a netns handle across the return is how a plugin leaks a file descriptor per pod. -
The interface name inside the sandbox comes from
CNI_IFNAME, not from a constant. It iseth0in every Kubernetes invocation you will ever see, which is exactly why hard-coding it survives testing and fails the one time it matters. -
The result names both interfaces and gives the sandbox one an
ipsentry with agateway. The runtime readsipsto populate the pod's status; a result that configures a working network and reports an emptyipsproduces a pod with connectivity and nopodIP, which is a fault exercise 10 will show you from the API side. -
DELsucceeds when there is nothing to delete. No netns, no veth, no address: exit 0, no output. This is a spec requirement and it is also the single most valuable line of defensive code in the plugin, because it is what stops a failedADDfrom wedging a pod inTerminatingforever. -
CHECKasserts rather than assumes. It must look at the sandbox and report a mismatch — which means it is the one command that can catch aDELthat half-worked.
Build and install into the harness's own bin directory:
cd ~/src/k8s-academy/build/07-cni-academy
go build -o /opt/cni/lab-bin/academy ./cmd/academy
sudo tee /etc/cni/lab-net.d/10-harness.conflist >/dev/null <<'JSON'
{
"cniVersion": "1.0.0",
"name": "harness",
"plugins": [
{ "type": "academy", "bridge": "br-academy", "subnet": "10.98.0.0/24",
"gateway": "10.98.0.1", "testAddress": "10.98.0.20/24" }
]
}
JSON
Gate — cnitool ADD/DEL against a netns, which the strand lists as an objective harness. It is objective for a specific reason worth stating: cnitool validates the result JSON against the spec's schema for the negotiated version, so a result that is nearly right is rejected by somebody else's code rather than accepted by your own eyes.
sudo -i
export CNI_PATH=/opt/cni/lab-bin NETCONFPATH=/etc/cni/lab-net.d
ip netns add a1
cnitool add harness /var/run/netns/a1 | jq .
ip netns exec a1 ip -br addr show; ip netns exec a1 ip route
ip netns exec a1 ping -c2 10.98.0.1
cnitool check harness /var/run/netns/a1 && echo CHECK-OK
cnitool del harness /var/run/netns/a1
cnitool del harness /var/run/netns/a1 && echo SECOND-DEL-OK
ip netns exec a1 ip -br addr show
Expect — the same shape of output as the reference plugin produced in exercise 7, a ping to the gateway, CHECK-OK, and SECOND-DEL-OK. Then make the gate fail on purpose, twice, because a gate nobody has watched fail is not evidence:
- Return the result with
ipsomitted.cnitool addmust reject it. Note the error text; you will see it again in exercise 12 wearing kubelet's clothes. - Skip the
ip link set eth0 upinside the netns.cnitool addsucceeds — nothing in the result is wrong — andpingfails. Thencnitool checkcatches it. That asymmetry is the argument for having writtenCHECKat all.
Write down — the ADD handler's steps in order, each annotated with the exact command from exercise 1's part 1 that it replaces, and the two steps that have no by-hand equivalent because P0 never needed them (reading a config off stdin, and reporting a result). That mapping is the checklist's 7.2 written artifact.
Footprint note — a small Go binary; the build is nothing next to the measured figures. One bridge and one veth pair per run on forge.
Teardown
The harness stays — exercise 9 drives it thirty more times.