CNI, CSI and CRI

The three plugin interfaces that run containers, wire Pod networks and attach storage: where each lives on a node, which component calls it, and where it shows up when something breaks.

Architecture · 25%35 min2 tasks

After this lesson

  • 01You can find a node's container runtime, its endpoint and its configuration, and use crictl to inspect pods and containers below the API.
  • 02You can locate the CNI configuration and plugin binaries on a node and explain how a new Pod gets its network.
  • 03You can read CSIDriver, CSINode and StorageClass objects and map a storage, network or runtime symptom to the right interface.

The competency is Understand extension interfaces (CNI, CSI, CRI, etc.). Kubernetes does not run containers, build Pod networks or attach disks itself. The kubelet calls a container runtime through the CRI, the runtime calls network plugins through the CNI, and storage vendors plug in through the CSI. Exam tasks here are short and concrete: find a value on a node and write it to a file, use crictl where kubectl cannot reach, or recognize which interface a broken node or Pod is pointing at. On this lab a node shell is docker exec -it cka-worker bash; on the exam it is ssh <node>, usually followed by sudo -i.

InterfaceCalled byOn the nodeCluster objects
CRI (container runtime)kubelet, over gRPC on a Unix socketcontainerd: /run/containerd/containerd.sock, config /etc/containerd/config.toml. CRI-O: /var/run/crio/crio.sockRuntimeClass; the runtime and version in kubectl get nodes -o wide
CNI (network)the container runtime, each time it creates a Pod sandboxconfig in /etc/cni/net.d, binaries in /opt/cni/bin, plus the plugin's agent (here the calico-node DaemonSet)None required; plugins often add CRDs (Calico's crd.projectcalico.org)
CSI (storage)kubelet for mounts; sidecars such as external-provisioner and external-attacher for create and attachdriver sockets registered under /var/lib/kubelet/plugins_registry; a node plugin DaemonSet and a controller DeploymentCSIDriver, CSINode, VolumeAttachment, StorageClass.provisioner
Device pluginskubelet/var/lib/kubelet/device-plugins/kubelet.sockExtended resources such as nvidia.com/gpu in node capacity

CRI: the kubelet talks to a runtime, not to Docker

Since dockershim was removed in v1.24, every node runs a CRI runtime, almost always containerd or CRI-O. The kubelet finds it through containerRuntimeEndpoint in its config file /var/lib/kubelet/config.yaml; older kubeadm clusters may still pass the deprecated --container-runtime-endpoint flag in /var/lib/kubelet/kubeadm-flags.env. crictl talks to the same socket (set in /etc/crictl.yaml) and shows what the runtime sees, which is what you need when the API server or kubelet is down. kubeadm clusters use the systemd cgroup driver, so containerd's runc options set SystemdCgroup = true. containerd 2.x keeps CRI settings under plugins.'io.containerd.cri.v1.runtime'; files written for containerd 1.x use version = 2 and plugins."io.containerd.grpc.v1.cri", and containerd config dump prints the effective result either way.

Runtime facts and crictl, from a node shellbash
docker exec -it cka-worker bash                 # exam: ssh <node>, then sudo -i

grep containerRuntimeEndpoint /var/lib/kubelet/config.yaml
cat /var/lib/kubelet/kubeadm-flags.env           # extra kubelet flags kubeadm wrote
cat /etc/crictl.yaml                             # runtime-endpoint crictl uses
systemctl status containerd --no-pager
grep -nE 'SystemdCgroup|sandbox|snapshotter' /etc/containerd/config.toml
containerd config dump | grep -A4 "cri.v1.runtime'.cni]"

crictl pods --namespace kube-system              # pod sandboxes (POD ID)
crictl ps -a --name coredns                      # containers, including exited ones (CONTAINER)
crictl ps --name web --no-trunc -q               # full 64-character IDs only
crictl inspect <container-id> | grep -m1 '"pid"'
crictl logs --tail 20 <container-id>
crictl images
crictl pull busybox:1.36

CNI: how a Pod gets its network

When the kubelet asks the runtime for a new Pod sandbox, containerd creates a network namespace, reads the first file in lexical order in /etc/cni/net.d (.conflist, .conf or .json), and executes the binaries named by each plugin's type from /opt/cni/bin. The kubelet does not load CNI itself any more; it only reports what the runtime says, which is why a node without a CNI config stays NotReady with cni plugin not initialized. A network add-on like Calico ships as a DaemonSet whose init containers drop the binaries and the config onto every node, and whose agent programs routes and NetworkPolicy. A leftover file such as 05-old.conf sorts before 10-calico.conflist and wins, a classic cause of Pods getting the wrong network.

The CNI pieces on this labbash
docker exec cka-worker ls -l /etc/cni/net.d       # 10-calico.conflist and calico-kubeconfig
docker exec cka-worker cat /etc/cni/net.d/10-calico.conflist   # plugins: "calico" (ipam "calico-ipam"), then "portmap"
docker exec cka-worker ls /opt/cni/bin            # calico, calico-ipam, host-local, loopback, portmap, ...
docker exec cka-worker tail -5 /var/log/calico/cni/cni.log
kubectl -n kube-system get ds calico-node
kubectl -n kube-system get pods -l k8s-app=calico-node -o wide
kubectl describe node cka-worker | grep -iE 'networkunavailable|ready'

CSI: storage drivers outside the tree

A CSI driver usually has two parts: a node plugin DaemonSet that registers its socket with the kubelet and performs mounts, and a controller Deployment whose sidecars watch PVCs and VolumeAttachments to create, attach, resize and snapshot volumes. A StorageClass names the driver in provisioner (for example ebs.csi.aws.com). The CSIDriver object describes the driver's capabilities, and each node's CSINode lists the drivers registered there. Not every provisioner is CSI: this lab's standard class uses rancher.io/local-path, an external provisioner that creates hostPath-backed volumes, so kubectl get csidrivers returns nothing here and every CSINode shows zero drivers.

Storage plumbing, read from the APIbash
kubectl get csidrivers
kubectl get csinode
kubectl get csinode cka-worker -o jsonpath='{.spec.drivers[*].name}'
kubectl get storageclass -o custom-columns=NAME:.metadata.name,PROVISIONER:.provisioner,BINDING:.volumeBindingMode
kubectl get volumeattachments
kubectl -n local-path-storage get deploy,pods        # the provisioner behind "standard"
kubectl describe pvc <name> | tail -5                # "waiting for a volume to be created by ..." names the provisioner
SymptomInterfaceFirst place to look
Node NotReady, condition message cni plugin not initializedCNI/etc/cni/net.d on that node; the CNI DaemonSet Pod on that node
Pod stuck in ContainerCreating, event failed to setup network for sandboxCNIThe CNI agent Pod on that node and its logs; the plugin's own log file
Node NotReady, kubelet log says the runtime socket is missing or refusedCRIsystemctl status containerd; the endpoint path in the kubelet config
PVC Pending, waiting for a volume to be createdCSI or external provisionerThe provisioner's Pods and logs; the StorageClass provisioner name
Pod in ContainerCreating with FailedMount or FailedAttachVolumeCSIkubectl get csinode, volumeattachments, the node plugin Pod on that node

In the docs · the tab you get on exam day

Taskar-ext-1
Architecture4%~5 min

context · kubectl config use-context kind-cka

Record a node's CNI and runtime configuration

Inspect node cka-worker (lab: docker exec -it cka-worker bash; exam: ssh cka-worker) and write these answers to files on your workstation under /tmp/cka/ext/: in cni-config.txt, the file name (without its directory) of the CNI network configuration the container runtime loads on that node; in cni-plugin.txt, the type of the first plugin in that configuration; in runtime-endpoint.txt, the container runtime endpoint the kubelet on that node is configured to use.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ext-1/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ext-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ext-1/cleanup | bash
Taskar-ext-2
Architecture4%~6 min

context · kubectl config use-context kind-cka

Find a container below the API with crictl

Pod storefront in namespace arx-ext runs two containers. On the node where it runs, use crictl to find the ID of the Pod's sandbox and write it to /tmp/cka/ext/storefront-sandbox.txt, then find the ID of its log-agent container and write it to /tmp/cka/ext/log-agent-id.txt. Write the full, untruncated IDs.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ext-2/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ext-2/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ext-2/cleanup | bash

Finished reading?

0 of 2 tasks passed. Readiness counts tasks, so run them before you move on.