K8s:一文认知 CRI,OCI,容器运行时,Pod 之间的关系
写在前面
-
博文内容结构为结合 华为云云原生课程
整理而来,部分内容做了补充 -
课程是免费的,有华为云账户就可以看,适合理论认知,感觉很不错。 -
有需要的小伙伴可以看看,链接在文末 -
理解不足小伙伴帮忙指正
「 不必太纠结于当下,也不必太忧虑未来, 当你经历过一些事情的时候, 眼前的风景已经和从前不一样了。 ——村上春树」
为什么从 CRI
讲起,因为 k8s 集群使用 kubelet
服务通过 CRI
接口和对应的 runtime(运行时)
交互,从而控制管理容器。
那 CRI
是什么? CRI 是一个 Kubernetes API
,它定义了 Kubernetes
与不同容器运行时
交互的方式。因为它在规范中是标准化的,所以可以选择要使用的 CRI
实现或编写自己的实现。
CRI
在 K8s
生态中通过 CRI
接口来对 容器运行时进行管理,从而实现对容器镜像的管理,具体一点,通过 kubelet
调用容器运行时的 grpc
接口。
面向接口编程,类比在刚学编程时, Java
中,操作数据库,使用 JDBC API
来连接不同的数据库实现 CRUD
,这里具体的数据操作通过不同数据库的驱动包
来实现。CRI
可以单纯理解为JDBC
,CRI 实现类比不同的数据库驱动包
对于 CRI
接口,有下面的一些实现。
dockershim
: 由于 docker
没有实现 CRI 接口,所以 kubernetes
在最初对接 docker api
的时候提供了 CRI
接口适配器, 但是这是一种冗余的行为,k8s
只是需要 docker
中的 containerd
,完全可以直接使用 containerd
,所以在之后版本中删除了这部分代码, 在 kubernetes 1.21 版本已经将其标注为废弃接口。在 1.24 版本中彻底移除了该部分代码, 不过 如果任然想使用 docker ,可以使用开源项目 cri-docker
来代替。在部署时需要单独指定 socket
。
docker 的前置依赖,可以看到它依赖 containerd.service
┌──[root@vms100.liruilongs.github.io]-[~/ansible/openkruise]
└─$systemctl list-dependencies docker | head -n 3
docker.service
● ├─containerd.service
● ├─docker.socket
CRI-containerd
:基于 containerd 的 CRI 兼容插件,通过 containerd
中的 CRI插件实现了CRI接口
,当前使用最广泛的 CRI接口接口实现
。性能好,功能全,适合需要利用 containerd 强大功能的场景。
CRI-O
:专注于在 kubernetes 运行容器的轻量级 CRl 接口实现(不关注开发态),CRI-O 是基于 Open Container Initiative (OCI)
标准开发的独立容器管理引擎,独立性好,适合注重兼容性和移植性的场景
CRI 接口由两部分组成
CRI 接口由两部分组成:
-
通过 RuntimeServiceServer
属性来描述管理容器生命周期的相关行为,包括创建、删除、启动、停止等容器操作, -
通过 ImageServiceServer
属性来描述管理镜像相关的行为,如拉取、推送镜像等操作。
通过对这两个行为接口委托
,实现 CRI 接口,具体来说:
-
RuntimeServiceServer
定义了管理容器生命周期需要调用`的通用方法,如CreateContainer、StartContainer、StopContainer等。这些方法主要用于管理容器运行时状态。 -
ImageServiceServer
定义了镜像管理需要调用的通用方法,如ListImages、PullImage、RemoveImage等。这些方法主要用于管理容器镜像资源。
type grpcServices interface {
runtime.RuntimeServiceServer
runtime.ImageServiceServer
}
-
运行时
:负责容器的生命周期管理
,包括容器创建,启动、停止、日志和性能采集等接口。 -
镜像
:负责容器镜像的管理
,包括显示镜像、拉取镜像、删除镜像等接口。
在早期的CRI
实现中,Shimv1
是最常见的容器运行时接口实现之一。它通过创建一个额外的代理进程(Shim)来管理容器的生命周期,并与容器运行时进行通信。然而,Shimv1
存在一些性能和可靠性方面的限制。
为了改进CRI的性能和稳定性,Shimv2
被引入作为CRI
的改进版本。
CRI Shimv2
Shimv2
使用了ttrpc(Tiny Transport Protocol RPC)作为通信协议
,通过与容器运行时直接通信而无需创建额外的代理进程。这种直接的通信方式可以提供更高效、更可靠的容器运行时接口。
OCI
CRI 接口实现和容器运行时交互,这里就要讲到 OCI
OCI
组织:Linux基金会于2015年6月成立OCI(Open Container Initiative)
组织,旨在围绕容器格式和运行时
制定一个开放的工业化标准
。
目前主要有两个标准文档:容器运行时标准(runtime spec)
和容器镜像标准(image spec)
Runtime spec
:容器运行时标准
,定义了容器状态和配置文件格式,容器生命周期管理命令格式和参数等。
image spec
:镜像标准包
,定义了容器镜像的文件系统、config 文件、manifest 文件、index 文件等。
容器标准中包含:
容器命令:容器生命周期管理命令、包括创建、启动、停止、删除等。
#runc create <container id>
#runc start <container id>#runc state <container id>
#runc kill <container-id><signal>
#runc delete <container-id>
config.json
文件是OCI(Open Container Initiative)
规范定义的容器配置文件
,它定义了容器运行时需要了解和使用的所有配置信息
。
-
rootfs
:容器内部文件系统挂载点信息 -
mounts
:容器内外部挂载的文件系统信息 -
process
:容器内第一个进程(pid=1)的设置,如cmd、args等 -
cgroups
:容器要加入的cgroup信息 -
namespaces
:容器所在的namespace,比如pid、network等 -
caps
:容器能使用的Linux capabilities -
annotations
等其它配置
主流的容器运行时:
这里需要说明一下,我们常讲的 容器运行时
是一个 混合概念,包含低级别容器运行时,和高级别容器运行时,
低级容器运行时
,比如runc、lxc、gVisor、Kata
容器等,它们主要负责真正与内核进行交互,创建和管理容器运行时的内核级别资源,如cgroups、namespaces
等。但无法直接管理镜像。
高级容器运行时
,比如Docker
引擎、Podman
、containerd
等,它们基于低级容器运行时提供了更高层次的管理能力,可以管理容器生命周期并直接调用低级运行时,同时也内置镜像管理能力,能够拉取、推送镜像等。
两者关系是,高级运行时对低级运行时进行抽象,向上提供完整的容器管理接口。高级运行时在内部会调用对应的低级运行时来管理内核级资源和运行容器。
runc
runc: docker 捐献给 OCI 社区的一个 runtime spec
的参考实现,docker 容器也是基于 runc 创建的。
利用 Linux 内核特性,来隔离进程:
-
Namespace
:资源和信息的可见性隔离,通过namespace隔离,容器中的应用只能看到分配到该容器的资源、其他主机上的信息在容器中不可见。常用的namespace有PID(进程号)、MNT(挂载点)、NET(网络)、UTS(主机名)和IPC(跨进程通信)等 -
Cgroups
:资源使用量的隔离,通过cgroup、限制了容器使用的资源量,通过不同的子系统,限制不同的资源。包括CPU、内存、io带宽、大页、fd 数等等 -
Capability
:权限限制(最低特权原则),Capability是Linux系统设计的一种安全机制。它定义了一组特权标记
,可以细粒度控制进程对系统资源的访问权限
,通过对进程的capability定义,限制容器中的进程调用某些系统调用,以达到容器进程无法逃逸到主机
的目的,比如容器中的进程是不具有以下capability
的: SYS ADMIN/MKNOD/SYS RESOURCE/SYS MODULES.…
容器主进程
1 1898 1898 1034 ? -1 Sl 0 0:34 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 057fb
1898 1957 1957 1957 ? -1 Ss 65535 0:00 \_ /pause
1 2013 2013 1034 ? -1 Sl 0 2:58 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 7d167
2013 2080 2080 2080 ? -1 Ss 0 0:17 \_ haproxy -W -db -f /usr/local/etc/haproxy/haproxy.cfg
2080 2133 2080 2080 ? -1 Sl 0 48:48 \_ haproxy -W -db -f /usr/local/etc/haproxy/haproxy.c
图片来自华为云课程
Kata-runtime
图片来自华为云课程Kata-runtime:一种基于 虚拟化
的安全隔离的 OCI runtime spec
的实现。
-
虚拟化隔离:每个pod都运行在一个独立的 虚拟机
中,提供虚拟化接口对接不同的虚拟化实现,包括qemu、cloud hypervisor、firecracker 等等 -
轻量化:为了达到和容器近似的使用体验,需要对各组件进行裁剪,达到轻量化和启动加速的目的,对于hypervisor,去除通用虚拟化的各种不必要的设备、总线等。对于guestkernel,也裁剪了大量不需要的驱动和文件系统模块。而运行在虚拟机中的1号进程(一般为kata-agent),资源占用可小于1MB。 -
主机资源访问:通过virtio、vfio等方式访问主机资源,如virtio-blk(块设备)、virtio-fs(文件)、virtio-net(网络)、vfio(物理设备)、vhost-user(用户态网络或存储)
gVisor
gVisor
:是Google开源的一款安全容器运行时,它使用了系统调用拦截(System call filtering)
这一技术来实现容器安全隔离。
-
gVisor通过内核模块注入方式,在用户空间和内核空间之间插入一个中间层。 -
这个中间层对所有从容器发起的系统调用请求进行拦截和filtering。 -
根据预先定义的安全策略,选择性通过或拒绝这些系统调用请求,限制容器对内核资源的访问。
通过这种方式:
-
gVisor无需真正 virtualize
内核功能,性能开销小。 -
但能实现丰富的安全隔离,如文件系统读写限制、网络隔离等。
所以总体来说,gVisor采用轻量级的系统调用过滤技术,在不影响性能的情况下,有效地隔离和限制容器对系统资源的访问,从而有效提升容器安全性。
图片来自华为云课程-
虚拟内核:设置进程的4种模式,HRO、HR3、GRO、GR3,通过拦截系统调用,实现了一个虚拟内核,用户进程与host kernel不直接交互 -
拦截系统调用的方式:ptrace、kvm -
优点:额外内存消耗小,容器启动速度快 -
缺点:系统调用慢,导致I0、网络等性能差,由于是模拟内核,有POSIX兼容性问题
「知道了CRI,OCI,可能会有这样一个疑问,容器运行时(runtime)是如何和 Pod 关联起来的?」 这里就要讲到 RuntimeClass
RuntimeClass
RuntimeClass
是 Kubernetes 中的一个对象类型
,用于定义集群中的特定运行时``。RuntimeClass
允许您通过指定 overhead(开销)
和 nodeSelector(节点选择器)
来自定义特定运行时的资源和调度行为
。
RuntimeClass
的主要作用是将容器运行时与 Pod 关联起来。它定义了一组属性,包括运行时名称、调度器名称、节点选择器和资源限制。通过将 Pod
的 RuntimeClassName
字段设置为特定的 RuntimeClass
名称,可以指定该 Pod 使用特定的容器运行时。
使用 RuntimeClass,可以针对不同的运行时场景进行资源和调度的优化。例如,您可以定义一个 RuntimeClass,针对 CPU 密集型任务使用资源更多的容器运行时,并通过节点选择器将该 RuntimeClass 的 Pod 调度到具备较高 CPU 资源的节点上。
Runtime Plugin
:containerd中的runtime插件配置,定义了runtime名称、二进制路径、传递的annotation、特权容器模式等等。
关于容器引擎和运行时机制原理认知就和小伙伴们分享到这里
博文部分内容参考
文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
https://edu.huaweicloud.com/courses
© 2018-2023 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)
- 点赞
- 收藏
- 关注作者
评论(0)