CRI shim:kubelet怎么与runtime交互(一)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: CRI shim:kubelet怎么与runtime交互(一)

CRI shim是什么?


实现了 CRI 接口的容器运行时通常称为 CRI shim, 这是一个 gRPC Server,监听在本地的 unix socket 上;而 kubelet 作为 gRPC 的客户端来调用 CRI 接口,来进行 Pod 和容器、镜像的生命周期管理。另外,容器运行时需要自己负责管理容器的网络,推荐使用 CNI。


kubelet 调用下层容器运行时的执行过程,并不会直接调用Docker 的 API,而是通过一组叫作 CRI(Container Runtime Interface,容器运行时接口)的 gRPC 接口来间接执行的,意味着需要使用新的连接方式与 docker 通信,为了兼容以前的版本,k8s 提供了针对 docker 的 CRI 实现,也就是kubelet包下的dockershim包,dockershim是一个 grpc 服务,监听一个端口供 kubelet 连接,dockershim收到 kubelet 的请求后,将其转化为 REST API 请求,再发送给docker daemon。Kubernetes 项目之所以要在 kubelet 中引入这样一层单独的抽象,当然是为了对 Kubernetes 屏蔽下层容器运行时的差异。


7d296752ee67a83bcd95b3530b4137e7.png


解决思路再次体现了《代码大全2》里提到的那句经典名言:any problem in computer science can be sloved by another layer of indirecition。计算机科学领域的任何问题都可以通过增加一个中间层来解决,我们的 CRI shim就是加了这样一层。


CRI shim server 接口图示


**CRI 接口包括 RuntimeService 和 ImageService 两个服务,这两个服务可以在一个 gRPC server 中实现,也可以分开成两个独立服务。**目前社区的很多运行时都是将其在一个 gRPC server 里面实现。


052146306c43a7a4bb8c32a6a5b15066.png


ImageServiceServer 提供了 5 个接口,用于管理容器镜像。管理镜像的 ImageService 提供了 5 个接口:


  • 查询镜像列表;
  • 拉取镜像到本地;
  • 查询镜像状态;
  • 删除本地镜像;
  • 查询镜像占用空间等。


关于容器镜像的操作比较简单,所以我们就暂且略过。接下来,我主要为你讲解一下 RuntimeService 部分。RuntimeService 则提供了更多的接口,按照功能可以划分为四组:


  • PodSandbox 的管理接口:CRI 设计的一个重要原则,就是确保这个接口本身,只关注容器,不关注 Pod。
  • PodSandbox 是对 Kubernete Pod 的抽象,用来给容器提供一个隔离的环境(比如挂载到相同的 CGroup 下面),并提供网络等共享的命名空间。PodSandbox 通常对应到一个 Pause 容器或者一台虚拟机;
  • Container 的管理接口:在指定的 PodSandbox 中创建、启动、停止和删除容器;


e44efaf97968c9f43e93c9a8fb9dbef2.png


  • Streaming API 接口:包括 Exec、Attach 和 PortForward 等三个和容器进行数据交互的接口,这三个接口返回的是运行时 Streaming Server 的 URL,而不是直接跟容器交互。kubelet 需要跟容器项目维护一个长连接来传输数据。这种 API,我们就称之为 Streaming API。


  • 状态接口:包括查询 API 版本和查询运行时状态。

我们通过 kubectl 命令来运行一个 Pod,那么 Kubelet 就会通过 CRI 执行以下操作:

  • 首先调用 RunPodSandbox 接口来创建一个 Pod 容器,Pod 容器是用来持有容器的相关资源的,比如说网络空间、PID空间、进程空间等资源;
  • 然后调用 CreatContainer 接口在 Pod 容器的空间创建业务容器;
  • 再调用 StartContainer 接口启动运行容器
  • 最后调用停止,销毁容器的接口为 StopContainer 与 RemoveContainer。


就完成了整个Container的生命周期。


Streaming API


CRI shim 对 Streaming API 的实现,依赖于一套独立的 Streaming Server 机制。Streaming API 用于客户端与容器进行交互,包括 Exec、PortForward 和 Attach 等三个接口。kubelet 内置的 Docker 通过 nsenter、socat 等方法来支持这些特性,但它们不一定适用于其他的运行时,也不支持 Linux 之外的其他平台。因而,CRI 也显式定义了这些 API,并且要求容器运行时返回一个 Streaming Server 的 URL 以便 kubelet 重定向 API Server 发送过来的流式请求。


cddfba54b16997d1197964805bfb99d9.png


因为所有容器的流式请求都会经过 kubelet,这可能会给节点的网络流量带来瓶颈,因而 CRI 要求容器运行时启动一个对应请求的单独的流服务器,将地址返回给  kubelet。kubelet 将这个信息再返回给 Kubernetes API Server,会直接打开与运行时提供的服务器相连的流连接,并通过它与客户端连通。


这样一个完整的 Exec 流程就如上图所示,分为多个阶段:

  • 客户端 kubectl exec -i -t ...;
  • kube-apiserver 向 kubelet 发送流式请求 /exec/;
  • kubelet 通过 CRI 接口向 CRI Shim 请求 Exec 的 URL;
  • CRI Shim 向 kubelet 返回 Exec URL;
  • kubelet 向 kube-apiserver 返回重定向的响应;
  • kube-apiserver 重定向流式请求到 Exec URL,然后将 CRI Shim 内部的 Streaming Server 跟 kube-apiserver 进行数据交互,完成 Exec 的请求和响应。


也就是说 apiserver 其实实际上是跟 streaming server 交互来获取我们的流式数据的。这样一来让我们的整个 CRI Server 接口更轻量、更可靠。


注意:当然,这个 Streaming Server 本身,是需要通过使用 SIG-Node 为你维护的 Streaming API 库来实现的。并且,Streaming Server 会在 CRI shim 启动时就一起启动。此外,Stream Server 这一部分具体怎么实现,完全可以由 CRI shim 的维护者自行决定。比如,对于 Docker 项目来说,dockershim 就是直接调用 Docker 的 Exec API 来作为实现的。


CRI-containerd架构解析与主要接口解析


0b82e0f53a48531552051bfa8a047d12.png

a97e80a1f26c05950d6793b6b748c1e2.png


整个架构看起来非常直观。这里的 Meta services、Runtime service 与 Storage service 都是 containerd 提供的接口。它们是通用的容器相关的接口,包括镜像管理、容器运行时管理等。CRI 在这之上包装了一个 gRPC 的服务。右侧就是具体的容器的实现。比如说,创建容器时就要创建具体的 runtime 和它的containerd-shim。Container 和 Pod Sandbox组成了一个Pod。


CRI-containerd 的一个好处是,containerd 还额外实现了更丰富的容器接口,所以它可以用 containerd 提供的 ctr 工具来调用这些丰富的容器运行时接口,而不只是 CRI 接口

CRI实现了两个GRPC协议的API,提供两种服务ImageService和RuntimeService。


// grpcServices are all the grpc services provided by cri containerd.
type grpcServices interface {
  runtime.RuntimeServiceServer
  runtime.ImageServiceServer
}
// CRIService is the interface implement CRI remote service server.
type CRIService interface {
  Run() error
  // io.Closer is used by containerd to gracefully stop cri service.
  io.Closer
  plugin.Service
  grpcServices
}


CRI的实现CRIService中包含了很多重要的组件:其中最重要的是cni.CNI,用于配置容器网络。还有containerd.Client,用于连接containerd来创建容器。


// criService implements CRIService.
type criService struct {
 // config contains all configurations.
 config criconfig.Config
 // imageFSPath is the path to image filesystem.
 imageFSPath string
 // os is an interface for all required os operations.
 os osinterface.OS
 // sandboxStore stores all resources associated with sandboxes.
 sandboxStore *sandboxstore.Store
 // sandboxNameIndex stores all sandbox names and make sure each name
 // is unique.
 sandboxNameIndex *registrar.Registrar
 // containerStore stores all resources associated with containers.
 containerStore *containerstore.Store
 // containerNameIndex stores all container names and make sure each
 // name is unique.
 containerNameIndex *registrar.Registrar
 // imageStore stores all resources associated with images.
 imageStore *imagestore.Store
 // snapshotStore stores information of all snapshots.
 snapshotStore *snapshotstore.Store
 // netPlugin is used to setup and teardown network when run/stop pod sandbox.
 netPlugin cni.CNI
 // client is an instance of the containerd client
 client *containerd.Client
 // streamServer is the streaming server serves container streaming request.
 streamServer streaming.Server
 // eventMonitor is the monitor monitors containerd events.
 eventMonitor *eventMonitor
 // initialized indicates whether the server is initialized. All GRPC services
 // should return error before the server is initialized.
 initialized atomic.Bool
 // cniNetConfMonitor is used to reload cni network conf if there is
 // any valid fs change events from cni network conf dir.
 cniNetConfMonitor *cniNetConfSyncer
 // baseOCISpecs contains cached OCI specs loaded via `Runtime.BaseRuntimeSpec`
 baseOCISpecs map[string]*oci.Spec
}


我们知道 Kubernetes 的一个运作的机制是面向终态的,在每一次调协的循环中,Kubelet 会向 apiserver 获取调度到本 Node 的 Pod 的数据,再做一个面向终态的处理,以达到我们预期的状态。


循环的第一步,首先通过 List 接口拿到容器的状态。确保有镜像,如果没有镜像则 pull 镜像再通过 Sandbox 和 Container 接口来创建容器。需要注意的是,我们的 CNI(容器网络接口)也是在 CRI 进行操作的,因为我们在创建 Pod 的时候需要同时创建网络资源然后注入到 Pod 中(PS:CNI包含在创建Pod 这个动作里)。接下来就是我们的容器和镜像。我们通过具体的容器创建引擎来创建一个具体的容器。


执行流程为:


  • 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 完成启动。


总结


发现 CRI 只是服务于 Kubernetes 的,而且它呈现向上汇报的状态。它是帮助 Kubernetes 的,它不帮助OCI的。所以说当你去做这个集成时候,你会发现尤其对于 VM gVisor\KataContainer 来说,它与 CRI 的很多假设或者是 API 的写法上是不对应的。所以你的集成工作会比较费劲,这是一个不 match 的状态。


9741e45616a28c849d585513300864e3.png


最后一个就是我们维护起来非常困难,因为由于有了 CRI 之后,比如 RedHat 拥有自己的 CRI 实现叫 cri-o,他们和 containerd 在本质上没有任何区别,跑到最后都是靠 runC 起容器,为什么还需要cri-o这种东西?


我们不知道,如果我想使用Kata container与containerd多运行时的话,我需要给他们两个分别写两部分的一体化把 Kata 集成进去。这就很麻烦,就意味着我有 100 种这样的 CRI ,我就要写 100 个shim去集成,而且他们的功能全部都是重复的。


所以这就产生了Containerd ShimV2的这样的shim来解决这个问题。我们下回分解。

相关实践学习
通过workbench远程登录ECS,快速搭建Docker环境
本教程指导用户体验通过workbench远程登录ECS,完成搭建Docker环境的快速搭建,并使用Docker部署一个Nginx服务。
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
Kubernetes Shell 开发工具
Kubernetes CKS 测试之环境准备
Kubernetes CKS 测试之环境准备
Kubernetes CKS 测试之环境准备
|
8月前
|
Kubernetes Linux 调度
将Kubernetes集群的CRI实现从cri-docker更改为containerd
本文记录了将Kubernetes集群的CRI实现从cri-docker更改为containerd的过程,包括cri-docker相关的卸载和containerd的安装配置。
462 0
|
9月前
|
Kubernetes Linux 调度
K8S CRI解析
上篇文章,我们讲到容器引擎Docker与Podman,关于K8S弃用Docker的根本原因在于容器运行时接口CRI,Kubelet 之前使用的是一个命名为 dockershim 的模块,用以实现对 Docker 的 CRI 支持。具体,可参考上篇文章:容器引擎Docker与Podman解析。本文主要针对CRI进行简要解析,以使得大家能够更深入了解K8S底层运行机制,以便能够更好地掌握容器生态技能。
72 0
|
Kubernetes 安全 容器
Kubernetes CKS【21】---Runtime Security -主机与容器行为安全分析(strace、/proc、env、falco)(1)
Kubernetes CKS【21】---Runtime Security -主机与容器行为安全分析(strace、/proc、env、falco)(1)
Kubernetes CKS【21】---Runtime Security -主机与容器行为安全分析(strace、/proc、env、falco)(1)
|
Kubernetes 安全 容器
Kubernetes CKS【21】---Runtime Security -主机与容器行为安全分析(strace、/proc、env、falco)(2)
Kubernetes CKS【21】---Runtime Security -主机与容器行为安全分析(strace、/proc、env、falco)(2)
Kubernetes CKS【21】---Runtime Security -主机与容器行为安全分析(strace、/proc、env、falco)(2)
|
Kubernetes 安全 容器
Kubernetes CKS【22】---Runtime Security -容器安全加固
Kubernetes CKS【22】---Runtime Security -容器安全加固
Kubernetes CKS【22】---Runtime Security -容器安全加固
|
Kubernetes Linux API
CRI shim:kubelet怎么与容器运行时交互(二)
CRI shim:kubelet怎么与容器运行时交互(二)
CRI shim:kubelet怎么与容器运行时交互(二)
|
Kubernetes 网络协议 Unix
Kubernetes CRI -- 容器运行时接口解析
Kubernetes CRI -- 容器运行时接口解析
Kubernetes CRI -- 容器运行时接口解析
|
域名解析 Kubernetes 负载均衡
kubernetes Service:让客户端发现pod并与之通信(上)
kubernetes Service:让客户端发现pod并与之通信
kubernetes Service:让客户端发现pod并与之通信(上)
|
Kubernetes 负载均衡 网络协议
kubernetes Service:让客户端发现pod并与之通信(下)
kubernetes Service:让客户端发现pod并与之通信