前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >容器运行时

容器运行时

作者头像
CNCF
发布2022-11-28 17:28:54
1.4K0
发布2022-11-28 17:28:54
举报
文章被收录于专栏:CNCFCNCFCNCF

要把进程运行在容器中,还需要有便捷的SDK或命令来调用Linux的系统功能,从而创建出容器。容器的运行时(runtime)就是运行和管理容器进程、镜像的工具。

作者:赵慧慧, 中国移动云能力中心软件开发工程师,专注于云原生、Istio、微服务等领域。

01

容器运行时分类

Docker属于容器技术早期的发展项目,也是目前最广泛的容器引擎技术。当然,随着容器生态圈的日益繁荣,业界慢慢也出现了其他各种运行时工具,如containerd、rkt、Kata Container、CRI-O等。这些工具提供的功能不尽相同,有些只有容器运行的功能,有些除运行容器外还提供了容器镜像的管理功能。根据容器运行时提供功能,可以讲容器运行时分为低层运行时高层运行时

低层运行时主要负责与宿主机操作系统打交道,根据指定的容器镜像在宿主机上运行容器的进程,并对容器的整个生命周期进行管理。而这个低层运行时,正是负责执行我们前面讲解过的设置容器 Namespace、Cgroups等基础操作的组件。常见的低层运行时种类有:

Ø runc:传统的运行时,基于Linux Namespace和Cgroups技术实现,代表实现Docker

Ø runv:基于虚拟机管理程序的运行时,通过虚拟化 guest kernel,将容器和主机隔离开来,使得其边界更加清晰,代表实现是Kata Container和Firecracker

Ø runsc:runc + safety ,通过拦截应用程序的所有系统调用,提供安全隔离的轻量级容器运行时沙箱,代表实现是谷歌的gVisor

高层运行时主要负责镜像的管理、转化等工作,为容器的运行做前提准备。主流的高层运行时主要containerd和CRI-O。

高层运行时与低层运行时各司其职,容器运行时一般先由高层运行时将容器镜像下载下来,并解压转换为容器运行需要的操作系统文件,再由低层运行时启动和管理容器。

两者之间的关系如下:

02

Kubernetes容器运行时

前面的两部分,我们介绍了容器运行的原理及常见的容器运行时工具,Kubernetes作为容器编排工具会对容器进行调度和管理。那Kubernetes如何启动容器的?

Kubernetes早期是利用Docker作为容器运行时管理工具的,在1.6版本之前Kubernetes将Docker默认为自己的运行时工具,通过直接调用Docker的API来创建和管理容器。在Docker项目盛行不久,CoreOS推出了rkt运行时工具,Kubernetes又添加了对rkt的支持。但随着容器技术的蓬勃发展,越来越多的运行时工具出现,提供对所有运行时工具的支持,显然是一项庞大的工程;而且直接将运行时的集成内置于Kubernetes,两者紧密结合,对Kubernetes代码本身也是一种负担,每更新一次重要的功能,Kubernetes都需要考虑对所有容器运行时的兼容适配。

为了打破这种尴尬的局面,Kubernetes将对容器的操作抽象为一个接口,将接口作为kubelet与运行时工具之间的桥梁,kubelet通过发送接口请求对容器进行启动和管理,各个容器工具通过实现这个接口即可接入Kubernetes。这个统一的容器操作接口,就是容器运行时接口(Container Runtime Interface, CRI)。

我们来具体看下CRI的工作流程,上图可以看到,CRI主要有gRPC client、gRPC Server和具体的容器运行时工具三个组件。其中kubelet作为gRPC 的客户端来调用 CRI 接口;CRI shim作为gRPC服务端来响应CRI请求,负责将CRI请求的内容转换为具体的容器运行时API,在kubelet和运行时之间充当翻译的角色。具体的容器创建逻辑是,Kubernetes在通过调度指定一个具体的节点运行pod,该节点的Kubelet在接到pod创建请求后,调用一个叫作 GenericRuntime 的通用组件来发起创建 Pod 的 CRI 请求给CRI shim;CRI shim监听一个端口来响应kubelet, 在收到CRI请求后,将其转化为具体的容器运行时指令,并调用相应的容器运行时来创建pod。

因此,任何容器运行时如果想接入Kubernetes,都需要实现一个自己的CRI shim,来实现CRI接口规范。那么CRI有哪些接口需要实现呢?查看Kubernetes代码可以发现,它定义了下图所示两类接口:RuntimeService和ImageService。RuntimeService定义了跟容器相关的操作,如创建、启动、删除容器等。ImageService主要定义了容器镜像相关的操作,如拉取镜像、删除镜像等。

ImageService的操作比较简单,就是拉取、删除、查看镜像状态及获取镜像列表这几个操作。下面着重介绍下RuntimeService。

从上图可以看到,RuntimeService除了有container的管理接口外,还包含PodSandbox相关的管理接口和exec、attach等与容器交互的接口。

顾名思义,PodSandbox这个概念对应的是Kubernetes里的Pod,它描述了Kubernetes里的Pod与容器运行相关的属性或者信息,如HostName、CgroupParent等。设计这个的初衷是因为Pod里所有容器的资源和环境信息是共享的,但是不同的容器运行时实现共享的机制不同,如Docker中Pod会是一个Linux命名空间,各容器网络信息的共享通过创建pause 容器的方法来实现,而Kata Containers则直接将pod具化为一个轻量级的虚拟机。将这个逻辑抽象为PodSandbox接口,可以让不同的容器运行时在pod实现上自由发挥,自己解释和实现pod的的逻辑。

Exec、Attach 和 PortForward 是三个和容器进行数据交互的接口,由于交互数据需要长链接来传输,称这些接口为 Streaming API。CRI shim依赖一套独立的Streaming Server机制来实现客户端与容器的交互需求。长连接比较消耗网络资源,为了避免因长连接给kubelet节点带来网络流量瓶颈,CRI要求容器运行时启动一个对应请求的单独的流服务器,让客户端直接与流服务器进行连同交互。

上图所示,kubectl exec命令实现过程如下:

1. 客户端发送 kubectl exec命令给apiserver;

2. apiserver 调用 kubelet 的 Exec API;

3. kubelet 调用CRI 的Exec接口(具体的执行者为实现该接口的 CRI Shim );

4. CRI Shim 向 kubelet 返回Streaming Server 的地址和端口;

5. kubelet 以 redirect 的方式返回给apiserver

6. apiserver 通过重定向来向 Streaming Server 发起真正的 /exec 请求,与它建立长连接,完成 Exec 的请求和响应。

以上是CRI的设计及工作原理。

概括来讲,kubelet在引入CRI之后,主要的架构如下图所示。其中Generic Runtime Manager负责发送容器创建、删除等CRI请求,Container Runtime Interface(CRI)负责定义CRI接口规范,具体的CRI实现可分为两种:kubelet内置的dockershim和远端的CRI shim。其中dockershim是Kubernetes自己实现的适配Docker接口的CRI接口实现,主要用来将CRI 请求里的内容组装成 Docker API 请求发给 Docker Daemon;远端的CRI shim主要是用来匹配其他的容器运行时工具到kubelet。CRI shim主要负责响应kubelect发送的CRI请求,并将请求转化为具体的运行时命令发送给具体的运行时(如runc、kata等);Stream Server用来响应客户端与容器的交互,除此之外,CRI还提供接入CNI的能力以实现pod网络的共享。常用的远端CRI的实现有CRI-Containerd、CRI-O等。

从上图可以看出,Kubernetes把docker shim内置在了官方的代码库中,将Docker设计为Kubernetes默认的容器运行时工具。但是官方在Kubernetes 1.20版本的更新日志中声明已经废用对Docker的支持,并将在未来的版本中将其删除。在Kubernetes 1.24版本中,dockershim代码也如期被删除,替换为containerd作为其默认运行时。

那Kubernetes为何要抛弃Docker转而使用containerd,其中的缘由是什么?

这话要从头说起,Docker最初是一个单体引擎,主要负责容器镜像的制作、上传、拉取及容器的运行及管理。随着容器技术的繁荣发展,为了促进容器技术相关的规范生成和Docker自身项目的发展,Docker将单体引擎拆分为三部分,分别为runC、containerd和dockerd,其中runC主要负责容器的运行和生命周期的管理(即前述的低层运行时)、containerd主要负责容器镜像的下载和解压等镜像管理功能(即前述的高层运行时)、dockerd主要负责提供镜像制作、上传等功能同时提供容器存储和网络的映射功能,同时也是Docker服务器端的守护进程,用来响应Docker客户端(命令行CLI工具)发来的各种容器、镜像管理的任务。Docker公司将runC捐献给了OCI,将containerd捐献给了CNCF,剩下的dockerd作为Docker运行时由Docker公司自己维护。

如前所述,Kubernetes在引入CRI之后,kubelet需要通过CRI shim去调用具体的容器运行时工具,由于早期Kubernetes对Docker的支持是内置的,因此官方自己实现了dockershim,通过dockershim去访问dockerd。

由于dockershim的维护出现了问题,官方废弃了对Docker的支持,使用containerd为默认运行时。那我们知道,kubelet需要一个CRI shim作为中间件去调用运行时,那kubelet在抛弃了dockershim之后又是怎么访问containerd的呢?答案是containerd自己集成了CRI shim,提供了一个CRI插件来实现shim的功能,这样kubelet就可以直接访问containerd。

由此可以看到,废弃dockershim之前kubelet其实也是使用containerd作为高层运行时,只是中间通过了dockershim和dockerd两步转发;在将dockershim移除之后,kubelet越过docker门户直接访问了containerd,这明显的轻量化了调用过程,大大加快了kubelet调用运行时的速度。

03

安全容器运行时

Kubernetes目前作为企业级容器平台,企业生产最重要的是安全。前面我们讲过,Docker 容器通过Linux Namespace和Cgroups实现容器之间的资源限制和隔离,在实际运行中各容器资源(网络、存储、计算)仍由宿主机直接提供,这就可能出现某个容器进程夺取整个宿主机控制权的问题,在安全问题上存在一定的隐患。于是,Kata Container和gVisor等安全容器运行时应用而生。

Kata Container 来源于 Intel Clear Containers 和 Hyper runV 项目的合并,它使用传统的虚拟化技术,通过虚拟硬件模拟出了一台“小虚拟机”,然后再这台小虚拟机中安装了一个裁剪后的Linux内核来实现容器建的隔离。gVisor由谷歌公司发布,它通过为容器进程配置一个用Go语言实现的、在用户态实现的、极小的“独立内核”,通过这个内核控制容器进程向宿主机发起有限可控的系统调用。

那Kubernetes如何集成这些安全运行时呢?下面以Kata Container为例,介绍安全容器运行时如何集成到Kubernetes中对各种资源进行管控。

Kata Container支持OCI运行时规范,可以以插件形式嵌入到 Docker 中作为低层的容器运行时;也支持 Kubernetes 的 CRI 接口,可以与 CRI-O 和 containerd 集成。由于目前Kubernetes默认的运行时是containerd,下面主要讲解Kata Container如何与containerd集成以及集成后Kubernetes如何使用Kata Container创建负载。

由于Kata Container使用虚拟化技术实现,首先需要集成的Kubernetes环境支持Intel VT-x technology、ARM Hyp mode、IBM Power Systems或IBM Z manframes四种中的任意一种CPU虚拟化技术

集成及使用过程如下:

1、安装Kata Container(以centos为例)


source /etc/os-release
sudo yum -y install yum-utils
export BRANCH='stable-1.10'
ARCH=$(arch)
BRANCH="${BRANCH:-master}"
sudo -E yum-config-manager --add-repo "http://download.opensuse.org/repositories/home:/katacontainers:/releases:/${ARCH}:/${BRANCH}/CentOS_${VERSION_ID}/home:katacontainers:releases:${ARCH}:${BRANCH}.repo"
sudo -E yum -y install kata-runtime kata-proxy kata-shim

安装完成之后,执行命令 kata-runtime kata-check 检查系统是否支持运行 kata runtime,下面的输出表示运行环境支持 Kata Containers 。

1. [root@node1 ~]# kata-runtime kata-check
2.System is capable of running Kata Containers
3.System can currently create Kata Containers

2、修改Kubernetes 启动参数

1)修改 kubelet 启动参数

1. mkdir -p  /etc/systemd/system/kubelet.service.d/
2.cat << EOF | sudo tee  /etc/systemd/system/kubelet.service.d/0-containerd.conf
3.[Service]                                                 
4.Environment="KUBELET_EXTRA_ARGS=--container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
5.EOF

2)重启 containerd 和 kubelet

1. systemctl daemon-reload
2.systemctl start containerd
3.systemctl restart kubelet

3、Kubernetes 配置使用Kata Container

配置了Kata Container之后,我们就可以在Kubernetes集群中使用Kata Container进行容器管理了。如上所述,目前containerd中存在两个低层运行时,分别是默认的runC和新接入的Kata Container。那我们该如何告诉 Kubernetes 哪些负载需要使用 Kata Container呢?根据不同的版本,Kata 提供了不同的方式:

Ø 使用 Kubernetes 的 RuntimeClass(推荐)

Ø 使用 Kubernetes的untrusted_workload_runtime

1)使用RuntimClass

这种方式对相关组件版本有要求:

Kata Containers v1.5.0 or above (including 1.5.0-rc)
Containerd v1.2.0 or above
Kubernetes v1.12.0 or above

a)、修改containerd的配置文件

在/etc/containerd/config.toml配置文件中[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]下增加kata运行时配置:

 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata] #这里最后的这个kata 将被作为 RuntimeClass handler 关键字
  runtime_type = "io.containerd.kata.v2"
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.katacli]
    runtime_type = "io.containerd.runc.v1"
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.katacli.options]
      NoPivotRoot = false
      NoNewKeyring = false
      ShimCgroup = ""
      IoUid = 0
      IoGid = 0
      BinaryName = "/usr/bin/kata-runtime"
      Root = ""
      CriuPath = ""
      SystemdCgroup = false

需要注意的是,[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata] 中的 kata 将被作为 RuntimeClass handler 关键字。

b)、重启 containerd

systemctl daemon-reload
systemctl restart containerd

c)、使用Kata Container

ü 准备RuntimeClass,并用该RuntimeClass创建Pod声明文件

runtime.yaml
apiVersion: node.k8s.io/v1beta1  # RuntimeClass is defined in the node.k8s.io API group
kind: RuntimeClass
metadata:
  name: kata  
handler: kata  # 这里与containerd配置文件中的 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.{handler}] 匹配
pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kata-nginx
spec:
  runtimeClassName: kata
  containers:
    - name: nginx
      image: nginx
      ports:
      - containerPort: 80

ü执行yaml,创建RuntimeClass和Pod

[root@node1 zhh]# kubectl apply -f runtime.yaml
runtimeclass.node.k8s.io/kata created
[root@node1 zhh]# kubectl get runtimeclass
NAME   HANDLER   AGE
kata   kata      7s
[root@node1 zhh]# kubectl apply -f pod.yaml
7pod/kata-nginx created

ü验证是否正确创建:通过kata-runtime list 可以查看创建出来的 container

[root@node1 zhh]# kata-runtime list
ID                                                                 PID         STATUS      BUNDLE                                                                                                                  CREATED                          OWNER
a5abf7227bf3e1868ff590e207d4f755ff6c879d821274a6ae25ae33839f5933   -1          running     /run/containerd/io.containerd.runtime.v2.task/k8s.io/a5abf7227bf3e1868ff590e207d4f755ff6c879d821274a6ae25ae33839f5933   2022-10-12T11:30:44.156957259Z   #0
[root@node1 zhh]# kubectl get pod
NAME                                READY   STATUS              RESTARTS   AGE
kata-nginx                          0/1     ContainerCreating   0          33s
nginx-deployment-746fbb99df-lmjcv   1/1     Running             1          18h

2) 使用untrusted_workload_runtime

对于不符合上述版本要求的环境,可以使用untrusted_workload_runtime的方式,该方式对版本无要求。

a)、修改containerd的配置文件

在/etc/containerd/config.toml配置文件中新增untrusted_workload_runtime运行时配置:

 [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
 runtime_type = "io.containerd.kata.v2"

对于不支持Runtime V2 (Shim API)的早期版本的Kata Containers和containd,可以使用以下替代配置:

[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
  # runtime_type is the runtime type to use in containerd e.g. io.containerd.runtime.v1.linux
  runtime_type = "io.containerd.runtime.v1.linux"
  # runtime_engine is the name of the runtime engine used by containerd.
  runtime_engine = "/usr/bin/kata-runtime"

b)、重启 containerd

systemctl daemon-reload & systemctl restart containerd

c)、使用Kata Container

untrusted_workload_runtime 使用 annotations 告诉 Kubernetes 集群哪些负载需要使用 kata-runtime。因此需要使用kata-runtime的资源,只需要在annotations中声明即可。如:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-untrusted
  annotations:
    io.kubernetes.cri.untrusted-workload: "true"
spec:
  nodeName: k8s3
  containers:
  - name: nginx
    image: nginx

验证方式Pod是否被正确创建方法同RuntimClass。

当然,如果想直接把Kata Container作为Kubernetes默认的运行时也是可以的。我们知道Kubernetes默认的低层运行时是runC,如果想把Kata Container设置为默认的低层运行时,在containerd的配置文件中设置default_runtime,重启containerd(systemctl daemon-reload & systemctl restart containerd)后,创建的负载就自动使用Kata Container来进行管理了。

  [plugins."io.containerd.grpc.v1.cri".containerd]
    [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
      runtime_type = "io.containerd.kata.v2"

对于不支持Runtime V2 (Shim API)的早期版本的Kata Containers和containerd,可以使用以下替代配置:

 [plugins."io.containerd.grpc.v1.cri".containerd]
    [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
      runtime_type = "io.containerd.runtime.v1.linux"
      runtime_engine = "/usr/bin/kata-runtime"

参考文献:

1. https://www.bilibili.com/video/BV15Q4y1B7yk?spm_id_from=333.337.search-card.all.click

2.https://github.com/kata-containers/documentation/blob/master/how-to/containerd-kata.md

3. https://blog.csdn.net/m2l0zgssvc7r69efdtj/article/details/110297314

4. https://zhuanlan.zhihu.com/p/102171749

5. https://blog.csdn.net/u013533380/article/details/115682900

6. https://zhuanlan.zhihu.com/p/438351320

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CNCF 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考文献:
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档