Architecture
Big picture
CRI-O runs as one daemon, crio. The entry point is cmd/crio/main.go, which builds a urfave/cli v2 app and brings up a gRPC server multiplexed with cmux (cmd/crio/main.go:1-60). The kubelet connects over a Unix socket and issues CRI calls. Those calls land on the Server type, which implements both CRI services and delegates execution to an OCI runtime, storage to containers/storage, and networking to CNI.
Components
Daemon and CRI server (cmd/crio, server/)
cmd/crio/main.go is the process entry point. The CRI surface lives in server/, where one Server struct implements both the RuntimeService and the ImageService (server/server.go:68-104). The per-RPC handlers are split across files such as container_*.go, sandbox_*.go, and image_*.go. Server embeds *lib.ContainerServer, the core that holds the runtime, the store, and the storage runtime server (server/server.go:69-70).
OCI runtime abstraction (internal/oci/)
internal/oci/ hides which runtime actually runs a container behind the RuntimeImpl interface, which declares the full container lifecycle: create, start, exec, stop, checkpoint, and more (internal/oci/oci.go:60-86). There are three implementations: runtime_oci.go for conmon plus runc or crun, runtime_pod.go for conmonrs, and runtime_vm.go for VM runtimes like Kata. A Runtime keeps a runtimeImplMap from handler name to implementation (internal/oci/oci.go:95-98), so a pod's runtime handler selects the implementation.
Core library and storage (internal/lib/, internal/storage/)
internal/lib/ holds ContainerServer and the sandbox package with the Sandbox type, one per pod. internal/storage/ wraps containers/storage for image pulls, layers, and container rootfs. Networking is handled through CNI from server/sandbox_network_linux.go, with host port mapping in internal/hostport/.
How a request flows
RunPodSandbox is the call the kubelet makes to start a pod. The trace:
RunPodSandbox(server/sandbox_run.go:68) hands off to the platform implementationrunPodSandbox(server/sandbox_run_linux.go:409).- A sandbox is built and
GenerateNameAndID()assigns the OCI name<ns>-<name>-<attempt>and an ID (server/sandbox_run_linux.go:413-438). - The pod name is reserved, idempotently returning an existing sandbox if present, and cleanup steps are pushed onto a
resourceCleanerfor rollback (server/sandbox_run_linux.go:440-468). - Unless the pod uses host networking, CRI-O waits for the CNI plugin to be ready (
server/sandbox_run_linux.go:472-476). - The pause (infra) image is created in storage via
StorageRuntimeServer().CreatePodSandbox(...), withErrDuplicateNamehandled explicitly (server/sandbox_run_linux.go:535-547). - The infra container is created with
oci.NewContainer(...), orNewSpoofedContainerfor VM and pod runtime types (server/sandbox_run_linux.go:1294-1310). createAndStartInfraContainerruns PreStart hooks, emits aCONTAINER_CREATEDevent, then callsRuntime().StartContainerand persists state to disk (server/sandbox_run_linux.go:1350-1372).- Networking is brought up with
s.networkStart(ctx, sb)to obtain the IP and CNI result (server/sandbox_run_linux.go:1489). - The rootfs is mounted with
StorageRuntimeServer().StartContainer(sboxID)(server/sandbox_run_linux.go:1587).
If any step fails mid-creation, the deferred resourceCleaner unwinds the prior steps in LIFO order (server/sandbox_run_linux.go:444-453).
Key design decisions
CRI-O does not fork containers itself. The OCI create path does not exec runc directly; it launches the monitor daemon conmon, passing -r <runtime path> and --runtime-arg root=<root> so conmon invokes runc on its behalf (internal/oci/runtime_oci.go:145-160,217). Because conmon is the container's parent rather than crio, restarting the CRI-O daemon does not kill running containers. conmon owns stdio, the log, the exit code, terminal allocation, and OOM handling.
The second decision is the runtime abstraction itself. By routing every lifecycle call through RuntimeImpl (internal/oci/oci.go:60-86) and selecting the implementation by RuntimeType per handler (internal/oci/oci.go:184), the same CRI path serves conmon-and-runc, conmonrs, and Kata VMs without branching in the server layer.
A third is parallel-pull coalescing. Server keeps pullOperationsInProgress keyed by image plus credentials, guarded by pullOperationsLock, so concurrent pulls of the same image block on one operation instead of racing (server/server.go:84-126).
Extension points
- Runtime handlers: each
config.RuntimeHandlernames aRuntimePath,MonitorPath, andRuntimeType, letting operators register runc, crun, conmonrs, or a VM runtime and select it per pod (internal/oci/oci.go:108-124). - NRI: the Node Resource Interface plugin surface lives in
internal/nri/, exposed onServerasnri *nriAPI(server/server.go:99). - Hooks: OCI runtime hooks are resolved per sandbox through
hooksRetriever(server/server.go:101). - CNI: networking is any CNI plugin, called from
server/sandbox_network_linux.go.