内容简介:【编者的话】作为后台支撑,Kubernetes优势明显,具有自动化部署、服务伸缩、故障自我修复、负载均衡等特性。咪付的蓝牙过闸系统和全态识别AI系统的后台支撑采用了Kubernetes,经过线上的长期运行,其状态良好运行平稳。蓝牙过闸系统和全态识别AI系统有着不同的数据特性,对数据的安全要求及运行效率也各不一样,因此如何选择容器的运行时成为了一个重点考虑的因素。CRI(Container Runtime Interface)是Kubernetes提出的容器运行时接口规范。
【编者的话】作为后台支撑,Kubernetes优势明显,具有自动化部署、服务伸缩、故障自我修复、负载均衡等特性。咪付的蓝牙过闸系统和全态识别AI系统的后台支撑采用了Kubernetes,经过线上的长期运行,其状态良好运行平稳。
蓝牙过闸系统和全态识别AI系统有着不同的数据特性,对数据的安全要求及运行效率也各不一样,因此如何选择容器的运行时成为了一个重点考虑的因素。
CRI的由来
CRI(Container Runtime Interface)是Kubernetes提出的容器运行时接口规范。
在Kubernetes体系中,是由Kubelet组件负责与容器运行时交互的。Kubelet调用容器运行时的流程如上图所示。CRI shim是实现CRI接口的gRPC server服务,负责连接Kubelet和Container runtime,Container runtime是容器运行时工具,它为用户进程隔离出一个独立的运行环境;具体的流程是Kubelet调用CRI shim接口,CRI shim响应请求,然后调用底层的Container runtime工具运行容器。Kubelet、CRI shim和Container runtime都部署在一个Kubernetes worker节点上,前两者是以独立的守护进程的方式启动的,而Container runtime不是守护进程,它通常是一个命令行工具。 如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态 。
Kubernetes在v1.5版本之前是没有CRI接口的,那时Kubelet源码内部只集成了两个容器运行时(Docker和rkt)的相关代码。这两种容器运行时并不能满足所有用户的使用需求,在某些业务场景,用户对容器的安全隔离性有着更高的需求,用户希望Kubernetes能支持更多种类的容器运行时。因此,Kubernetes在1.5版本推出了CRI接口,各个容器运行时只要实现了CRI接口规范,就可以接入到Kubernetes平台为用户提供容器服务。
CRI接口带来的好处,首先它很好的将Kubernetes和容器运行时解耦,容器运行时的每次更新迭代,都不必对Kubelet工程源码进行编译发布;其次解放了容器运行时更新迭代的步伐,也能保证Kubernetes的代码质量和平台的稳定。
OCI是什么
OCI规范(Open Container Initiative 开放容器标准),该规范包含两部分内容:容器运行时标准(runtime spec)、容器镜像标准(image spec)。其具体内容的定义如下图:
容器运行时标准
runtime spec包含配置文件、运行环境、生命周期三部分内容。
配置文件
config.json,包含容器的配置信息(mounts、process、hostname、hooks等)。
运行环境
定义运行环境,是为了确保应用程序在多个运行时之间,能具有一致的环境 。
容器生命周期
定义了运行时的相关指令及其行为:state、create、start、kill、delete、prestart hooks、poststart hooks、poststop hooks。
容器镜像标准
通常我们根据Dockerfile定义的内容制作镜像,目的是构建一个符合OCI标准的镜像文件,那么OCI标准镜像文件的内容都有哪些呢?在OCI规范里的image spec对容器镜像格式做了定义,它主要包括以下几块内容:
文件系统
描述了如何以layer的方式叠加成一个完整的文件系统,以及如何用layer去表示对文件作出的改动(增加、删除、修改)。
config文件
保存了文件系统的层级信息(每个层级的 hash 值、历史信息),以及容器运行时需要的一些信息(比如环境变量、工作目录、命令参数、mount 列表等)。
manifest文件
记录镜像元信息,包括Image Config和Image Layers。
index文件
可选的文件,指向不同平台的manifest文件,相当于整个镜像的入口,从这个文件可以获取整个镜像依赖的所有文件信息。
OCI项目
下表是兼容OCI规范的容器运行时项目:
容器运行时对比
按照底层容器运行环境依托的技术分类,我们将容器运行时分为以下三类:
- OSContainerRuntime(基于进程隔离技术)
- HyperRuntime(基于Hypervisor技术)
- UnikernelRuntime(基于unikernel)
通过下图对比它们之间的区别:
OSContainerRuntime下的Linux Container共享 Linux 内核,使用namespace、cgroup等技术隔离进程资源。namespace只包含了六项隔离(UTS、IPC、PID、Network、Mount、User),并非所有Linux资源都可以通过这些机制控制,比如时间和Keyring,另外,容器内的应用程序和常规应用程序使用相同的方式访问系统资源,直接对主机内核进行系统调用。因此即使有了很多限制,内核仍然向恶意程序暴露过多的攻击面。
HyperRuntime下的VM Container容器各自拥有独立Linux内核,资源隔离比Linux Container更彻底。但并不是说使用VM容器用户就可以高枕无忧,只是VM容器的攻击面比Linux容器小了很多,黑客要逃逸到宿主机就只剩下Hypervisor这个入口,所以说没有绝对的安全,相对来说VM容器更安全。
另一方面,VM容器的性能比不上Linux容器,因为Hypervisor这一层带来的性能损耗,在Linux容器这边是不存在的。
UnikernelRuntime下的容器同VM Container一样有着安全级别很高的运行环境,同样是使用Hypervisor技术进行容器隔离。
简单来说Unikernel是一个运行在Hypervisor之上的libOS系统,而libOS是由应用程序和libraries一起构建出的操作系统。
unikernel的特点如下:
性能好,应用程序和内核在同一地址空间,消除了用户态和内核态转换以及数据复制带来的开销。
更精简的内核,去掉了多余的驱动、依赖包、服务等,最终打包镜像更小,启动速度更快。
完全不可调试,在生产环境中如果遇到问题,只能依赖于收集到的日志进行排查,要不就是重启容器,原先熟悉的Linux排查方法和 工具 完全派不上用场。
Kubelet CRI架构
Kubernetes在引入CRI之后,Kubelet的架构如下图所示:
每一个容器引擎只需要自己实现一个CRI shim,对CRI请求进行处理,就可以接入Kubelet当中去。
我们所说的容器运行时,准确来说包含两部分,一部分是上层容器运行时CRI shim(即容器运行时管理程序,如Containerd、CRI-O),另一部分是下层容器运行时Container runtime(即容器运行时命令工具,如runc)。
CRI接口定义
CRI接口分为两部分,一个是容器运行时服务RuntimeService,负责管理pod和容器的生命周期;一个是镜像服务ImageService,负责管理镜像的生命周期。
当前CRI格局
目前实现了CRI的主流项目有:Docker、containerd、CRI-O、Frakti、Pouch,它们衔接Kubelet与运行时方式对比如下:
PS:由于rkt容器引擎目前未能完全兼容OCI规范,所以图中未将其包含进来。
Docker
Docker容器引擎安装后包含有这些组件:dockerd、Containerd、runC。
dockershim是内置在Kubelet的CRI gRPC server服务,它接收CRI请求后,通过unix本地套接字调用dockerd的API接口,再由dockerd请求下游的运行时组件Containerd和runc。调用的链路:dockershim => dockerd => Containerd => runc。
在当前CRI的请求链路上,dockerd只是简单接收CRI请求,在转换之后调用Containerd,dockerd还集成有其他功能,如network、swarm、volume等在Kubernetes平台下没有用武之地,可以说在CRI场景下 docker 显得笨重。
Containerd
Containerd项目是从早期的Docker源码中提炼出来的,它使用CRI插件来向kubelet提供CRI接口服务。
CRI插件是一个独立的项目,在Containerd编译时,如果go build命令没有显示设置参数-tags=no_cri,那么CRI插件将自动编译集成到Containerd的二进制文件中,然后在配置文件/etc/containerd/config.toml中声明启用CRI插件,就可以在Containerd中启动CRI shim服务了。
Containerd能支持多运行时,目前它内置了runc运行时,其他运行时如果要接入Containerd,则需要实现Containerd shim v2 gRPC接口,这样Containerd就可以通过shim v2调用其他运行时。他们的调用关系如下:Containerd --> shim v2 --> runtimes
CRI-O
CRI-O完整实现CRI接口功能,并且严格兼容OCI标准,CRI-O比Containerd更专注,它只服务于Kubernetes(而Containerd除支持Kubernetes CRI,还可用于Docker Swarm),从官网上我们可以了解到CRI-O项目的功能边界:
- 支持多种image格式
- 支持多种image下载方式
- 容器镜像管理
- 容器生命周期管理
- 提供CRI要求的监控、日志功能
- 提供CRI要求的资源隔离功能
CRI-O通过命令行调用默认运行时runC,所以runC二进制文件必须部署在目录/usr/bin/runc。CRI-O和Containerd调用runtime的方式不同,前者是通过Linux命令调用,后者是通过gRPC服务调用,所以只要符合OCI规范的runtime,都能直接接入CRI-O提供运行时服务,而除runC外的其他运行时要接入Containerd,只能走shim v2接口,因此我们看到像kata-runtime这样的运行时项目就是通过shim v2接口来适配Containerd的。
Frakti
Frakti是基于Hypervisor虚拟机管理程序的容器运行时,它相比其他的容器运行时具有如下这些功能特性:
PouchContainer
PouchContainer是阿里开源的容器引擎,它内部有一个CRI协议层和cri-manager模块,用于实现CRI shim功能。它的技术优势包括:
- 强隔离,包括的安全特性:基于Hypervisor的容器技术、lxcfs、目录磁盘配额、补丁Linux内核等。
- 基于P2P镜像分发,利用P2P技术在各节点间互传镜像,减小镜像仓库的下载压力,加快镜像下载速度。
- 富容器技术,PouchContainer的容器中除了运行业务应用本身之外,还有运维套件、系统服务、systemd进程管家等。
CRI命令工具
cri-tools是由kubernetes-sigs 开发维护的CRI命令工具(后简称crictl),它是为了在Kubernetes node节点上管理镜像、管理pod/container、与容器进行交互而设计的,crictl调用CRI接口获取相关信息、或者通过CRI的exec接口与容器交互。
crictl并不是docker cli的完全继任者,它仅提供了在Kubernetes node节点上常用的一些运维功能,具备一个较小的功能子集,而docker cli的命令更强大,事实上很多docker命令在生产环境并没有必要使用,所以crictl相对来说更安全,它避免运维人员在生产节点非法使用docker cli(rename、rm、rmi等子命令)造成一些人为的故障。所有实现了CRI接口的容器运行时,都可以通过crictl工具对其进行操作。
Kubernetes支持多运行时
为什么要支持多运行时呢?举个例子,有一个开放的云平台向外部用户提供容器服务,平台上运行有两种容器,一种是云平台管理用的容器(可信的),一种是用户部署的业务容器(不可信)。在这种场景下,我们希望使用runc运行可信容器(弱隔离但性能好),用runv运行不可信容器(强隔离安全性好)。面对这种需求,Kubernetes也给出了解决方案(使用API对象RuntimeClass支持多运行时)。
多运行时工作原理
Kubelet从apiserver接收到的Pod Spec,如果Pod Spec中使用runtimeClassName指定了容器运行时,则在调用CRI接口的RunPodSandbox()函数时,会将runtimeClassName信息传递给CRI shim,然后CRI shim根据runtimeClassName去调用对应的容器运行时,为Pod创建一个隔离的运行环境。
RuntimeClass配置
Kubernetes在v1.12中增加了RuntimeClass这个新API对象来支持多运行时(目的就是在一个woker节点上运行多种运行时)。
在Kubernetes中启用RuntimeClass时需要注意,尽量保持当前Kubernetes集群中的节点在容器运行时方面的配置都是同构的,如果是异构的,那么需要借助node Affinity等功能来调度Pod到已部署有匹配容器运行时的节点。
接下来只需要三步配置就可以为pod指定运行时:
- 在Kubernetes worker节点配置CRI shim;
- 创建RuntimeClass资源对象;
- 在pod中指定RuntimeClass。
配置CRI shim
例如CRI-O运行时的配置,需要在文件/etc/crio/crio.conf定义runtime的handler_name:
[crio.runtime.runtimes.${HANDLER_NAME}] runtime_path = "${PATH_TO_BINARY}"
创建RuntimeClass
RuntimeClass yaml定义如下:
apiVersion: node.k8s.io/v1beta1 # 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
在pod yaml中指定RuntimeClassName:
apiVersion: v1 kind: Pod metadata: name: mypod spec: runtimeClassName: myclass # ...
如何选择合适的容器运行时
在生产环境中,我们并不需要Docker的镜像打包、容器网络、文件挂载、swarm等这些能力,只需要部署Containerd + runC就可以在Node节点上运行Pod。因此在生产环境中我们可以不安装Docker,而是安装CRI shim组件和运行时工具来运行pod。在多个CRI shim和OCI工具之间,我们该如何选择呢?
首先对比Containerd和CRI-O调用runC的方式,runC代码内置在Containerd内部,通过函数调用;CRI-O是通过Linux命令方式调用runC二进制文件,显然前者属于进程内的函数调用,在性能上Containerd更具优势。其次对比runC和runV,这是两种完全不同的容器技术,runC创建的容器进程直接运行在宿主机内核上,而runV是运行在由Hypervisor虚拟出来的虚拟机上,后者占用的资源更多、启动速度慢,而且runV容器在调用底层硬件时(如CPU),中间多了一层虚拟硬件层,计算效率上不如runC容器。
因此建议结合自身业务特点、以及使用场景选择合适的容器运行时。在对用户的隔离没有很高诉求的情况下,可以优先考虑使用性能更好更轻量的Containerd + runc;在隔离性要求较高的业务场景下,推荐使用基于Hypervisor 的虚拟化容器运行时Frakti + runv。
以上所述就是小编给大家介绍的《为Kubernetes选择合适的容器运行时》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 像虚拟机一样运行容器
- 三大层面保护容器运行时安全
- 容器化运行TensorFlow Serving
- 以Docker容器的形式运行openvas
- 为Kubernetes选择合适的容器运行时
- docker 容器不自动退出结束运行的方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
LINUX与UNIX Shell编程指南
David Tansley / 徐炎、张春萌 / 机械工业出版社 / 2000-6 / 38.00元
本书共分五部分,详细介绍了shell编程技巧,各种UNIX命令及语法,还涉及了UNIX下的文字处理以及少量的系统管理问题。本书内容全面、文字简洁流畅,适合Shell编程人员学习、参考。一起来看看 《LINUX与UNIX Shell编程指南》 这本书的介绍吧!