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

前 言

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

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

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

这篇文章包含以下内容:

介绍CRI接口规范内容

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

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

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

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

01

CRI的由来

CRI(Container Runtime Interface)是Kubernetes提出的容器运行时接口规范。

image

在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)。其具体内容的定义如下图:

image

容器运行时标准

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规范的容器运行时项目:

image

容器运行时对比

按照底层容器运行环境依托的技术分类,我们将容器运行时分为以下三类:

OSContainerRuntime(基于进程隔离技术)

HyperRuntime(基于Hypervisor技术)

UnikernelRuntime(基于unikernel)

通过下图对比它们之间的区别:

image

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的架构如下图所示:

image

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

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

04

CRI接口定义

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

image
image

05

当前CRI格局

目前实现了CRI的主流项目有:docker、containerd、CRI-O、Frakti、pouch,它们衔接Kubelet与运行时方式对比如下:

image

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虚拟机管理程序的容器运行时,它相比其他的容器运行时具有如下这些功能特性:

image

- 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支持多运行时)。

多运行时工作原理

image

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。

- 配置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

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

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

image

因此建议结合自身业务特点、以及使用场景选择合适的容器运行时。在对用户的隔离没有很高诉求的情况下,可以优先考虑使用性能更好更轻量的Containerd + runc;在隔离性要求较高的业务场景下,推荐使用基于Hypervisor 的虚拟化容器运行时Frakti + runv。

转自 https://toutiao.io/posts/eod7r9/preview

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260