简介

CRI是什么

  • 容器运行时插件(Container Runtime Interface,简称 CRI)是 Kubernetes v1.5 引入的容器运行时接口,它将 Kubelet 与容器运行时解耦,将原来完全面向 Pod 级别的内部接口拆分成面向 Sandbox 和 Container 的 gRPC 接口,并将镜像管理和容器管理分离到不同的服务。
  • CRI主要定义了两个grpc interface
    • RuntimeService:容器(container) 和 (Pod)Sandbox 运行时管理
    • ImageService:拉取、查看、和移除镜像
  • OCI (开放容器标准): 定义了ImageSpec(镜像格式, 比如文件夹结构,压缩方式)和RuntimeSpec(如何运行,比如支持create, start, stop, delete)
    • 代表实现有:runC,Kata(以及它的前身runV和Clear Containers),gVisor
  • CRI 区别于OCI,CRI的定义比较简单直接,只是定义了一套协议(grpc 接口)。

    • 代表实现有 kubernetest 内置的 dockershim, CRI-containerd(或者 containerd with CRI plugin), cri-o

      CRI位于什么位置

      在 kubernetes 中:
      1.png
      2.png
      在和OCI,调度层的角度看:
      1. graph LR
      2. OrchestrationAPI --> ContainerAPI-criRuntime
      3. ContainerAPI-criRuntime --> KernelAPI-ociRuntime

      CRI/CRI Runtime

      CRI Runtime的执行流程

      经典的kubernetes runtime执行流程

      3.png
  • 执行流程里面核心组件是kubelet/KubeGenericRuntimeManager他调用很多其他组件,比如cm (ContainerManager/podContainerManager/cgroupManager/cpuManager/deviceManager, pod级别的资源管理), RuntimeService(grpc调用CRI的客户端) 共同完成SyncPod的操作。

    经典的dockershim -> containerd的流程 (称为docker cri)

    4.png

  1. Kubelet通过CRI接口(gRPC)调用dockershim,请求创建一个容器。CRI即容器运行时接口(Container Runtime Interface),这一步中,Kubelet可以视作一个简单的CRI Client,而dockershim就是接收请求的 Server。目前dockershim的代码其实是内嵌在Kubelet 中的,所以接收调用的凑巧就是Kubelet进程。
  2. dockershim收到请求后,转化成Docker Daemon能听懂的请求,发到Docker Daemon上请求创建一个容器。
  3. Docker Daemon早在1.12版本中就已经将针对容器的操作移到另一个守护进程——containerd中了,因此 Docker Daemon仍然不能帮我们创建容器,而是要请求containerd创建一个容器;
  4. containerd收到请求后,并不会自己直接去操作容器,而是创建一个叫做containerd-shim的进程,让 containerd-shim去操作容器。这是因为容器进程需要一个父进程来做诸如收集状态,维持stdin等fd打开等工作。而假如这个父进程就是containerd,那每次containerd挂掉或升级,整个宿主机上所有的容器都得退出了。而引入了containerd-shim就规避了这个问题(containerd和 shim并不是父子进程关系);
  5. 我们知道创建容器需要做一些设置namespaces和cgroups,挂载root filesystem等等操作,而这些事该怎么做已经有了公开的规范了,那就是OCI(Open Container Initiative,开放容器标准)。它的一个参考实现叫做runC。于是,containerd-shim在这一步需要调用runC这个命令行工具,来启动容器;
  6. runC启动完容器后本身会直接退出,containerd-shim则会成为容器进程的父进程,负责收集容器进程的状态,上报给containerd,并在容器中pid为1的进程退出后接管容器中的子进程进行清理,确保不会出现僵尸进程。

    直接对接 cri-containerd/cri-o 的运行时

    5.png
    6.png

使用CRI-Containerd的调用流程更为简洁, 省去了上面的调用流程的 1,2 两步。

常见CRI runtime实现

7.png

CRI-Containerd

9.png
执行流程为:

  1. Kubelet通过CRI runtime service API调用cri plugin创建pod。
  2. cri通过CNI创建pod的网络配置和namespace。
  3. cri使用containerd创建并启动pause container (sandbox container) 并且把这个container置于pod的 cgroups/namespace。
  4. Kubelet接着通过CRI image service API调用cri plugin,获取容器镜像。
  5. cri通过containerd获取容器镜像。
  6. Kubelet通过CRI runtime service API调用cri,在pod的空间使用拉取的镜像启动容器。
  7. cri通过containerd创建/启动/应用容器,并且把container置于pod的cgroups/namespace. Pod完成启动。

    实践

    准备集群

    使用terraform在腾讯云上创建tke测试集群。
    # Configure the TencentCloud Provider
    provider "tencentcloud" {
    secret_id  = var.secret_id
    secret_key = var.secret_key
    region     = var.region
    }
    # test cluster
    resource "tencentcloud_kubernetes_cluster" "managed_cluster" {
    vpc_id                  = var.vpc
    cluster_cidr            = "10.4.0.0/16"
    cluster_max_pod_num     = 32
    cluster_name            = "test"
    cluster_desc            = "test cluster desc"
    cluster_max_service_num = 32
    container_runtime          = "containerd"
    cluster_version            = "1.14.3"
    worker_config {
     count                      = 2
     availability_zone          = var.availability_zone
     instance_type              = var.default_instance_type
     system_disk_size           = 50
     security_group_ids         = [var.sg]
     internet_charge_type       = "TRAFFIC_POSTPAID_BY_HOUR"
     internet_max_bandwidth_out = 100
     public_ip_assigned         = true
     subnet_id                  = var.subnet
     enhanced_security_service = false
     enhanced_monitor_service  = false
     key_ids                   = [var.key_id]
    }
    cluster_deploy_type = "MANAGED_CLUSTER"
    }
    

    用bash实现一个CRI runtime

    CRI runtime的实现需要实现大量API,这里我们做一个简单的shell脚本,将请求转发给runc,同时打印出调用的参数。把这个脚本命名为runb。
    $ cat /usr/local/bin/runb
    #!/bin/bash -e
    echo "["`date --iso-8601=seconds`"] call runb: $@" >> /var/log/runb.log
    exec runc $@
    

    使用ctr/crictl测试

    ctr是调用containerd的命令行工具,而crictl是调用cri相关api的工具,一个测试的例子参考这里

    配置 containerd, 使用 k8s yaml 测试

    在/etc/containerd/config.toml下面添加如下的配置,配置runtime为runb,二进制程序为runb. containerd的配置因版本变化有所不同,具体可以参考这里。重启containerd。
    [plugins.cri.containerd.runtimes.runb]
          runtime_type = "io.containerd.runc.v1"
          [plugins.cri.containerd.runtimes.runb.options]
            NoPivotRoot = false
            NoNewKeyring = false
            ShimCgroup = ""
            IoUid = 0
            IoGid = 0
            BinaryName = "runb"
            Root = ""
            CriuPath = ""
            SystemdCgroup = false
    
    用kubectl把名为runb的runtimeclass创建出来。
    apiVersion: node.k8s.io/v1beta1  # RuntimeClass is defined in the node.k8s.io API group
    kind: RuntimeClass
    metadata:
    name: runb  # The name the RuntimeClass will be referenced by
    # RuntimeClass is a non-namespaced resource
    handler: runb  # The name of the corresponding CRI configuration
    
    创建一个使用runb为runtime的pod。
    vi nginx.yaml
    
    内容如下:
    apiVersion: v1
    kind: Pod
    metadata:
    name: nginx
    spec:
    runtimeClassName: runb
    containers:
     - image: nginx
       name: nginx
       ports:
         - containerPort: 80
           name: http
    
    观察创建结果和runb的输出日志。
    crictl ps
    head /var/log/runb.log
    
    日志参考:
    starting with runb -namespace default -address /run/containerd/containerd.sock -publish-binary /usr/local/bin/containerd -id hello -debug start
    starting with runb: --root /run/containerd/runc/k8s.io --log /run/containerd/io.containerd.runtime.v2.task/k8s.io/5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac/log.json --log-format json create --bundle /run/containerd/io.containerd.runtime.v2.task/k8s.io/5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac --pid-file /run/containerd/io.containerd.runtime.v2.task/k8s.io/5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac/init.pid 5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac
    starting with runb: --root /run/containerd/runc/k8s.io --log /run/containerd/io.containerd.runtime.v2.task/k8s.io/5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac/log.json --log-format json state 5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac
    starting with runb: --root /run/containerd/runc/k8s.io --log /run/containerd/io.containerd.runtime.v2.task/k8s.io/5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac/log.json --log-format json start 5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac
    starting with runb: --root /run/containerd/runc/k8s.io --log /run/containerd/io.containerd.runtime.v2.task/k8s.io/5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac/log.json --log-format json state 5dd814edd58826c3ebcfa87ae96bb972b1541d6007f7f7ed5c01a4da639b0fac
    

    参考

    腾讯云:扩展Kubernetes之CRI
    https://cloud.tencent.com/developer/article/1579900
    CRI-feisky
    K8S Runtime CRI OCI contained dockershim 理解
    CRI API 定义
    cri-containerd
    containerd
    K8S 容器运行时-feisky
    critools
    **