简介
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 接口)。
执行流程里面核心组件是kubelet/KubeGenericRuntimeManager他调用很多其他组件,比如cm (ContainerManager/podContainerManager/cgroupManager/cpuManager/deviceManager, pod级别的资源管理), RuntimeService(grpc调用CRI的客户端) 共同完成SyncPod的操作。
经典的dockershim -> containerd的流程 (称为docker cri)
- Kubelet通过CRI接口(gRPC)调用dockershim,请求创建一个容器。CRI即容器运行时接口(Container Runtime Interface),这一步中,Kubelet可以视作一个简单的CRI Client,而dockershim就是接收请求的 Server。目前dockershim的代码其实是内嵌在Kubelet 中的,所以接收调用的凑巧就是Kubelet进程。
- dockershim收到请求后,转化成Docker Daemon能听懂的请求,发到Docker Daemon上请求创建一个容器。
- Docker Daemon早在1.12版本中就已经将针对容器的操作移到另一个守护进程——containerd中了,因此 Docker Daemon仍然不能帮我们创建容器,而是要请求containerd创建一个容器;
- containerd收到请求后,并不会自己直接去操作容器,而是创建一个叫做containerd-shim的进程,让 containerd-shim去操作容器。这是因为容器进程需要一个父进程来做诸如收集状态,维持stdin等fd打开等工作。而假如这个父进程就是containerd,那每次containerd挂掉或升级,整个宿主机上所有的容器都得退出了。而引入了containerd-shim就规避了这个问题(containerd和 shim并不是父子进程关系);
- 我们知道创建容器需要做一些设置namespaces和cgroups,挂载root filesystem等等操作,而这些事该怎么做已经有了公开的规范了,那就是OCI(Open Container Initiative,开放容器标准)。它的一个参考实现叫做runC。于是,containerd-shim在这一步需要调用runC这个命令行工具,来启动容器;
- runC启动完容器后本身会直接退出,containerd-shim则会成为容器进程的父进程,负责收集容器进程的状态,上报给containerd,并在容器中pid为1的进程退出后接管容器中的子进程进行清理,确保不会出现僵尸进程。
直接对接 cri-containerd/cri-o 的运行时
使用CRI-Containerd的调用流程更为简洁, 省去了上面的调用流程的 1,2 两步。
常见CRI runtime实现
CRI-Containerd
执行流程为:
- Kubelet通过CRI runtime service API调用cri plugin创建pod。
- cri通过CNI创建pod的网络配置和namespace。
- cri使用containerd创建并启动pause container (sandbox container) 并且把这个container置于pod的 cgroups/namespace。
- Kubelet接着通过CRI image service API调用cri plugin,获取容器镜像。
- cri通过containerd获取容器镜像。
- Kubelet通过CRI runtime service API调用cri,在pod的空间使用拉取的镜像启动容器。
- 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。
用kubectl把名为runb的runtimeclass创建出来。[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
创建一个使用runb为runtime的pod。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
内容如下:vi nginx.yaml
观察创建结果和runb的输出日志。apiVersion: v1 kind: Pod metadata: name: nginx spec: runtimeClassName: runb containers: - image: nginx name: nginx ports: - containerPort: 80 name: http
日志参考: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
**