您的位置:首页 > 其它

为Kubernetes选择合适的容器运行时

2019-06-13 11:30 183 查看

 前 言 

作为后台支撑,Kubernetes优势明显,具有自动化部署、服务伸缩、故障自我修复、负载均衡等特性。咪付的蓝牙过闸系统和全态识别AI系统的后台支撑采用了Kubernetes,经过线上的长期运行,其状态良好运行平稳。


蓝牙过闸系统和全态识别AI系统有着不同的数据特性,对数据的安全要求及运行效率也各不一样,因此如何选择容器的运行时成为了一个重点考虑的因素。


本文将介绍Kubernetes支持哪些容器运行时以及如何选择合适的容器运行时。


这篇文章包含以下内容:

介绍CRI接口规范内容

介绍不同OCI项目的技术特点

CRI是如何衔接Kubelet和OCI层及当前CRI格局

Kubernetes如何支持多种容器运行时

如何选择合适的容器运行时01

  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在v1.5版本之前是没有CRI接口的,那时Kubelet源码内部只集成了两个容器运行时(Docker和rkt)的相关代码。这两种容器运行时并不能满足所有用户的使用需求,在某些业务场景,用户对容器的安全隔离性有着更高的需求,用户希望Kubernetes能支持更多种类的容器运行时。因此,Kubernetes在1.5版本推出了CRI接口,各个容器运行时只要实现了CRI接口规范,就可以接入到Kubernetes平台为用户提供容器服务。


CRI接口带来的好处,首先它很好的将Kubernetes和容器运行时解耦,容器运行时的每次更新迭代,都不必对Kubelet工程源码进行编译发布;其次解放了容器运行时更新迭代的步伐,也能保证Kubernetes的代码质量和平台的稳定。02

  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排查方法和工具完全派不上用场。03

  Kubelet CRI架构  

Kubernetes在引入CRI之后,Kubelet的架构如下图所示:

每一个容器引擎只需要自己实现一个CRI shim,对CRI请求进行处理,就可以接入Kubelet当中去。


我们所说的容器运行时,准确来说包含两部分,一部分是上层容器运行时CRI shim(即容器运行时管理程序,如Containerd、CRI-O),另一部分是下层容器运行时Container runtime(即容器运行时命令工具,如runc)。04

  CRI接口定义  

CRI接口分为两部分,一个是容器运行时服务RuntimeService,负责管理pod和容器的生命周期;一个是镜像服务ImageService,负责管理镜像的生命周期。

05

  当前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功能。它的技术优势包括:

1. 强隔离,包括的安全特性:基于Hypervisor的容器技术、lxcfs、目录磁盘配额、补丁Linux内核等。

2. 基于P2P镜像分发,利用P2P技术在各节点间互传镜像,减小镜像仓库的下载压力,加快镜像下载速度。

3. 富容器技术,PouchContainer的容器中除了运行业务应用本身之外,还有运维套件、系统服务、systemd进程管家等。


06

  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工具对其进行操作。07

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指定运行时:

1. 在Kubernetes worker节点配置CRI shim;

2. 创建RuntimeClass资源对象;

3. 在pod中指定RuntimeClass。


▲[p]-  配置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
  # ...
08[/p]

如何选择合适的容器运行时

在生产环境中,我们并不需要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。

往期

精选

《2D与3D人脸识别详解》

《深度学习中,CPU、GPU、NPU、FPGA如何发挥优势?》

《自动驾驶:把方向盘交给汽车还有多远?》

《步态识别技术:识人不看脸,走路就能认出你!》



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: