【容器运行时】一文理解 OCI、runc、containerd、docker、shim进程、cri、kubelet 之间的关系

news2024/11/15 10:07:00

参考

  • docker,containerd,runc,docker-shim 之间的关系
  • Containerd shim 进程 PPID 之谜
  • 内核大神教你从 Linux 进程的角度看 Docker
  • RunC 简介
  • OCI和runC
  • Containerd 简介
  • 从 docker 到 runC
  • Dockershim究竟是什么
  • 技术干货|Docker和 Containerd 的区别,看这一篇就够了
  • Docker,containerd,CRI,CRI-O,OCI,runc 分不清?看这一篇就够了
  • k8s、dockershim、containershim、容器运行时的关系
  • Docker服务进程关系
  • 关于容器中进程的继承关系
  • containerd,containerd-shim和runc的依存关系
  • dockerd、contaierd、containerd-shim、runC通信机制分析
  • docker,containerd,runc,docker-shim 之间的关系 k8s cri 的演变
  • 容器中的 Shim 到底是个什么鬼?

排错博客整理

  • 【Pod Terminating原因追踪系列之一】containerd中被漏掉的runc错误信息

  • pod terminating 排查之旅

  • pod terminating 排查之旅(二)

  • docker hang 死排查之旅

  • 最新的示意图(取消了 docker-shim)

  • Kubernetes 的容器运行时对接示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kmVTuCso-1678071207063)(/Users/dufengyang/Library/Application Support/typora-user-images/image-20230303150508901.png)]

名词解释补充
OCIOCI(open Container Initiative)容器标准化组织的主要目的是推进容器技术的标准化。对容器标准进行准确的定义。其主要目的是为了解决容器标准混乱的问题。没有统一的容器标准,工业界就无法按照统一的标准进行容器开发。因此OCI于2015年由docker牵头和其他公司制定了相应的容器标准。
OCI目前包含两个标准: runtime-spec和image-spec。分别定义了容器运行时标准和容器镜像标准。
- 可以理解为,是个标准,统一创建容器的接口以及镜像的结构
接口规范标准
RunCRunC 是一个轻量级的工具,它是用来运行容器的
- 可以理解为实现了 OCI 定义的接口的工具
标准的简单实现
ContainerdContainerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性。Containerd 可以在宿主机中管理完整的容器生命周期:容器镜像的传输和存储、容器的执行和管理、存储和网络等。
详细点说,Containerd 负责干下面这些事情:
- 管理容器的生命周期(从创建容器到销毁容器)
- 拉取/推送容器镜像
- 存储管理(管理镜像及容器数据的存储)
- 调用 runC 运行容器(与 runC 等容器运行时交互)
- 管理容器网络接口及网络
注意:Containerd 被设计成嵌入到一个更大的系统中,而不是直接由开发人员或终端用户使用。
较为丰富的功能,对 RunC 的封装
工业级容器运行时,主要用于大型系统,不直接面向用户
Dockerdocker本身而言,包括docker client和dockerd,是一个客户端工具,用来把用户的请求发送给docker daemon(dockerd)。
dockerd:dockerd是对容器相关操作的最上层封装,直接面向操作用户。Docker daemon,一般也会被称为docker engine。dockerd启动时会启动containerd 子进程。
封装了 Containerd,可以理解为面向用户级别的产品
简单总结- OCI :是个容器和镜像标准,定义了一些接口和规范
- RunC : 实现了 OCI 标准,可以实现容器的创建,功能比较简单
- Containerd:封装了 RunC,提供更丰富功能,适用于工业级需求(对接大型系统,如k8s)
- Docker:引擎部分 dockerd 封装了 Containerd,提供 client 端由客户操作,用户级别的产品
CRICRI(容器运行时接口)是 Kubernetes 用来控制创建和管理容器的不同运行时的 API,它使 Kubernetes 更容易使用不同的容器运行时。它一个插件接口,这意味着任何符合该标准实现的容器运行时都可以被 Kubernetes 所使用接口规范标准
Docker-shim在 Kubernetes 包括一个名为 dockershim 的组件,使它能够支持 Docker。**但 Docker 由于比 Kubernetes 更早,没有实现 CRI,所以这就是 dockershim 存在的原因,它支持将 Docker 被硬编码到 Kubernetes 中。**随着容器化成为行业标准,Kubernetes 项目增加了对额外运行时的支持,比如通过 Container Runtime Interface (CRI) 容器运行时接口来支持运行容器。因此 dockershim 成为了 Kubernetes 项目中的一个异类,对 Docker 和 dockershim 的依赖已经渗透到云原生计算基金会(CNCF)生态系统中的各种工具和项目中,导致代码脆弱。是组件
K8s决定在 1.20 开始放弃 Docker,并在1.21完全抛弃 Docker 的支持。。今后 Kubernetes 将取消对 Docker 的直接支持,而倾向于只使用实现其容器运行时接口的容器运行时,这可能意味着使用 containerd 或 CRI-O。这并不意味着 Kubernetes 将不能运行 Docker 格式的容器。containerd 和 CRI-O 都可以运行 Docker 格式(实际上是 OCI 格式)的镜像,它们只是无需使用 docker 命令或 Docker 守护程序。
适配器,将k8s cri接口与各种容器实现的接口进行适配
- 早期,k8s cri 容器运行时接口直接对接 docker,由于 cri 是个标准(有固定的接口形式),因此为了将 k8s cri 的命令传达给 docker,需要有个翻译,也就是 docker-shim
Containerd-shim调用runc启动容器,监控容器进程状态,回收容器中的相关进程等
- 一个 containerd-shim 进程只负责管理一个运行的容器
- 通过 docker 运行容器,containerd-shim 进程可能被命名为 docker-containerd-shim
- k8s 1.24 后,cri 直接对接了 containerd,容器对应的 shim 进程名称为 containerd-shim
是进程
CRI-OCRI-O 是另一个实现了容器运行时接口(CRI)的高级别容器运行时,可以使用 OCI(开放容器倡议)兼容的运行时,它是 containerd 的一个替代品。
CRI-O 诞生于 RedHat、IBM、英特尔、SUSE、Hyper 等公司。它是专门从头开始创建的,作为 Kubernetes 的一个容器运行时,它提供了启动、停止和重启容器的能力,就像 containerd 一样。
是组件
注意是容器运行时,和containerd 同等地位
k8s 调用链路的变化
K8s 1.21 前调用链路K8S -> kubelet -> grpc call -> Dockerd --> Containerd-> runC
- 进程纳管情况:Containerd(纳管所有containerd-shim) --> Containerd-shim(纳管单个容器) --> 容器进程
k8s 1.21 后调用链路K8S -> kubelet -> grpc call -> Containerd-> runC
- 进程纳管情况:Root-init(宿主机上1号进程)–> Containerd-shim(纳管单个容器) --> 容器进程
- 如何实现被 Root-init 进程纳管呢?
- 猜测是(下文有详细介绍):
1. Containerd 启动一个进程(称之为 Start 进程)拉起 Containerd-shim 进程,从而创建出容器
2. 当容器起来后,Start 进程快速结束,此时 Containerd-shim 进程变为了孤儿集成,因此被 Root-init 进程纳管
完全理解 Docker 创建容器的一个过程
首先理解linux创建子进程Docker Daemon 的 fork 和我们程序员普通的 fork 有什么区别,为什么 Docker 的 fork,fork 出的是容器,而我们的却不叫容器呢?
Linux 中创建进程的基本模型Linux 操作系统中,由父进程创建并执行子进程,创建通过 fork 完成,执行通过 exec 完成
在上图中,我们看到进程 A 创建了一个新的进程 B,最终两个进程各自运行。创建时,进程 A 通过 fork 系统调用来完成。fork 之后,两个进程最大的区别就是:进程 A 依然拥有原来的 PID,新创建的进程 B 会占用一个全新的 PID,两者的 PID 不同。
并且 Linux 内核会在 fork 系统调用时,会拷贝进程 A 的 task_struct,拷贝的副本是为进程 B 准备的。完成 fork 操作之后,拥有全新 PID 的进程 B 会执行 exec 操作,保证执行新的程序,真正开始进程 B 的运行逻辑。
进程 B 运行过程中,假若 B 正常或者异常退出,那么内核就会给进程 B 的父进程 A,发送一个 SIGHOLD 信号,父进程 A 则对退出的进程 B 执行 wait 操作,实现对 B 进程资源的回收,如进程描述符 task_struct 等。
创建逻辑1. Dockerd 通过 GRPC 与 Containerd 通信,传输一些镜像信息等
2. Containerd 通过 exec 系统调用创建 Containerd-shim 进程(创建过程中会传递 namespace 等参数,用于实现之后不同容器进程视图和资源的隔离)
3. Contaienrd-shim 进程通过 exec 系统调用创建容器进程
补充:execve 系统调用创建出来的进程是全新的,不会从原进程复制进程结构
创建容器1. 容器镜像的下载是由 dockerd 完成的,但容器的创建和运行就需要 containerd(docker-containerd) 来完成了。
2. Dockerd 与 docker-containerd 之间是通过 grpc 协议通信的
3. 当 docker-containerd 收到 dockerd 启动容器的请求之后,会做一些初始化工作,然后启动 docker-containerd-shim 进程,并将相关配置作为参数传给它。
4. docker-containerd 负责管理所有本机正在运行的容器,而一个 docker-containerd-shim 进程只负责管理一个运行的容器,它相当于 docker-runc 的一个封装,充当 docker-containerd 和 docker-runc 之间的桥梁,docker-runc 能干的就交给 docker-runc 来做,docker-runc 做不了的就放到这里来做。
创建完成后没有见到 runc 进程1. 在容器启动的过程中,docker-runc 进程是作为 docker-containerd-shim 的子进程存在的。
2. docker-runc 进程根据配置找到容器的 rootfs 并创建子进程(根据容器的entrypoint和cmd创建进程) 作为容器中的第一个进程。
3. 当这一切都完成后 docker-runc 进程退出,然后容器进程由 docker-runc 的父进程 docker-containerd-shim 接管
为什么需要 docker-containerd-shim为什么在容器的启动或运行过程中需要一个 docker-containerd-shim 进程呢?把它移除掉整个架构会更简洁也更优美一些!事实上 docker-containerd-shim 的存在是非常有必要的,其目的有如下几点:
1. 它允许容器运行时(即 runC)在启动容器之后退出,简单说就是不必为每个容器一直运行一个容器运行时(runC)
2. 即使在 containerd 和 dockerd 都挂掉的情况下,容器的标准 IO 和其它的文件描述符也都是可用的
3. 向 containerd 报告容器的退出状态
此处 docker-containerd-shim 进程,指的就是 containerd-shim 进程

理解OCI标准规定的容器状态转移

在运行 busybox 容器前让我们先来看看 OCI 都定义了哪几种容器状态,以及这些状态是如何转移的。先看容器的状态:

  • creating:使用 create 命令创建容器,这个过程称为创建中。
  • created:容器已经创建出来,但是还没有运行,表示镜像文件和配置没有错误,容器能够在当前平台上运行。
  • running:容器里面的进程处于运行状态,正在执行用户设定的任务。
  • stopped:容器运行完成,或者运行出错,或者 stop 命令之后,容器处于暂停状态。这个状态,容器还有很多信息保存在平台中,并没有完全被删除。
  • paused:暂停容器中的所有进程,可以使用 resume 命令恢复这些进程的执行。

下图则是对容器不同状态间转移的一个粗略描述:

img

从进程角度看 Docker 创建容器

dockerd、contaierd、containerd-shim、runC通信机制分析

img

通信流程:

  1. docker daemon 模块通过 grpc 和 containerd模块通信:dockerd 由libcontainerd负责和containerd模块进行交换, dockerd 和 containerd 通信socket文件:docker-containerd.sock
  2. containerd 在dockerd 启动时被启动,启动时,启动grpc请求监听。containerd处理grpc请求,根据请求做相应动作;
  3. 若是start或是exec 容器,containerd 拉起一个container-shim , 并通过exit 、control 文件(每个容器独有)通信;
  4. container-shim被拉起后,start/exec/create拉起runC进程,通过exit、control文件和containerd通信,通过父子进程关系和SIGCHLD监控容器中进程状态;
  5. 若是top等命令,containerd通过runC二级制组件直接和容器交换;
  6. 在整个容器生命周期中,containerd通过 epoll 监控容器文件,监控容器的OOM等事件

NOTE: containerd,container-shim 组件本质上runC 和dockerd 间的adapter中间件,容器本身有runC单独完成 — 使用runC可以单独完成一个容器部署。

理解 Docker 和 containerd 的容器进程管控方式

  • containerd,containerd-shim和runc的依存关系

首先,我们来看下containerd,containerd-shim和容器进程的关系:

root      2156  1733  0 13:17 pts/0    00:00:00 ./bin/containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim /home/fankang/docker/containerd-0.2.4/src/github.com/docker/containerd/bin/containerd-shim --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --runtime docker-runc      # containerd 进程,纳管所有容器,就是所有 containerd-shim 进程
root      2198  2156  0 13:45 pts/0    00:00:00 /home/fankang/docker/containerd-0.2.4/src/github.com/docker/containerd/bin/containerd-shim nginx /home/fankang/mycontainer runc  # containerd-shim 进程,一个 shim 进程纳管一个容器进程,因此 1 个 shim 进程可以等同于 1 个容器进程
root      2214  2198  0 13:45 ?        00:00:00 /usr/bin/python /usr/bin/supervisord  # 容器进程

可以看出,containerd是containerd-shim的父进程,contaienrd-shim是容器进程的父进程。
而杀死containerd进程后,contaienrd-shim和容器进程依然存在,只是containerd进程成孤儿进程后,被1进程接收了:

root      2301     1  0 13:50 pts/0    00:00:00 /home/fankang/docker/containerd-0.2.4/src/github.com/docker/containerd/bin/containerd-shim nginx /home/fankang/mycontainer runc
root      2317  2301  1 13:50 ?        00:00:00 /usr/bin/python /usr/bin/supervisord

所以,为了简化三个进程的关系,我们从下面4种情况来分析:

  1. containerd进程存在的情况下,杀死containerd-shim进程;
    • 结论:容器进程退出。在containerd运行的情况下,杀死containerd-shim,容器进程会退出。
  2. containerd进程存在的情况下,杀死容器进程;
    • containerd存在的情况下,杀死容器进程,conainerd-shim主动退出,containerd触发exit事件以清理该容器。
  3. containerd进程不存在的情况下,杀死containerd-shim进程,然后启动containerd进程;
    • 容器进程还在,成为孤儿进程,被进程1接收
    • 启动containerd,容器进程消失
  4. containerd进程不存在的情况下,杀死容器进程,然后启动containerd进程;
    • 所有进程都不存在。
    • 所以,在Go中,默认子进程的退出会引起父进程的退出。

理解 k8s 1.21 后 containerd 的管控方式

  • Containerd shim 进程 PPID 之谜

Kubernetes 自从 1.21 版废除对 dockershim 的支持,改用 Containerd 作为默认的容器运行时。

我们使用 ps 命令来观察一下 Containerd 相关进程:

$ ps -ef | grep containerd
root      1002     1  3 02:29 ?        00:00:19 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --container-runtime-endpoint=/run/containerd/containerd.sock
root      1011     1  1 02:29 ?        00:00:07 /usr/bin/containerd
root      1622     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 5ca114f2233d4638fae47b86ed058c0774a248168b3bb66d41f94bdcd1e56626 -address /run/containerd/containerd.sock
root      1624     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 4727e762c3fa1a7f2d4beebfeb79a4ee22298e48018beee5204cc8fd98e7bd41 -address /run/containerd/containerd.sock
root      1660     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 35d2d1cafe57afde4a1e3041a75d216e48f75312760b1c77ffaa7acc0ee8802f -address /run/containerd/containerd.sock
root      1661     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id c7769877c77465c86e803b1522ad44ec5ee62b4ff90d1e7f9afd13680215f048 -address /run/containerd/containerd.sock
root      2003     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id f4165704fb540c52586e2edcff1c420fe4177d0494205a019201689c7d65d5d4 -address /run/containerd/containerd.sock
root      2090     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id ebe6394198639bfe1e9a09e5e72bf4fc6f55fb1c1e617cdda5409a7d35941010 -address /run/containerd/containerd.sock
root      2637     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 68611b98a5fa4e19a18494898d084b1c025ff94bf840ffc035dd00694bb3fd17 -address /run/containerd/containerd.sock
root      2792     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 5aba84afafa0e41a93034903d079aaa5ba730b7b134fff3a1fd533e4db85f28b -address /run/containerd/containerd.sock
root      2957     1  0 02:29 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id ced3e51f8774077aaaf52bf6e381f0e1d21be585cc1f2f1abd285a1588da638b -address /run/containerd/containerd.sock

我们会发现了一个奇怪的现象,containerd 进程是由 PID 1 号进程 systemd 托管,所以 containerd 进程的父进程 ID(PPID)毫无疑问就是 1;而由 containerd 拉起的 containerd-shim 进程的 PPID 也是 1,但实际上 containerd-shim 并非由 systemd 托管。

这一定是有意为之,containerd-shim 进程与 containerd 进程彻底脱离关系,containerd 进程即使崩溃重启就不会对 containerd-shim 进程造成任何影响,而 Kubernetes 集群中的各个容器进程正是由 containerd-shim 拉起的。

那么 Containerd 是如何做到 fork 出 containerd-shim 进程后保留其 PPID 的呢?

最终落实下来是一个名为 do_fork 的函数 https://github.com/torvalds/linux/blob/v3.10/kernel/fork.c#L1557-L1636:

fork 系统调用会通过 copy_process 函数复制进程结构,第一个参数 clone_flags 标记子进程从父进程中需要继承的资源清单。

再找同一文件下 copy_process 函数的定义 https://github.com/torvalds/linux/blob/v3.10/kernel/fork.c#L1124-L1533:

clone_flags 参数只要传入 CLONE_PARENT 即可在复制进程结构时保留原先的父进程信息。

结合上面 ps 命令的输出,containerd-shim 进程启动时确实带上了 namespaceidaddress 这几个参数,但可执行二进制文件却是通过 os.Executable() 得到的,并不是通过变量传递来的,我们已经知道了这个执行文件就是 /usr/bin/containerd-shim-runc-v2。那就说明 containerd-shim 进程也是由一个 containerd-shim 父进程拉起来的。

结合源码 https://github.com/containerd/containerd/blob/v1.4.3/runtime/v2/shim/shim.go#L221-L229

还有 StartShim 方法 https://github.com/containerd/containerd/blob/v1.4.3/runtime/v2/runc/v2/service.go#L174-L286

**证实了我的猜想,containerd-shim 进程都是由一个 containerd-shim 父进程通过 start 子命令启动的。**那就回到原来的问题了,containerd-shim 是如何将 PPID 设置为为 1 的,毕竟 execsnoop 显示该进程实际的 PPID 是 1693。

containerd-shim 进程是通过 os/exec 包中的 Start 方法启动的 https://github.com/golang/go/blob/master/src/os/exec/exec.go#L370-L458:

再跳到 os 包的 StartProcess 函数 https://github.com/golang/go/blob/master/src/os/exec_posix.go:

再跳到 syscall 包的 StartProcess 函数 https://github.com/golang/go/blob/master/src/syscall/exec_unix.go

因为 Containerd 运行在 Linux 系统,所以 forkAndExecInChild 函数要看 Linux 的那份 https://github.com/golang/go/blob/master/src/syscall/exec_linux.go

根据 https://github.com/golang/go/blob/master/src/syscall/zsysnum_linux_amd64.go#L68 在 Linux amd64 架构中 SYS_EXECVE 为 59,这与 Linux 系统调用表 sys_call_table 是完全相同的。

再追下去就是汇编了。。。

所以搞了半天最终的系统调用还不是 fork。。。execve 落实下来是一个名为 do_execve 的函数 https://github.com/torvalds/linux/blob/v3.10/fs/exec.c

使用 execve 系统调用创建出来的进程是全新的,不会从原进程复制进程结构。

所以容器进程是通过系统调用 execve 创建出来的

62.028335          1688   1118 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -address /run/containerd/containerd.sock -publish-binary /usr/bin/containerd -id 66cbc3f2e8a67d59177959801cd6b9b3c76cb27833068c426e14dee5667b20d3 start   # 此处有个 start
62.061869          1698   1693 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 66cbc3f2e8a67d59177959801cd6b9b3c76cb27833068c426e14dee5667b20d3 -address /run/containerd/containerd.sock

根据源码当 1693 进程也就是 1688 进程很快结束后,1698 也就成为了孤儿进程,孤儿进程会被 init 进程也即是 1 号进程(systemd)收养,这就是 containerd-shim 进程的 PPID 全都是 1 的原因。

kubelet是怎样创建容器的

Dockershim 作用:把外部收到的请求转化成 docker daemon 能听懂的请求,让 Docker Daemon 执行创建、删除等容器操作。

CRI容器运行时接口

  • 参考链接:https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md
  • CRI:容器运行时接口 container runtime interface,CRI 中定义了容器和镜像两个接口,实现了这两个接口目前主流的是:CRI-O、Containerd。(目前 PCI 产品使用的即为 Containerd)。

CRI接口的具体用处就在于

  1. 对容器操作的接口,包括容器的创建、启动和停止.即createstop等操作。
  2. 对镜像的操作,下载、删除镜像等. 即pullrmi等操作。
  3. podsandbox
  1. Kubelet 通过 CRI 接口(gRPC)调用dockershim,请求创建一个容器。CRI 即容器运行时接口,这一步中,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。它的一个参考实现叫做 runC。于是,containerd-shim 在这一步需要调用 runC 这个命令行工具,来启动容器。
  6. runC 启动完容器后本身会直接退出,containerd-shim 则会成为容器进程的父进程,负责收集容器进程的状态,上报给 containerd,并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理,确保不会出现僵尸进程。

img

k8s 中 shim 的变化

  • docker,containerd,runc,docker-shim 之间的关系

在容器标准的大战中,docker公司围绕docker swarm推出了CNM,Google等以屠龙者的姿态围绕 k8s 推出了CNI,目前来看,k8s已经奠定了在 PaaS 事实的地位。

CRI 是一套通过 protocol buffers 定义的 API,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SG6AfPAj-1678071207067)(/Users/dufengyang/Library/Application Support/typora-user-images/image-20230306103608224.png)]

kubelet 实现了 client 端,CRI shim 实现 server 端。只要实现CRI对应的接口,就能接入 k8s 作为 Container Runtime。

  1. k8s 1.5 中自己实现了 docker CRI shim,此时启动容器的流程如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TlTY5T3H-1678071207067)(/Users/dufengyang/Library/Application Support/typora-user-images/image-20230303150331590.png)]

  2. 从 containerd 1.0 开始,为了能够减少一层调用的开销(废掉docker,也就是把上边的docker cri shim和docker踢掉),containerd 开发了一个新的 daemon,叫做 CRI-Containerd,直接与 containerd 通信,从而取代了 dockershim:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tiBwdcdh-1678071207068)(/Users/dufengyang/Library/Application Support/typora-user-images/image-20230303150351430.png)]

  3. 但是这仍然多了一个独立的 daemon,从 containerd 1.1 开始,社区选择在 containerd 中直接内建 CRI plugin,通过方法调用来进行交互,从而减少一层 gRPC 的开销,最终的容器启动流程如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IplTFGUN-1678071207068)(/Users/dufengyang/Library/Application Support/typora-user-images/image-20230303150444829.png)]

    最终的结果是 k8s 的 Pod 启动延迟得到了降低,CPU 和内存占用率都有不同程度的降低。

  4. 但是这还不是终点,为了能够直接对接 OCI 的 runtime 而不是 containerd,社区孵化了 CRI-O 并加入了 CNCF。CRI-O 的目标是让 kubelet 与运行时直接对接,减少任何不必要的中间层开销。CRI-O 运行时可以替换为任意 OCI 兼容的 Runtime,镜像管理,存储管理和网络均使用标准化的实现。

@xuxinkun 的文章中有个图将他们之间的关系描绘的很清楚:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vqmWbwF9-1678071207069)(/Users/dufengyang/Library/Application Support/typora-user-images/image-20230303150508901.png)]

以下更新于2021年4月9日

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C# 中的abstract和virtual

重新理解了下关键字abstract,做出以下总结: 1.标记为abstract的类不能实例化,但是依然可以有构造函数,也可以重载构造函数,在子类中调用 2.abstract类中可以有abstract标记的方法和属性,也可以没有,被标记…

Linux网络编程学习(网络基础)

文章目录网络基础浅谈计算机网络背景了解协议网络传输基本流程局域网传输基本流程跨网络的数据传输流程网络基础 浅谈计算机网络背景 ​ 计算机最早是没有网络的,当时想要数据之间交互就需要人来传递,但是这样效率非常低,而且也容易出错&am…

机器人姿态规划的三种常见方法:欧拉角、角轴和四元数

参考文献: 1. 布鲁诺西西里安诺等[意] 《机器人学:建模、规划与控制》 2. 四元数小总结 - 孤独の巡礼 - 博客园 (cnblogs.com) 3. 基于单位四元数的姿态插补(Matlab) - 知乎 (zhihu.com) 4. 基于四元数的工业机器人姿态规划与插…

javaScript基础面试题 --- JS作用域

面试10家公司,得有8家会问到作用域的题。所以说JS的作用域一定要弄清楚,非常重要! 1、除了函数之外,JS没有块级作用域 2、作用域链:内部可以访问外部的变量,但是外部不能访问内部变量,如果内部有…

Docker搭建jenkins(Vue自动化部署)

前言 需要提前准备的条件 Docker环境 一、jenkins镜像 # 查询镜像 docker search jenkins# 下载镜像 # lts稳定版 docker pull jenkins/jenkins:lts#查看镜像 docker images二、启动Jenkins容器 创建挂载文件夹,并且进行文件授予权限 #创建文件夹 mkdir -p /home/j…

2023年湖北武汉初级、中级工程师职称评审条件和评审流程是什么呢?

职称分三个级别:初级、中级、高级,原则是需要一级一级的,但是有的地方可以跨级申报。 一、湖北武汉初级、中级工程师职称评定条件: 助理职称评定条件: 1.大学本科毕业,从事专业技术工作1年以上。 2.大学专…

Java Web 实战 02 - 多线程基础篇(1)

Java Web 实战 02 - 多线程基础篇 - 1一 . 认识线程1.1 概念1.1.1 什么是线程?1.1.2 为什么要有多个线程?1.1.3 进程和线程的区别(面试题)1.2 第一个多线程程序1.3 创建线程1.3.1 继承Thread类1.3.2 实现Runnable接口1.3.3 继承 Thread 类 , 使用匿名内部类1.3.4 实现 Runnab…

Linux嵌入式开发 | 汇编驱动LED(1)

文章目录🚗 🚗Linux嵌入式开发 | 汇编驱动LED(1)🚗 🚗初始化IO🚗 🚗STM32🚗 🚗使能GPIO时钟🚗 🚗设置IO复用🚗 &#x1f6…

3.5多线程

一.线程的状态1.NEW安排了工作,还未开始行动把Thread对象创建好了,但是还没有调用startjava内部搞出来的状态,与PCB的状态没什么关系2.TERMINATED工作完成了操作系统的线程执行完毕,销毁了,但是Thread对象还在,获取的对象3.RUNNABLE可以工作的,又可以分为正在工作中和即将开始工…

聊聊内存那些事(基于单片机系统)

单片机的RAM和ROM单片机的ROM,叫只读程序存储器,是FLASH存储器构成的,如U盘就是FLASH存储器。所以,FLASH和ROM是同义的。单片机的程序,就是写到FLASH中了。而RAM是随机读/写存储器,用作数据存储器&#xff…

SpringBoot笔记(一)入门使用

一、为什么用SpringBootSpringBoot优点创建独立Spring应用内嵌web服务器自动starter依赖,简化构建配置自动配置Spring以及第三方功能提供生产级别的监控、健康检查及外部化配置无代码生成、无需编写XMLSpringBoot缺点人称版本帝,迭代快,需要时…

电路基础(1)电路模型和电路定律

电路中的电压、电流之间具有两种约束,一种是由电路元件决定的元件约束;另一种是元件间连接而引入的几何约束(就是拓扑约束),后者由基尔霍夫定律来表达。基尔霍夫定律是集总参数电路的基本定律。 1.电路和电路模型电源又…

电路模型和电路定律(2)——“电路分析”

各位CSDN的uu们你们好呀,好久没有更新电路分析的文章啦,今天来小小复习一波,之前那篇博客,小雅兰更新了电路的历史以及电压电流的参考方向,这篇博客小雅兰继续!!! 电阻元件 电压源和…

FFMPEG 安装教程windowslinux(CentOS版)

ps: 从笔记中迁移至blog 版本概述 Windows 基于win10 Linux 基于CentOS 7.6 一.Windows安装笔记 1.下载安装 https://ffmpeg.org/download.html 2 解压缩,拷贝到需要目录,重命名 3 追加环境变量 echo %PATH%setx /m PATH "%PATH%;F:\dev_tools\…

用C/C++制作一个简单的俄罗斯方块小游戏

用C/C制作一个简单的俄罗斯方块小游戏 用C/C制作一个简单的俄罗斯方块小游戏 0 准备1 游戏界面设计 1.1 界面布局1.2 用 EasyX 显示界面1.3 音乐播放 2 方块设计 2.1 方块显示2.2 随机生成一个方块2.3 方块记录 3 方块移动和旋转 3.1 方块的移动3.2 方块的旋转3.3 方块的碰撞和…

基于 WebSocket、Spring Boot 教你实现“QQ聊天功能”的底层简易demo

目录 前言 一、分析 1.1、qq聊天功能分析 1.2、WebSocket介绍 1.2.1、什么是消息推送呢? 1.2.2、原理解析 1.2.3、报文格式 二、简易demo 2.1、后端实现 2.1.1、引入依赖 2.1.2、继承TextWebSocketHandler 2.1.3、实现 WebSocketConfigurer 接口 2.2、…

LeetCode096不同的二叉搜索树(相关话题:卡特兰数)

目录 题目描述 解题思路 代码实现 进出栈序列理解卡特兰数分析策略 相关知识 参考文章 题目描述 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: …

《程序员面试金典(第6版)》面试题 02.07. 链表相交

题目描述 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意,函数返回结果…

socket本地多进程通信基本使用方法和示例

目录 前言: socket是什么 socket基本原理框图 socket基本函数 1 socket() 函数 2 bind()函数 3 connect()函数 4 listen() 函数 5 accept() 函数 6 read() write() send() recv()函数 7 close()函数 8 字节序转换(hton) 示例代码 …

使用 Pulumi 打造自己的多云管理平台

前言在公有云技术与产品飞速发展的时代,业务对于其自身的可用性提出了越来越高的要求,当跨区域容灾已经无法满足业务需求的情况下,我们通常会考虑多云部署我们的业务平台,以规避更大规模的风险。但在多云平台部署的架构下&#xf…