k8s 读书笔记 - CRI容器运行时接口详解

k8s Node 节点(kubelet)的主要功能就是启动和停止容器的组件,这组件我们称之为 容器运行时(Container Runtime),这其中最知名的就是 Docker 了。为了更具扩展性,k8s 从 v1.5 版本开始就加入了容器运行时插件 API,即 Container Runtime Interface,简称 CRI

CRI 概述

每个容器运行时都有各自的特点,因此用户希望 k8s 能够支持更多的容器运行时。k8s 从 v1.5 版本开始,引入了 CRI 接口规范,通过插件接口模式,k8s 无需重新编译就可以使用更多的容器运行时

可替代的容器运行时支持是 k8s 中的新概念。在 k8s v1.3 发布时,rktnetes 项目同时发布,让 rkt 容器引擎成为除 Docker 外的又一选择。然而,不管是 Docker 还是 rkt ,都用到了 kubelet 的内部接口,同 kubelet 源码纠缠不清。这种程度的集成需要对 kubelet 的内部机制有非常深入的了解,还会给社区带来管理压力,这就给新生代容器运行时造成了难于跨越的集成壁垒。CRI 接口规范试图用定义清晰的抽象层清除这一壁垒,让开发者能够专注于容器运行时(Container Runtime)本身。在通向插件式容器支持及建设健康生态环境的路上,这是一小步,也是很重要的一步。

什么是 CRI?

  1. CRI(容器运行时接口)是一个插件接口,它使 kubelet 能够使用各种容器运行时,无需重新编译集群组件。你需要在集群中的每个节点上都有一个可以正常工作的 容器运行时, 这样 kubelet 能启动 Pod 及其容器。

  2. CRI 是 kubelet 和 容器运行时(Container Runtime) 之间通信的主要协议。k8s 容器运行时接口(CRI) 定义了主要 gRPC 协议, 用于集群组件 kubelet 和 容器运行时。

CRI 包含 Protocol Buffers、gRPC API、运行库支持及开发中的标准规范和工具。Docker 的 CRI 实现在 k8s v1.6 中被更新为 Beta 版本,并在 kubelet 启动时默认启动。

什么是 Container Runtime?

Container Runtime 提供容器运行环境,是负责运行容器的软件

说明:k8s 自 v1.24 版起,Dockershim 已从 Kubernetes 项目中移除。

你需要在 集群内每个 Node 节点上安装一个 容器运行时 以使 Pod 可以运行在上面。

说明:v1.24 之前的  k8s 版本包括与 Docker Engine 的直接集成,使用名为 dockershim(垫片) 的组件。这种特殊的直接整合不再是 k8s 的一部分 (这次删除被作为 v1.20 发行版本的一部分宣布)。你可以阅读检查 Dockershim 弃用是否会影响你 以了解此删除可能会如何影响你。要了解如何使用 dockershim 进行迁移, 请参阅从 dockershim 迁移。如果你正在运行 v1.24 以外的 Kubernetes 版本,检查该版本的文档。

CRI 主要组件

kubelet 使用 gRPC 框架通过 UNIX Socket 与容器运行时(或CRl代理)进行通信。在这个过程中 kubelet 是客户端,CRI 代理(shim)是服务端,如下图所示。

cri 主要组件

Protocol Buffers API 包含两个 gRPC 服务:ImageService 和 RuntimeService

  • ImageService 提供了从仓库拉取镜像、查看和移除镜像的功能。

  • RuntimeService 负责 Pod 和容器的生命周期管理,以及与容器的交互 (exec/attach/port-forward )。

rkt 和 Docker 这样的容器运行时可以使用一个 Socket 同时提供两个服务,在 kubelet 中可以用 --containcr-runtime-endpoint 和 --image-service-endpoint 参数设置这个 Socket

Pod 和 Container 生命周期管理

Pod 由一组应用容器(Container)组成,其中包含共有的环境和资源约束。在 CRI 里,这个环境被称为 PodSandbox(Pod 沙箱)。k8s 有意为容器运行时留一些发挥空间,他们可以根据自己的内部实现来解释PodSandbox。对于 Hypervisor 类的运行时,PodSandbox 会具体化为一个虚拟机。其他例如 Docker,会是一个 Linux 命名空间(namespace)。在 v1alphal API,kubelet 会创建 Pod 级别的 cgroup 传递给容器运行时,并以此运行所有进程来满足 PodSandbox 对 Pod 的资源保障。

在启动 Pod 之前,kubelet 调用 RuntimeService.RunPodSandbox 来创建环境。这一过程包括为 Pod 设置网络资源(分配IP等操作)。PodSandbox 被激活后,就可以独立地创建启动、停止和删除不同的容器了。kubelet 会在停止和删除 PodSandbox 之前首先停止和删其中的容器。

kubelet 的职责在于通过 RPC 管理容器的生命周期,实现容器生命周期的钩子,存活和健康监测,以及执行 Pod 的重启策略等

RuntimeService 服务包括对 Sandbox 和 Container 操作的方法,下面的伪代码展示了主要的 RPC 方法:

service RuntimeService {
// 沙箱操作
rpc RunPodSandbox(RunPodSandboxRequest) returns(RunPodSandboxResponse) { }
rpc StopPodSandbox(StopPodSandboxRequest) returns(StopPodSandboxResponse) { }
rpc RemovePodSandbox(RemovePodSandboxRequest) returns(RemovePodSandboxResponse) { }
rpc PodSandboxStatus(PodSandboxStatusRequest) returns(PodSandboxStatusResponse) { }
rpc ListPodSandbox(ListPodSandboxReauest) returns(ListPodSandboxResponse) { }
// 容器操作
rpc CreateContainer(CreateContainerRequest) returns(CreateContainerResponse) { }
rpc StartContainer(StartContainerReauest) returns(StartContainerResponse) { }
rpc StopContainer(StopContainerReauest) returns(StopContainerResponse) { }
rpc RemoveContainer(StopContainerReauest) returns(RemoveContainerResponse) { }
rpc ListContainers(ListContainersReauest) returns(ListContainersResponse) { }
rpc ContainerStatus(ContainerStatusReauest) returns(ContainerStatusResponse) { }
...
}

面向容器级别的设计思想

k8s 中最小的调度单元是 Pod,它曾经可能采用的一个 CRI 设计就是复用 Pod 对象,使得容器运行时可以自行实现控制逻辑和状态转换,极大地简化 API,让CRI 能够更广泛地适用于多种容器运行时。但是经过深入讨论之后,k8s 放弃了这一想法。

  • 首先,kubelet 有很多 Pod 级别的功能和机制(例如 crash-loop backoff机制)。如果交给容器运行时去实现,则会造成很重的负担;

  • 其次,更重要的是,Pod 标准还在快速演进中。很多新功能(如初始化容器,Init Conatiner)都是由 kubelet 完成管理的,无须交给容器运行时实现。

CRI 选择了在容器级别进行实现,使得容器运行时能够共享这些通用特性,以获得更快的开发速度。这并不意味着设计哲学的改变—— kubelet 要负责、保证容器应用的实际状态和声明状态的一致性。

Kubernetes 为用户提供了与 Pod 及其中的容器进行交互的功能(kubectlexec/attach/port-forward)。kubelet 目前提供了两种方式来支持这些功能。

  1. 调用容器的本地方法。

  2. 使用Node上的工具(例如 nsenter 及 socat )。

因为多数工具都假设 Pod 用 Linux 的 namespace 做了隔离,因此使用 Node 上的工具并不是一种容易移植的方案。在 CRI 中显式定义了这些调用方法,让容器运行时进行具体实现。下面的伪代码显示了 Exec、Attach、PortForward 这几个调用需要实现的 RuntimeService 方法:

service RuntimeService {
...
// Execsync 在容器中同步执行一个命令。
rpc ExecSync(ExecSyncRequest) returns(ExecSyncResponse) { }
// Exec 在容器中执行命令
rpc Exec(ExecRequest) returns(ExecResponse) { } 
// Attach 附着在容器上
rpc Attach(AttachRequest) returns(AttachResponse)  { } 
// PortForward 从 Pod 沙箱中进行端口转发
rpc PortForward(PortForwardRequest) returns(PortForwardResponse)  { }
...
}

目前还存在一个潜在的问题,kubectl 处理所有的请求连接,使其成为 Node 通信瓶颈的可能。在设计 CRI 时,要让容器运行时能够跳过中间的过程。容器运行时可以启动一个单独的流式服务来处理请求(还能对 Pod 的资源使用情况进行记录),并将服务地址返回给 kubelet 。这样 kubelet 就能反馈信息给 API Server(kube-apiserver),使之可以直接连接到容器运行时提供商的服务,并连接到客户端。

尝试使用新的 CRI 来创建容器

尝试新的 Kubelt-CRI-Docker 集成,只需为 kubelet 启动参数加上 --enable-cri=true 开关来启动 CRI。该选项从 k8s v1.6 开始已经作为 kubelet 的默认选项。如果不希望使用 CRI ,可以设置 --enable-cri=false 来关闭该功能。

查看 kubelet 的日志,可以看到启用 CRI 和创建 gRPC Server 的日志:

10603 15:08:28.953332 3442 container_manager_linux.go:250] Creating 
Container Manager object based on Node Config: {RuntimeCgroupsName:SystemCgroupsName: 
KubeletCgroupsName:ContainerRuntime :docker CgroupsPerQOS:true CgroupRoot:/
CgroupDriver:cgroupfs ProtectKernelDefaults:false EnableCRI :true
NodeAllocatableConfig: {KubeReservedCgroupName: SystemReservedCgroupName:
EnforceNodeAllocatable:map[pods:{}] KubeReserved:map[] SystemReserved:map[]
HardEvictionThresholds:[{Signal :memory.available Operator:LessThan
Value:{Quantity:100Mi Percentage:0} GracePeriod:0s MinReclaim:<niI>}]}
ExperimentalQ0SReserved:map[]}
...
10603 15:08:29.060283 3442 kuhelet.go:573] Starting the GRPC server for the docker CRI shim.

创建一个 Deployment:

kubectl run nginx --image=nginx:1.8 --replicas=1
...
deployment "nginx" created 

查看 Pod 的详细信息,可以看到将会创建 Sandbox(沙箱) 的 Event:

kubectl describe pod nginx
...
Events:
...From            Type      Reason           Message
...-------            --------  -----------      --------------- 
...default-scheduler  Normal    Scheduled        Successfuller assigned nginx to k8s-node-1
...kubelet,k8s-node-1 Normal    SandboxReceived  Pod sandbox received, it will be created.

从 Event 日志信息可以看出 kubelet 使用了 CRI 接口来创建 Container 容器。

常用的 CRI

目前已经有多款开源的 CRI 项目可用于 k8s,常见的容器运行时有以下几种:

  • containerd(k8s v1.24 版本及以后默认的容器运行时)

  • CRI-O

  • Docker Engine(k8s v1.24 版本已经剔除,默认使用 containerd)

  • Mirantis Container Runtime(MCR)

  • frakit(基于 Hypervisor 的容器运行时)

关于容器运行时的更多信息,请查看:https://kubernetes.io/zh-cn/docs/setup/production-environment/container-runtimes/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340