CRI-O is a lightweight container runtime specifically designed for Kubernetes, and using it with Minikube lets you see how Kubernetes interacts with a runtime that prioritizes its needs.
Here’s how to set up Minikube with CRI-O and see it in action:
First, you’ll need to install CRI-O itself. The installation process varies slightly by operating system, but the core idea is to get the CRI-O binary and its systemd service file in place.
On Debian/Ubuntu-based systems, you might add a repository and install it like this:
# Add CRI-O repository
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_22.04/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_22.04/Release.key | sudo apt-key add -
sudo apt update
sudo apt install -y cri-o
# Start and enable the CRI-O service
sudo systemctl daemon-reload
sudo systemctl enable crio
sudo systemctl start crio
Verify CRI-O is running:
sudo systemctl status crio
You should see output indicating the service is active and running.
Now, you can tell Minikube to use CRI-O. This is done via the --container-runtime flag when starting Minikube. You’ll also want to specify the Kubernetes version you intend to use.
minikube start --container-runtime=cri-o --kubernetes-version=v1.28.0
Minikube will now provision a Kubernetes cluster within a virtual machine (or Docker container, depending on your driver), but instead of using its default container runtime (usually Docker), it will instruct the Kubernetes components (like the kubelet) to use the CRI-O runtime you installed.
Let’s deploy a simple application to see CRI-O in action. We’ll use a basic Nginx deployment.
Create a file named nginx-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Apply the deployment:
kubectl apply -f nginx-deployment.yaml
Now, check your pods:
kubectl get pods
You should see two nginx-deployment pods in a Running state.
To confirm that CRI-O is indeed managing these containers, you can exec into one of the pods and check the process tree or use crictl, CRI-O’s command-line interface for interacting with the CRI.
First, get the container ID of one of your Nginx pods:
POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath='{.items[0].metadata.name}')
CONTAINER_ID=$(kubectl get pod $POD_NAME -o jsonpath='{.status.containerStatuses[0].containerID}')
# The container ID will look something like: docker://abcdef123456... or cri-o://abcdef123456...
# We need to extract the actual ID part after the runtime prefix.
RUNTIME_ID=$(echo $CONTAINER_ID | cut -d'/' -f3)
Now, use crictl to inspect the container:
sudo crictl inspect $RUNTIME_ID
This command will output detailed information about the container, including its configuration, status, and the image it’s running. The fact that you can inspect it using crictl is proof that CRI-O is the underlying runtime.
You can also check the images pulled by CRI-O:
sudo crictl images
You should see the nginx:latest image listed.
The primary benefit of using CRI-O with Kubernetes is its focus on the Container Runtime Interface (CRI). Unlike Docker, which is a full-featured container engine with its own daemon and API, CRI-O is a minimal implementation that only speaks CRI. This means Kubernetes directly communicates with CRI-O for container lifecycle management (pulling images, starting, stopping, deleting containers) without an intermediate layer.
This direct interaction leads to a simpler, more Kubernetes-native stack. The kubelet on your Minikube node talks directly to CRI-O’s socket, which then instructs the underlying container technology (like runc or crun) to create and manage the containers.
One of the things most people don’t realize is how much simpler the Kubernetes control plane becomes when it doesn’t have to manage a separate container runtime daemon. The kubelet is the only component that needs to understand the CRI. All the complexity of container orchestration, image management, and networking is handled by Kubernetes itself, delegating the actual container execution to a CRI-compliant runtime like CRI-O. This separation of concerns is a core tenet of Kubernetes’s design.
If you encounter issues, the most common one is that CRI-O isn’t running or isn’t properly configured to be picked up by the kubelet. Check sudo systemctl status crio and ensure the kubelet logs (often found via journalctl -u kubelet) show successful communication with CRI-O.
The next thing you’ll likely want to explore is how different network plugins behave when using CRI-O, as the networking setup can sometimes be sensitive to the container runtime.