K8s的CRI机制是什么?

1. 概述

进入 K8s 的世界,会发现有很多方便扩展的 Interface,包括 CRI, CSI, CNI 等,将这些接口抽象出来,是为了更好的提供开放、扩展、规范等能力。

K8s CRI(Container Runtime Interface) 是 K8s 定义的一组与容器运行时进行交互的接口,用于将 K8s 平台与特定的容器运行时实现解耦。CRI 在 Kubernetes 1.5 中引入,并充当 kubelet 和容器运行时之间的桥梁。目前实现了 CRI spec 的 Runtime 有 Docker Engine、containerd、CRI-O、Mirantis Container Runtime(Docker 企业版) 等。

2020 年,K8s 宣布弃用 dockershim,标志着容器运行时正式向 CRI 切换,一方面是为了将 kubelet 核心主干代码与 Runtime 相关代码解耦,便于更好的维护;另一方面则是为了便于生态圈按 CRI spec 实现自己的运行时插件,提供个性化的运行时扩展能力,以满足对更多 Runtime 的支持,提高 K8s 生态的开放性和扩展性。

本文将从 Docker Engine、Kubelet 启动、Pod 创建/删除、Container 创建/删除、CRI RPC 调用等核心流程,对 CRI 实现机制进行了解析。

流程概览如下:

2. 从 Docker 说起

2.1 Docker Engine

Docker Engine 是用来运行和管理容器的核心软件。通常人们会简单地将其代指为 Docker 或 Docker 平台。

Docker Engine 主要的组件构成:Docker 客户端(Docker Client)、Docker 守护进程(Docker daemon)、Docker APIs、containerd 以及 runc,它们共同负责容器的创建和运行。

2.2 OCI

OCI(Open Container Initiative,开放容器计划),是在 2015 年由 Docker、CoreOS 等公司共同成立的项目,并由 Linux 基金会进行管理,致力于 container runtime 标准的制定和 runc 的开发等工作。所谓 container runtime,主要负责的是容器的生命周期的管理。OCI 主要分为容器运行时规范(runtime-spec) 和镜像规范(image-spec) 两部分,runtime-spec 标准对容器的创建、删除、查看、状态等操作进行了定义,image-spec 对镜像格式、打包(Bundle)、存储等进行了定义。

2.3 runc

runc,是由 Docker 贡献的对于 OCI 标准的一个参考实现,是一个可以用于创建和运行容器的 CLI(command-line interface) 工具。runc 直接与容器所依赖的 Cgroup/OS 等进行交互,负责为容器配置 Cgroup/namespace 等启动容器所需的环境,创建启动容器的相关进程。为了兼容 OCI 标准,Docker 也做了架构调整。将容器运行时相关的程序从 Docker daemon 剥离出来,形成了containerd。containerd 向 Docker 提供运行容器的 API,二者通过 gRPC 进行交互。containerd 最后会通过 runc 来实际运行容器。

3. CRI

CRI(Container Runtime Interface,容器运行时接口)是 K8s 定义的一组与容器运行时进行交互的接口,用于将 K8s 平台与特定的容器实现解耦。在 K8s 早期的版本中,对于容器环境的支持是通过 Dockershim(hard code) 方式直接调用 Docker API 的,后来为了支持更多的容器运行时和更精简的容器运行时,K8s 在遵循 OCI 基础上提出了CRI。

3.1 dockershim

dockershim 是 Kubernetes 的一个组件,主要目的是为了通过 CRI 操作 Docker。Kubernetes 在创建之初便采用Docker 作为它的默认容器进行时,后续代码当中包含了很多对 Docker 相关的操作逻辑。后期 Kubernetes 为了能够做解耦,兼容更多的容器进行时,将操作 Docker 相关逻辑整体独立起来组成了 dockershim。

2020 年,K8s 宣布弃用 dockershim,标志着容器运行时正式向 CRI 切换,以满足对更多 Runtime 的支持,提高 K8s 生态的开放性和扩展性。

3.2 CRI shim

当前实现了 CRI 的 remote shim 有如下:

  • containerd:由 Docker 公司创建,并且在 2017 年捐赠给了 CNCF,2019 年毕业。

  • CRI-O:基于 OCI 规范的作为 CRI 和 OCI 之间的一座桥梁。

  • Docker Engine:Docker 运行时的支持,由 cri-dockerd 进行实现。

  • Mirantis Container Runtime:Docker 企业版(Enterprise Edition) 运行时的支持,由 Mirantis Container Runtime(MCR) 进行实现。

CRI shim 小结如下:

3.3 RuntimeClass

RuntimeClass 是 v1.12 引入的新 API 对象,用来支持多个容器运行时,可通过 Pod 字段直接指定。 定义一个 RuntimeClass 如下,对应的 CRI handler 即为目标容器运行时,比如 containerd、crio:

apiVersion: node.k8s.io/v1  # RuntimeClass is defined in the node.k8s.io API group
kind: RuntimeClass
metadata:
  name: myclass  # The name the RuntimeClass will be referenced by
  # RuntimeClass is a non-namespaced resource
handler: myconfiguration  # The name of the corresponding CRI configuration

在 Pod 中直接指定对应的 runtimeClassName 即可:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  runtimeClassName: myclass
  # ...

4. Kubelet 启动

kubelet 在 Node 节点上负责 Pod 的创建、销毁、监控上报等核心流程,通过 Cobra 命令行解析参数启动二进制可执行文件。

启动入口如下:

// kubernetes/cmd/kubelet/kubelet.go
func main() {
   command := app.NewKubeletCommand()

   // kubelet uses a config file and does its own special
   // parsing of flags and that config file. It initializes
   // logging after it is done with that. Therefore it does
   // not use cli.Run like other, simpler commands.
   code := run(command)
   os.Exit(code)
}

接着,一路往下进行初始化:

cmd -> Run -> PreInitRuntimeService -> RunKubelet -> createAndInitKubelet -> startKubelet -> Run

其中 PreInitRuntimeService 会进一步初始化 CRI shim,分别初始化 RuntimeService、ImageService 对容器运行时和镜像生命周期进行管理;然后启动 gRPC CRI server 监听 client 请求,进行具体的操作如 PodSandbox、Container 创建与删除。

kubelet 启动后,会负责:

  • Pod、Volume 事件的监听,创建/删除对应的 Pod/Volume;

  • Node 资源监控与更新、Pod 健康探测(Probe) 及上报;

  • 当 Node 资源紧张时,还负责 Pod Preemption(抢占) 与 Eviction(驱逐);

  • Metrics 监控采集、Image/Container GC 工作等;

其中,Pod 事件流程图请看上面概述中的流程概览图。

5. Pod 创建/删除

K8s 中 Pod 的调谐采用 channel 生产者-消费者模型实现,具体通过 PLEG(Pod Lifecycle Event Generator) 进行 Pod 生命周期事件管理。

// kubernetes/pkg/kubelet/pleg/pleg.go
// 通过 PLEG 进行 Pod 生命周期事件管理
type PodLifecycleEventGenerator interface {
   Start() // 通过 relist 获取所有 Pods 并计算事件类型
   Watch() chan *PodLifecycleEvent // 监听 eventChannel,传递给下游消费者
   Healthy() (bool, error)
}

Pod 事件生产者(producer) - 相关代码:

// kubernetes/pkg/kubelet/pleg/generic.go
// 生产者:获取所有 Pods 列表,计算出对应的事件类型,进行 Sync
func (g *GenericPLEG) relist() {
   klog.V(5).InfoS("GenericPLEG: Relisting")
   ...
   // 获取当前所有 Pods 列表
   podList, err := g.runtime.GetPods(true)
   if err != nil {
      klog.ErrorS(err, "GenericPLEG: Unable to retrieve pods")
      return
   }
   
   for pid := range g.podRecords {
      allContainers := getContainersFromPods(oldPod, pod)
      for _, container := range allContainers {

         // 计算事件类型:running/exited/unknown/non-existent
         events := computeEvents(oldPod, pod, &container.ID)
         for _, e := range events {
            updateEvents(eventsByPodID, e)
         }
      }
   }

   // 遍历所有事件
   for pid, events := range eventsByPodID {
      for i := range events {
         // Filter out events that are not reliable and no other components use yet.
         if events[i].Type == ContainerChanged {
            continue
         }
         select {
         case g.eventChannel <- events[i]: // 生产者:发送到事件 channel,对应监听的 goroutine 会消费
         default:
            metrics.PLEGDiscardEvents.Inc()
            klog.ErrorS(nil, "Event channel is full, discard this relist() cycle event")
         }
      }
   }
   ...
}

Pod 事件消费者(Consumer) - 相关代码:

// kubernetes/pkg/kubelet/kubelet.go
// 消费者:根据 channel 获取的各类事件,进行 Pod Sync
func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
   syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
   select {
   ...
   // 消费者:监听 plegCh 的事件
   case e := <-plegCh:
      if e.Type == pleg.ContainerStarted {
         // 更新容器的最后启动时间
         kl.lastContainerStartedTime.Add(e.ID, time.Now())
      }
      if isSyncPodWorthy(e) {
         if pod, ok := kl.podManager.GetPodByUID(e.ID); ok {
            klog.V(2).InfoS("SyncLoop (PLEG): event for pod", "pod", klog.KObj(pod), "event", e)

            // 进行相关 Pod 事件的 Sync
            handler.HandlePodSyncs([]*v1.Pod{pod})
         } else {
            // If the pod no longer exists, ignore the event.
            klog.V(4).InfoS("SyncLoop (PLEG): pod does not exist, ignore irrelevant event", "event", e)
         }
      }

      // 容器销毁事件处理:清除 Pod 内相关 Container
      if e.Type == pleg.ContainerDied {
         if containerID, ok := e.Data.(string); ok {
            kl.cleanUpContainersInPod(e.ID, containerID)
         }
      }
      ...
   }
   return true
}

当 kubelet 监听到 Pod 事件时,进行对应 Pod 的创建或删除,流程如下:

kubelet -> Run -> syncLoop -> SyncPodCreate/Kill -> UpdatePod -> syncPod/syncTerminatingPod -> dockershim gRPC -> Pod running/teminated

6. Container 创建/删除

当 Pod-Sandbox 创建出来以后,首先会创建基础容器 (infra-container,也叫 pause 容器),通过 CNI 机制 配置 Pod 网络环境,创建临时数据目录(存放 container logs),为下一步 Container 创建做好相关准备工作。

Container 目前分为三种类型:

  • Ephemeral Container:临时容器,用于 debug 及排障所需的一次性容器。

  • Init Container:初始化容器,在正常业务容器之前、按序启动,一般做一些准备工作。

  • Regular Container:普通业务容器,应用使用的主容器。

创建 Container 的过程主要有:

PullImage -> CreateContainer -> StartContainer -> PostStartHook -> Container running

当 Pod 内所有 Container 都已创建(created) && 都已启动(started) && 至少有一个容器在运行中(running),Pod-phase 更新为 Running,表示 Pod 正常运行。

Pod-Container 创建流程小结如下:

7. CRI RPC 接口

CRI 标准规范接口,包含了 ImageService、RuntimeService 两方面的接口:

  • ImageService:管理镜像的查询、拉取、删除、统计等操作;

  • RuntimeService:管理 PodSandbox 和容器的生命周期,包括查询、创建、启动、删除、统计等操作,另外还提供版本(Version)、执行命令(Exec)、端口转发(PortForward) 等接口能力;

可以看到,用户只要按照 CRI RPC 接口规范进行具体实现,就可以实现自己的 Runtime 插件,提高了 K8s 生态的高扩展性与灵活性。

8. 小结

本文通过分析 K8s 中 Kubelet 启动、Pod 创建/删除、Container 创建/删除、CRI RPC 调用等核心流程,对 K8s CRI 实现机制进行了解析。通过源码、图文方式说明了相关流程逻辑,以期更好的理解 K8s CRI 实现细节。

K8s CRI 经历了从 in-tree Dockershim 到 CRI remote-shim(out-of-tree) 的 迁移,一方面是为了将 kubelet 核心主干代码与 Runtime 相关代码解耦,便于更好维护;另一方面则是为了便于生态圈按 CRI spec 实现自己的运行时插件,提供个性化的运行时扩展能力,以期达到容器生态圈的开放共赢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/653315.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

红酒与不同烹饪方法的食物搭配原则

红酒与食物的搭配是一门艺术&#xff0c;而不同烹饪方法的食物与红酒的搭配也有其与众不同之处。红酒与食物的搭配不仅涉及到口感、风味和营养&#xff0c;还与烹饪方法和食物质地等因素息息相关。云仓酒庄雷盛红酒以其卓着的品质和丰富的口感&#xff0c;成为了实现完善搭配的…

AI批量剪辑视频素材,高效混剪快速出片/矩阵发布,一键管理自媒体账号。

今天给大家分享一个超级好用的办公神器。特别是玩矩阵的企业&#xff0c;这款工具高效解决短视频剪辑问题。 这款软件可以帮你快速生产出1000条视频内容,而且还能把内容同步到多个平台账号上&#xff0c;多平台矩阵发布。 这款系统真的太棒了! 不仅操作简单,而且功能超强大。 …

回归自然:拥抱生态农业

在这个喧嚣的时代&#xff0c;我们渴望回归大自然的怀抱&#xff0c;享受那份纯净与安宁。广西生态农业&#xff0c;正是我们追寻自然、健康生活的理想选择。它摒弃了传统农业的弊端&#xff0c;采用环保、可持续的种植方式&#xff0c;为我们带来了绿色、无污染的农产品。生态…

面试八-存泄漏是什么,有哪几种,怎么解决?

一、内存泄漏几种情况 当使用基类指针指向派生类对象时&#xff0c;如果基类的析构函数不是虚函数&#xff0c;那么在使用基类指针来删除这个对象时&#xff0c;只会调用基类的析构函数&#xff0c;而不会调用派生类的析构函数。这就导致了派生类中的资源无法正确释放&#xff…

Typora图床配置优化(PicGo-Core(command line) 插件 + gitee)

Typora图床配置优化&#xff08;PicGo-Core(command line) 插件 gitee&#xff09; 前言 在日常使用Typora编写markdown笔记时&#xff0c;经常需要插入图片来帮助理解和整理逻辑。然而&#xff0c;由于图片保存在本地&#xff0c;上传到网上时经常出现图片不见或错误警告的…

工业LED显示屏汉字乱码方式的解决

目录 研究背景 解决方法 原因分析 尝试的解决方法 本质原因 写在最后 研究背景 想实现LED显示屏数字、字母、汉字均能正常显示的效果&#xff08;效果如下&#xff09;。在将UTF-8改为GB2312 编码之前&#xff0c;数字和字母不乱&#xff0c;但是汉字会乱码。 解决方法 1…

【Docker】Linux 系统(CentOS 7)安装 Docker

文章目录 对 VMware 软件的建议官方说明文档Docker安装卸载旧版本docker设置仓库开始安装 docker 引擎最新版 Docker 安装指定版本 Docker 安装&#xff08;特殊需求使用&#xff09; 启动 Docker查看 Docker 版本查看 Docker 镜像设置 Docker 开机自启动 验证开机启动是否生效…

据阿谱尔APO Research调研显示,2023年全球热喷涂涂料市场销售额约为110.37亿美元

根据阿谱尔 (APO Research&#xff09;的统计及预测&#xff0c;2023年全球热喷涂涂料市场销售额约为110.37亿美元&#xff0c;预计在2024-2030年预测期内将以超过4.82%的CAGR&#xff08;年复合增长率&#xff09;增长。 热喷涂涂层是指将熔融或加热的金属、合金或陶瓷等材料喷…

ORCLE删除数据库文件

在实际操作中很少会去删除数据库文件&#xff0c;但是凡事都有例外&#xff0c;由于一些特殊原因&#xff0c;例如存储方式变化、磁盘空间不够等&#xff0c;需要调整和删除一些无效的数据库文件&#xff0c;本文介绍一下实践出来的一种删除数据库文件的操作方式。 删除前请对数…

室内也可以用北斗定位?还能用RTK?

室内卫星顾名思义&#xff0c;就是在室内有遮挡环境中的卫星定位技术&#xff0c;众所周知&#xff0c;目前全球几大GNSS定位系统已经很完善&#xff0c;但是GNSS有个致命的弱点&#xff0c;就是地面如果有遮挡就没有信号&#xff0c;在这样的条件下&#xff0c;在室内定位场景…

HTML大雪纷飞

目录 写在前面 HTML简介 完整代码 代码分析 运行结果 系列文章 写在后面 写在前面 小编又又又出现啦&#xff01;这次小编给大家带来大雪纷飞HTML版&#xff0c;不需要任何的环境&#xff0c;只要有一个浏览器&#xff0c;就可以随时随地下一场大雪哦&#xff01; HTM…

申请免费通配符SSL证书教程

申请免费通配符SSL证书的步骤相对直接&#xff0c;但需要注意的是免费且支持通配符的证书提供商较为有限&#xff0c;JoySSL是一个被多次提及提供此类服务的机构。以下是一个基于汇总信息的简明教程&#xff0c;帮助你申请免费的通配符SSL证书&#xff1a; 1. 准备工作 确认兼…

VPN的详细理解

VPN&#xff08;Virtual Private Network&#xff0c;虚拟私人网络&#xff09;是一种在公共网络上建立加密通道的技术&#xff0c;通过这种技术可以使远程用户访问公司内部网络资源时&#xff0c;实现安全的连接和数据传输。以下是对VPN的详细介绍&#xff1a; 选择代理浏览器…

颠仆流离学二叉树1 (Java版)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

Jetpack架构组件_2. 数据绑定库

1.理论基础 数据绑定库是一个支持库&#xff0c;可让您使用声明性格式&#xff08;而不是以程序化方式&#xff09;将布局中的界面组件绑定到应用中的数据源。 布局通常使用调用界面框架方法的代码在 activity 中定义。例如&#xff0c;以下代码会调用 findViewById() 来查找 T…

开源内网穿透神器:中微子代理(neutrino-proxy)实现内网穿刺

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

yarn dev启动项目时遇到的问题

用yarn dev启动项目的时候&#xff0c;遇到了如下问题&#xff1a; 这个时候&#xff0c;我们可以这样解决&#xff1a;用nvm list 看下已安装的node版本&#xff0c;用nvm use切换一下node版本&#xff0c;当然前提是你已经安装了nvm。

4.共享文件夹的设置

注&#xff1a;设置共享文件夹&#xff1a;首先要先关机 一、点击 编辑虚拟机设置 二、点击 选项 选项卡 三、点击 共享文件夹 四、在本地建立一个共享文件夹后选择路径 五、Linux系统文件夹中的共享文件夹路径 六、在Linux系统中查看共享文件夹 Ubuntushare是共享文件夹&…

httphere是一个反向代理和友好前端开发的小工具

httphere 是干什么的 httphere在任意目录启动 http服务, 不仅仅是启动一个简单的http服务。 只启动http服务&#xff0c;现成的做法有&#xff1a; python2 -m SimpleHTTPServer python3 -m http.server 其他 httphere 工具httphere功能有&#xff1a;静态服器、文件上传与下…

【NumPy】掌握NumPy的histogram函数:数据直方图的生成与应用详解

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…