icon-cookie
The website uses cookies to optimize your user experience. Using this website grants us the permission to collect certain information essential to the provision of our services to you, but you may change the cookie settings within your browser any time you wish. Learn more
I agree
blank_error__heading
blank_error__body
Text direction?

扩展 Kubernetes 之 CRI

阅读 6150

简介

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 中:

image
image

在和OCI,调度层的角度看:

graph LR
OrchestrationAPI --> ContainerAPI-criRuntime
ContainerAPI-criRuntime --> KernelAPI-ociRuntime

CRI/CRI Runtime

CRI Runtime 的执行流程

经典的 kubernetes runtime 执行流程

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

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

image
  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 的运行时

image
image

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

常见 CRI runtime 实现

image

Cri-containerd

image

执行流程为:

  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

root@VM-8-12-ubuntu:~# cat nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  runtimeClassName: runb
  containers:
    - image: nginx
      name: nginx
      ports:
        - containerPort: 80
          name: http

观察创建结果和 runb 的输出日志

root@VM-8-12-ubuntu:~# crictl ps
CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID
d0e454f31ad06       2073e0bcb60ee       11 minutes ago      Running             nginx               0                   f640723d57cc2

root@VM-8-12-ubuntu:~# 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

参考

Measure
Measure
Related Notes
Get a free MyMarkup account to save this article and view it later on any device.
Create account

End User License Agreement

Summary | 6 Annotations
ImageSpec
2020/06/21 08:27
RuntimeSpec
2020/06/21 08:27
dockershim
2020/06/21 08:27
CRI-containerd
2020/06/21 08:27
cri-o
2020/06/21 08:27
kubelet/KubeGenericRuntimeManager
2020/06/21 08:29