Cloud Native Standards: CRI, CNI, CSI and OCI
CRI, CNI, CSI and OCI are not trivia - they are why you can swap Docker for containerd, Flannel for Calico, or one storage backend for another without Kubernetes noticing. Each one is visible on a running cluster in a single command.
Cloud Native Architecture Guide 29 of 46 Beginner
- Kubernetes1.36.4
- containerdv2.2.6
- CNICalico v3.25.0
- metrics-serverv1beta1.metrics.k8s.io
- TimeAbout 16 min
- Reviewed22 August 2026
Written against the versions above. The interfaces below are versioned standards and change slowly; the implementations plugged into them change constantly. That is the point of them.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| CKA1001 | 192.168.0.175 | Ubuntu 26.04 LTS | Control Plane Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE01 | 192.168.0.176 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE02 | 192.168.0.177 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE03 | 192.168.0.178 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
Before you start
- A running cluster, and SSH to a node for the two runtime-level commands.
-
CRI: the runtime is replaceable
The Container Runtime Interface is the gRPC API the kubelet speaks to whatever runs containers.
RuntimeName: containerdwithRuntimeApiVersion: v1is two facts: which implementation is plugged in, and which version of the contract it honours.This interface is why the
dockershimremoval in 1.24 was survivable. Kubernetes did not learn to speak to containerd - it already only spoke CRI, and Docker was the thing needing a shim to fit.bash Example session sudo -n crictl version | grep -E 'RuntimeApiVersion|RuntimeName'RuntimeName: containerdRuntimeApiVersion: v1Expected resultThe runtime name and the CRI API version it implements.
Success conditionYou can name your runtime and the interface version it satisfies.
-
CNI: the network is replaceable
The Container Network Interface is a much smaller contract: a binary, a JSON config, and two verbs -
ADDandDEL. When a sandbox is created, the runtime invokes the CNI plugin to give it an interface and an IP.Two networks are configured here.
k8s-pod-networkis Calico, doing the real work.cni-loopbackis the trivial plugin that gives every sandbox itslointerface.This is the layer that makes "my Pods have no IP" a CNI question rather than a Kubernetes question - and why the answer usually lives in
/etc/cni/net.d/rather than in any Kubernetes object.bash Example session sudo -n crictl info | python3 -c "import json,sys; d=json.load(sys.stdin); print('CNI networks:', [n['Config']['Name'] for n in d['cniconfig']['Networks']])"CNI networks: ['cni-loopback', 'k8s-pod-network']Expected resultThe configured CNI network names, Calico's among them.
Success conditionYou can list the CNI networks the runtime knows about.
-
CSI: storage is replaceable, and pluggable at runtime
The Container Storage Interface is the newest of the four and the most visible inside Kubernetes, because a CSI driver registers itself as an object you can list. Two are installed here:
hostpath.csi.k8s.iofor local volumes andcsi.tigera.io, which Calico uses for its own needs.StorageClasses are where CSI meets everyday use.
csi-hostpath-scnames a CSI provisioner;local-pathnamesrancher.io/local-path, which is not CSI at all. Both work, and the difference matters when you ask for a feature - snapshots, expansion, topology - that only the CSI path implements.Note
ALLOWVOLUMEEXPANSION: true for the CSI class, false for the other. That is the standard buying you a capability.bash Example session kubectl --context cka1001 get csidriversNAME ATTACHREQUIRED PODINFOONMOUNT STORAGECAPACITY TOKENREQUESTS REQUIRESREPUBLISH MODES AGEcsi.tigera.io true true false <unset> false Ephemeral 16hhostpath.csi.k8s.io true true false <unset> false Persistent,Ephemeral 8hkubectl --context cka1001 get storageclassNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGEcsi-hostpath-sc hostpath.csi.k8s.io Delete Immediate true 8hlocal-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 14hExpected resultTwo CSI drivers, and two StorageClasses with different provisioners and different capabilities.
Success conditionYou can tell which of your StorageClasses is CSI-backed.
-
OCI: the image is portable
The Open Container Initiative standardises two things: the image format and the runtime spec.
runc --versionreportsspec: 1.2.1- that is the OCI runtime spec, not runc's own version.The image side is why one tag serves every architecture. The pause image's manifest is a manifest list: one document naming per-platform manifests, and the runtime picks the entry matching the node. Five architectures here, from a single
registry.k8s.io/pause:3.10.1.So "it works on my machine" has a precise meaning: the same digest, resolved on a different platform, is a different set of bytes - deliberately.
bash Example session sudo -n ctr -n k8s.io content get $(sudo -n ctr -n k8s.io images ls name==registry.k8s.io/pause:3.10.1 | awk 'NR==2 {print $3}') | head -c 520{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 501, "digest": "sha256:e5b941ef8f71de54dc3a13398226c269ba217d06650a21bd3afcf9d890cf1f41", "platform": { "architecture": "amd64", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json",sudo -n ctr -n k8s.io content get $(sudo -n ctr -n k8s.io images ls name==registry.k8s.io/pause:3.10.1 | awk 'NR==2 {print $3}') | python3 -c "import json,sys; d=json.load(sys.stdin); print('mediaType:', d['mediaType']); print('platforms:', [m['platform']['architecture'] for m in d['manifests']][:8])"mediaType: application/vnd.docker.distribution.manifest.list.v2+jsonplatforms: ['amd64', 'arm', 'arm64', 'ppc64le', 's390x', 'amd64', 'amd64']Expected resultA manifest list media type, and the architectures it covers.
Success conditionYou can show one tag resolving to several platform manifests.
Troubleshooting
crictl infofails or returns nothing parseable.Why: crictl is talking to the wrong socket, or the runtime is down.
Fix:Write
/etc/crictl.yamlnaming the socket from/var/lib/kubelet/config.yaml, thensystemctl status containerd.get csidriversis empty on a working cluster.Why: Storage is provided by an in-tree or non-CSI provisioner.
Fix:Normal. Check
get storageclassand look at the PROVISIONER column -rancher.io/local-pathandkubernetes.io/*are not CSI.A Pod stays
ContainerCreatingwith a CNI error in its Events.Why: The CNI plugin failed
ADD- usually a missing config or an exhausted IPAM pool.Fix:Read
/etc/cni/net.d/on the node, then the CNI plugin's own logs. The API server cannot help you here; this failure is below it.