In a few words Container Runtime Interface is the answer to this questions. But we are going a little bit deeper. First of all what is CRI ? CRI is one of the most mature interface in kubernetes, it's a bridge between kubelet and container runtime. k8-diagram. Creating such interfaces in k8s it's a way to shrink codebase by providing only set of rules which selected implementation of interface should fullfill. One of this example is docker, which is used as default CRI in kubelet.

$ kubelet --help
--container-runtime string
The container runtime to use. Possible values: 'docker', 'remote', 'rkt(deprecated)'. (default "docker")

So more precisely how kubelet is interacting with docker ? kubelet_docker

As we seen there is a layer between docker and kubelet called dockershim, it is defined in: dockershim Ok, but how it looks like in real k8s installation deployed by kubespray with k8s version 1.14.3:

# lsof -p $(pgrep kubelet) -i | grep -i docker
kubelet    2539            root    5u     unix 0xffff92c54bd96c00       0t0 843182256 /var/run/dockershim.sock type=STREAM
kubelet    2539            root   16u     unix 0xffff92c62b970400       0t0 843180363 /var/run/dockershim.sock type=STREAM
kubelet    2539            root   18u     unix 0xffff92c54bd95000       0t0 843180365 /var/run/dockershim.sock type=STREAM

so it's a unix socket, created by kubelet: docker_server.go On a diagram we see that kubelet is talking to dockershim, communication is done by making GRPC requests. GRPC protocol is using Protocol Buffers for serializing data, structure of this data is defined in proto file, proto file for communication with CRI is defined: api.proto for testing we can write simple code, just to list all the images:

package main

import (

    pb "test/src/v1alpha2"


const (
    addr = "unix:///var/run/dockershim.sock"

func main() {
    ctx := context.Background()
    newclient, err := NewImageService()
    if err != nil {
        fmt.Printf("error with client: %v", err)
    images, err := newclient.ListImages(ctx, &pb.ListImagesRequest{})
    if err != nil {
        fmt.Printf("error getting images: %v", err)
    for _, img := range images.Images {


func NewImageService() (pb.ImageServiceClient, error) {
    conn, err := grpc.Dial(addr, grpc.WithInsecure())
    return pb.NewImageServiceClient(conn), err

to make it happen I also need to transform proto file into golang code by using tool protoc. Now it's proven that GRPC is used to talk with dockershim. Dockershim is talking to docker using standard docker libraries: docker_container.go so using normal docker command we can list all the containers, images, etc. Interacting kubelet with docker is a longer way, than proposed in In the next blog post I will try to swap docker with just simpler containerd.