虚拟机容器Kata架构,让你了解

容器是一种轻量级虚拟化技术,“轻量”主要是因为容器与传统虚拟机比较,是内核共享的,所以启动快、资源占用小。随着虚拟化技术发展,Docker与Kubernets逐步成为应用打包与部署的标准,以及公有云租赁模式推广,出现了“虚拟机容器”这种形态。

Linux 容器模型飞速发展,因为容器轻巧,快速且易于集成到许多不同的应用程序工作流程中。

摘要: 在每个Kubernetes节点的最底层都有一个程序负责具体的容器创建删除工作,Kubernetes会对其接口进行调用,从而完成容器的编排调度。我们将这一层软件称之为容器运行时(Container Runtime),大名鼎鼎的Docker就是其中的代表。

虚拟机容器首先是一台虚拟机,由于Docker镜像打包与分发方面的优势,虚拟机容器也兼容Docker镜像;并提供了OCI规范的 runtime ,且适配了Kubernetes调度管理API,能够被Kubernetes调度,因此被称为虚拟机容器。

任何有关 Linux 容器的讨论总是会涉及容器技术和虚拟化之间的差异。现在,由OpenStack Foundation 管理的开源 Kata Containers 项目正在利用这些差异来使Linux 容器的实现更加安全。

在每个Kubernetes节点的最底层都有一个程序负责具体的容器创建删除工作,Kubernetes会对其接口进行调用,从而完成容器的编排调度。我们将这一层软件称之为容器运行时(Container Runtime),大名鼎鼎的Docker就是其中的代表。

虚拟机容器主要使用场景是在公有云模式下,云服务厂商提供无服务器计算服务,这种模式下租户只租一个容器或进程,不同租户的容器/进程可能运行到同一台机器上,传统的内核共享模式在这种场景下安全风险太高,虚拟机容器能够做到内核独占,适合这种场景。

Kata Containers  开源项目和社区的目标是构建轻量级虚拟机 ( VM ) 的标准实现,这些虚拟机可以像容器一样被感受和执行,但是可以提供虚拟机的工作负载隔离和安全优势。这意味着,Kata 可能会缓解一些关于容器的担忧,特别是在大规模应用时。以下是关于 Kata Containers 你需要了解的 10 件事。

当然,容器运行时并非只有Docker一种,包括CoreOS的rkt,hyper.sh的runV,Google的gvisor,以及本文的主角PouchContainer,都包含了完整的容器操作,能够用来创建特性各异的容器。不同的容器运行时有着各自独特的优点,能够满足不同用户的需求,因此Kubernetes支持多种容器运行时势在必行。

Kata 项目介绍

1.为什么有Kata?

最初,Kubernetes原生内置了对Docker的调用接口,之后社区又在Kubernetes 1.3中集成了rkt的接口,使其成为了Docker以外,另一个可选的容器运行时。不过,此时不论是对于Docker还是对于rkt的调用都是和Kubernetes的核心代码强耦合的,这无疑会带来如下两方面的问题:

Kata 源自 Intel 的开源项目 Hyper.sh ,当前在 Openstack 软件基金会下治理。

Linux 容器轻巧,快速且易于集成到许多不同的应用程序工作流程中。

1、新兴的容器运行时,例如PouchContainer这样的后起之秀,加入Kubernetes生态难度颇大。容器运行时的开发者必须对于Kubernetes的代码(至少是Kubelet)有着非常深入的理解,才能顺利完成两者之间的对接。

Kata南向虚拟化实现技术可插拔,当前支持QEMU/NEMU,在最新的1.5版本里支持Firecrack;北向支持与Docker的Containerd对接,并可以被Kubernetes编排,接入Docker与Kubernetes生态。

但是,在运行容器时存在一些潜在的安全问题,特别是在单个操作系统中的多租户容器:最终,容器共享一个内核、 I / O 的一条路径、网络和内存等。

2、Kubernetes的代码将更加难以维护,这也体现在两方面:将各种容器运行时的调用接口全部硬编码进Kubernetes,会让Kubernetes的核心代码变得臃肿不堪,容器运行时接口细微的改动都会引发Kubernetes核心代码的修改,增加Kubernetes的不稳定性

Kata 架构概览 (基于1.5版本)Kata container runtime and shimv2

因此,一个容器的妥协就是潜在的许多容器的妥协。

为了解决这些问题,社区在Kubernetes 1.5引入了CRI(Container Runtime Interface),通过定义一组容器运行时的公共接口将Kubernetes对于各种容器运行时的调用接口屏蔽至核心代码以外,Kubernetes核心代码只对该抽象接口层进行调用。而对于各种容器运行时,只要满足了CRI中定义的各个接口就能顺利接入Kubernetes,成为其中的一个容器运行时选项。方案虽然简单,但是对于Kubernetes社区维护者和容器运行时开发者来说,都是一种解放。

Kata容器项目主要由容器运行时与一个兼容CRI接口的 shim 部件组成。

Kata旨在通过虚拟机管理程序来缓解这种安全问题——创建一个外观和感觉像容器的虚拟机。

图片 1

Kata container runtime 符合 OCI 运行时规范 因此能够被Docker引擎管理,作为Docker引擎的一个runtime插件。 Kata container runtime 还基于Containerd的CRI插件与CRI-O实现了Kubernetes的CRI规范,因此,使用者可以在Docker默认的runtime runc 与 kata container runtime (runv) 之间平滑切换,上层组件不感知差异。

2.Kata是如何管理的

如上图所示,左边的Kubelet是Kubernetes集群的Node Agent,它会对本节点上容器的状态进行监控,保证它们都按照预期状态运行。为了实现这一目标,Kubelet会不断调用相关的CRI接口来对容器进行同步。

Kata容器的另外一个组成部分是 containerd-shim-kata-v2 ,简称为 shimv2 , shimv2 提供了了 Containerd Runtime V2 (Shim API) 的 Kata 实现,从而使得 Kubernetes 场景下能够实现每个 Pod 一个 shim 进程 – shimv2 ;而在此之前,一个Pod需要一个2个shim,如果 Pod Sandbox没有暴露 VSOCK 则还需要一个 kata-proxy 。

Kata Containers项目由 OpenStack Foundation 管理。OpenStack 宣布打算在开源运动中发挥更广泛的作用,而 Kata 是其第一次进军。Kata Containers不是“ OpenStack 项目”的一部分,而是一个拥有自己的技术管理和贡献者基础的独立项目。除了OpenStack 驱动的云之外,Kata Containers 路线图还支持多种流行的基础设施提供商和容器编排框架,而 Kata Architecture 委员会也有来自谷歌和微软等公司的代表。

CRI shim则可以认为是一个接口转换层,它会将CRI接口,转换成对应底层容器运行时的接口,并调用执行,返回结果。对于有的容器运行时,CRI shim是作为一个独立的进程存在的,例如当选用Docker为Kubernetes的容器运行时,Kubelet初始化时,会附带启动一个Docker shim进程,它就是Docker的CRI shime。而对于PouchContainer,它的CRI shim则是内嵌在Pouchd中的,我们将其称之为CRI manager。关于这一点,我们会在下一节讨论PouchContainer相关架构时再详细叙述。

agent 与 kata-proxy

3.Kata的起源是什么?

CRI本质上是一套gRPC接口,Kubelet内置了一个gRPC Client,CRI shim中则内置了一个gRPC Server。Kubelet每一次对CRI接口的调用,都将转换为gRPC请求由gRPC Client发送给CRI shim中的gRPC Server。Server调用底层的容器运行时对请求进行处理并返回结果,由此完成一次CRI接口调用。

Kata容器运行在虚拟机沙箱内,每个虚拟机内运行一个Agent,Agent负责运行container。Agent同时提供gRPC接口,通过QEMU的VIRTIO serial或VSOCK接口向HOST主机暴露接口,主机上的 kata-runtime 使用gRPC协议与Agent通信,向虚拟机内的容器发送指令,I/O流也通过此通道管理。 如果是使用 VIRTIO serial 的方式暴露接口到Host主机,那么还需要在主机上部署一个 kata-proxy 负责转发指令到Agent。

Kata基于 Intel Clear Containers 和  Hyper runV  技术。

CRI定义的gRPC接口可划分两类,ImageService和RuntimeService:其中ImageService负责管理容器的镜像,而RuntimeService则负责对容器生命周期进行管理以及与容器进行交互(exec/attach/port-forward)。

容器进程管理

英特尔和 Hyper 都在运行自己的容器项目,拥有不同的优势:英特尔专注于性能,而 Hyper 则与平台无关。英特尔和 Hyper 的独立项目并行解决了容器安全问题,但两者并非完全隔离,其结果有些类似。两者都解决了容器化环境中的关键问题:环境的分层,例如 OpenStack 上的 Kubernetes 或Kubernetes 上的 OpenStack ,甚至 Kubernetes  上的 OpenStack 上的 Kubernetes 。

图片 2

在Host主机上,每个容器进程的清理是由更上层的进程管理器完成的,在Docker containerd的实现里,进程管理器是 containerd-shim ;在CRI-O的实现里是common。

容器编排是堆栈中的一个层,Kata 在维护安全性的同时简化了这些层的集成。

在PouchContainer的整个架构体系中,CRI Manager实现了CRI定义的全部接口,担任了PouchContainer中CRI shim的角色。当Kubelet调用一个CRI接口时,请求就会通过Kubelet的gRPC Client发送到上图的gRPC Server中。Server会对请求进行解析,并调用CRI Manager相应的方法进行处理。

在Kata容器场景下,容器进程运行在虚拟机内,Host主机上的进程管理器不能直接管理到容器进程,Kata容器项目通过 kata-shim 来解决此问题。kata-shim 运行在Host主机上,介于容器进程管理器与kata-proxy之间,kata-shim 将来自Host主机的信号量、stdin 转发到虚拟机内的容器进程上,并将虚拟机容器内的stdout与stderr转发到Host主机上的容器进程管理器。

4.Kata是如何获得许可的

我们先通过一个例子来简单了解一下各个模块的功能。例如,当到达的请求为创建一个Pod,那么CRI Manager会先将获取到的CRI格式的配置转换成符合PouchContainer接口要求的格式,调用Image Manager拉取所需的镜像,再调用Container Manager创建所需的容器,并调用CNI Manager,利用CNI插件对Pod的网络进行配置。最后,Stream Server会对交互类型的CRI请求,例如exec/attach/portforward进行处理。

kata-runtime 为每个容器进程创建一个 kata-shim 守护进程,为每个通过OCI命令连接到容器进程内部执行用户命令的操作创建一个 kata-shim 守护进程。

Kata Containers 根据 Apache 2 许可在 Github 上托管。欲了解更多信息并参与其中,请访问www.katacontainers.io。

值得注意的是,CNI Manager和Stream Server是CRI Manager的子模块,而CRI Manager,Container Manager以及Image Manager是三个平等的模块,它们都位于同一个二进制文件Pouchd中,因此它们之间的调用都是最为直接的函数调用,并不存在例如Docker shim与Docker交互时,所需要的远程调用开销。下面,我们将进入CRI Manager内部,对其中重要功能的实现做更为深入的理解。

在 Kata1.5 版本,shimv2 收编 kata-runtime, kata-shim, kata-proxy 到 shimv2 进程中。

  1. Kata架构如何设计

在Kubernetes的世界里,Pod是最小的调度部署单元。简单地说,一个Pod就是由一些关联较为紧密的容器构成的容器组。作为一个整体,这些“亲密”的容器之间会共享一些东西,从而让它们之间的交互更为高效。例如,对于网络,同一个Pod中的容器会共享同一个IP地址和端口空间,从而使它们能直接通过localhost互相访问。对于存储,Pod中定义的volume会挂载到其中的每个容器中,从而让每个容器都能对其进行访问。

虚拟化

Kata Containers 项目最初将包含六个组件:Agent、Runtime、Proxy、Shim、Kernel和QEMU 2.9 打包。

事实上,只要一组容器之间共享某些Linux Namespace以及挂载相同的volume就能实现上述的所有特性。下面,我们就通过创建一个具体的Pod来分析PouchContainer中的CRI Manager是如何实现Pod模型的:

Kata 架构上能够支持多种虚拟化实现,在 Kata1.0 版本,支持 QEMU/KVM 虚拟化。在 Kata1.5 版本,支持 AWS Firecracker 极轻量的虚拟机。

它被设计成与架构无关,运行在多个虚拟机管理程序上,并与 Docker 容器的 OCI 规范和 Kubernetes 的  CRI  兼容。

当Kubelet需要新建一个Pod时,首先会对RunPodSandbox这一CRI接口进行调用,而CRI Manager对该接口的实现是创建一个我们称之为"infra container"的特殊容器。从容器实现的角度来看,它并不特殊,无非是调用Container Manager,创建一个镜像为pause-amd64:3.0的普通容器。但是从整个Pod容器组的角度来看,它是有着特殊作用的,正是它将自己的Linux Namespace贡献出来,作为上文所说的各容器共享的Linux Namespace,将容器组中的所有容器联结到一起。它更像是一个载体,承载了Pod中所有其他的容器,为它们的运行提供基础设施。而一般我们也用infra container代表一个Pod。

QEMU/KVM

6.Kata运行在什么操作系统上?

在infra container创建完成之后,Kubelet会对Pod容器组中的其他容器进行创建。每创建一个容器就是连续调用CreateContainer和StartContainer这两个CRI接口。对于CreateContainer,CRI Manager仅仅只是将CRI格式的容器配置转换为PouchContainer格式的容器配置,再将其传递给Container Manager,由其完成具体的容器创建工作。这里我们唯一需要关心的问题是,该容器如何加入上文中提到的infra container的Linux Namespace。其实真正的实现非常简单,在Container Manager的容器配置参数中有PidMode, IpcMode以及NetworkMode三个参数,分别用于配置容器的Pid Namespace,Ipc Namespace和Network Namespace。笼统地说,对于容器的Namespace的配置一般都有两种模式:"None"模式,即创建该容器自己独有的Namespace,另一种即为"Container"模式,即加入另一个容器的Namespace。显然,我们只需要将上述三个参数配置为"Container"模式,加入infra container的Namespace即可。具体是如何加入的,CRI Manager并不需要关心。对于StartContainer,CRI Manager仅仅只是做了一层转发,从请求中获取容器ID并调用Container Manager的Start接口启动容器。

根据Host主机架构,Kata容器支持多种主机类型,比如 x86 上的 pcq35 ,ARM 上的 virt , IBM Power System 上的 pseries 。默认的Kata容器主机类型是 pc ,默认主机类型可以通过配置修改。

目前,Kata Containers 仅限于Linux。

最后,Kubelet会不断调用ListPodSandbox和ListContainers这两个CRI接口来获取本节点上容器的运行状态。其中ListPodSandbox罗列的其实就是各个infra container的状态,而ListContainer罗列的是除了infra container以外其他容器的状态。现在问题是,对于Container Manager来说,infra container和其他container并不存在任何区别。那么CRI Manager是如何对这些容器进行区分的呢?事实上,CRI Manager在创建容器时,会在已有容器配置的基础之上,额外增加一个label,标志该容器的类型。从而在实现ListPodSandbox和ListContainers接口的时候,以该label的值作为条件,就能对不同类型的容器进行过滤。

Kata容器使用下面的 QEMU 特性来管理资源配额、缩短启动时间、减少内存占用:

在主机端,有几种流行的发行版的安装说明。

综上,对于Pod的创建,我们可以概述为先创建infra container,再创建pod中的其他容器,并让它们加入infra container的Linux Namespace。

机器加速器热插拔设备机器加速器

此外,通过 OSBuilder 还支持 Clear Linux、Fedora 和 CentOS 7 rootfs  镜像(还可用于展示自己的guest 镜像)。

因为Pod中所有的容器都是共享Network Namespace的,因此我们只需要在创建infra container的时候,对它的Network Namespace进行配置即可。

机器加速器是与特定服务器架构相关的,机器加速器能够提升性能并开启某些特性。下面这些机器加速器在Kata容器中使用。

7.Kata如何与现有的容器平台整合

在Kubernetes生态体系中容器的网络功能都是由CNI实现的。和CRI类似,CNI也是一套标准接口,各种网络方案只要实现了该接口就能无缝接入Kubernetes。CRI Manager中的CNI Manager就是对CNI的简单封装。它在初始化的过程中会加载目录/etc/cni/net.d下的配置文件,如下所示:

NVDIMM: x86平台的机器加速器,仅支持 pcq35 机器类型。nvdimm 用来以持久化内存方式提供虚拟机的根文件系统。

通过将两个高度集成的虚拟化容器开源代码库结合起来,并将项目转为开放式治理,Kata Containers社区支持多种架构,并推动跨多个基础设施和容器编排社区的技术采用,包括 Kubernetes、Docker、OCI、CRI、CNI、 QEMU、KVM 和 OpenStack 。例如, OCI 合规性使得 Docker 镜像无需重新加工即可被拿来使用。此外,由于 Kubernetes 已经在公有云上运行并编排容器,Kata 已经与 Kubernetes 取得了进展,并与其他新技术相结合。

图片 3

虽然Kata容器能够支持大多数QEMU发行版本,但是考虑到Kata容器的启动时间、内存占用、IO性能因素,Kata容器使用一个针对这些因素专门优化过的QEMU版本 qemu-lite ,并增加了一些自定义的机器加速器,这些自定义加速器在 QEMU Upstream 版本中不可用。

8.典型用例

其中指定了配置Pod网络会使用到的CNI插件,例如上文中的bridge,以及一些网络配置信息,例如本节点Pod所属的子网范围和路由配置。

nofw: x86平台的机器加速器,仅支持 pcq35 机器类型。 nofw 用来启动 ELF 格式的系统内核,但是可以跳过 BIOS 与固件自检 (BIOS/firmware) ,这个加速器可以显著提升虚拟机启动速度。static-prt: x86平台的机器加速器,仅支持 pcq35 机器类型。 static-prt 用来减少虚拟机ACPI(Advanced Configuration and Power Management Interface)的解释负担。热插拔设备

相比在单个内核中并行运行容器,Kata 在高安全性的容器堆栈环境中更高效。这包括持续集成/持续交付、网络功能虚拟化、边缘计算、开发和测试以及容器即服务。

下面我们就通过具体的步骤来展示如何将一个Pod加入CNI网络:

Kata容器虚拟机初始以最小的资源启动,为了提升启动速度,在启动过程中,设备可以热插到虚拟机上。如,容器定义了cpu资源,可以通过热插的方式加到虚拟机上。Kata容器虚拟机支持如下热插设备:

此外,Kata体积小、安全性高,因此非常适合资源有限的边缘部署。Kata Containers 仍处于起步阶段,但该项目的技术基础,Clear Containers和runV,已经在全球范围内被企业规模化使用,例如京东。

当调用container manager创建infra container时,将NetworkMode设置为"None"模式,表示创建一个该infra container独有的Network Namespace且不做任何配置。

Virtio blockVirtio SCSIVFIOCPUKernel 与 Image虚拟机内核

9.谁在支持Kata

根据infra container对应的PID,获取其对应的Network Namespace路径/proc/{pid}/ns/net。

虚拟机内核在虚拟机启动时候被加载,Kata容器提供的虚拟机内核针对启动时间与内存占用做了优化。

在发布时,Kata 得到了包括中国移动、CoreOS 、戴尔/ EMC、EasyStack、烽火、谷歌、华为、京东、Mirantis、NetApp、红帽、SUSE、腾讯和中兴等20多家公司的支持。

调用CNI Manager的SetUpPodNetwork方法,核心参数为步骤二中获取的Network Namespace路径。该方法做的工作就是调用CNI Manager初始化时指定的CNI插件,例如上文中的bridge,对参数中指定的Network Namespace进行配置,包括创建各种网络设备,进行各种网络配置,将该Network Namespace加入插件对应的CNI网络中。

虚拟机镜像

10.“Kata”是什么意思?

对于大多数Pod,网络配置都是按照上述步骤操作的,大部分的工作将由CNI以及对应的CNI插件替我们完成。但是对于一些特殊的Pod,它们会将自己的网络模式设置为"Host",即和宿主机共享Network Namespace。这时,我们只需要在调用Container Manager创建infra container时,将NetworkMode设置为"Host",并且跳过CNI Manager的配置即可。

Kata 容器支持 initrdroot filesystem 两种虚拟机镜像。

Kata 这个词来自希腊文, Καταπίστευμα (“ka-ta-PI-stev-ma”),意思是“信任某人”。Kata Containers 正在努力获得希望启动或扩展其 Linux 容器使用的组织的信任。

对于Pod中其他的容器,不论Pod是处于"Host"网络模式,还是拥有独立的Network Namespace,都只需要在调用Container Manager创建容器时,将NetworkMode配置为"Container"模式,加入infra container所在的Network Namespace即可。

root filesystem

Kubernetes提供了例如kubectl exec/attach/port-forward这样的功能来实现用户和某个具体的Pod或者容器的直接交互。如下所示:

Kata 容器提供的默认打包好的 root filesystem 镜像,这种镜像也被称为 “mini O/S”,是基于 Clear Linux 优化的,提供最小的运行环境与高度优化的启动路径。 镜像中只有Kata Agent与systemd两个进程,用户的工作负载被打包到docker镜像,在虚拟机内通过libcontainer库,以runc方式运行起来。

图片 4

举例,当用户执行 docker run -it ubuntu date 命令时,流程如下:

可以看到,exec一个Pod等效于ssh登录到该容器中。下面,我们根据kubectl exec的执行流来分析Kubernetes中对于IO请求的处理,以及CRI Manager在其中扮演的角色。

虚拟化层加载虚拟机内核,虚拟机内核加载虚拟机镜像。systemd 启动虚拟机运行环境,并启动 kata-agent 进程kata-agent 创建一个独立的context,运行用户指定的命令kata-agent 准备 ubuntu 的运行环境并运行 date 命令initrd

图片 5

待补充。

如上图所示,执行一条kubectl exec命令的步骤如下:

Agent

1、kubectl exec命令的本质其实是对Kubernetes集群中某个容器执行exec命令,并将由此产生的IO流转发到用户的手中。所以请求将首先层层转发到达该容器所在节点的Kubelet,Kubelet再根据配置调用CRI中的Exec接口。请求的配置参数如下:

kata-agent 是一个运行在虚拟机中的进程管理虚拟机中的容器进程。

图片 6

kata-agent 的最小运行单元是沙箱,一个 kata-agent 沙箱是一个由一些列namespace(NS, UTS, IPC, PID)隔离出来的。 kata-runtime 能够在一个虚拟机内运行多个容器进程以支持POD内多个container模式。

2、令人感到意外的是,CRI Manager的Exec方法并没有直接调用Container Manager,对目标容器执行exec命令,而是转而调用了其内置的Stream Server的GetExec方法。

kata-agent 使用gRPC协议与Kata其他组件通信,在gRPC同一个URL上还运行了一个 yamux 服务。

3、Stream Server的GetExec方法所做的工作是将该exec请求的内容保存到了上图所示的Request Cache中,并返回一个token,利用该token,我们可以重新从Request Cache中找回对应的exec请求。最后,将这个token写入一个URL中,并作为执行结果层层返回到ApiServer。

kata-agent 使用 libcontainer 管理容器生命周期,复用了 runc 的大部分代码。

4、ApiServer利用返回的URL直接对目标容器所在节点的Stream Server发起一个http请求,请求的头部包含了"Upgrade"字段,要求将http协议升级为websocket或者SPDY这样的streaming protocol,用于支持多条IO流的处理,本文我们以SPDY为例。

Runtime

5、Stream Server对ApiServer发送的请求进行处理,首先根据URL中的token,从Request Cache中获取之前保存的exec请求配置。之后,回复该http请求,同意将协议升级为SPDY,并根据exec请求的配置等待ApiServer创建指定数量的stream,分别对应标准输入Stdin,标准输出Stdout,标准错误输出Stderr。

kata-runtime 是一个符合OCI规范的容器运行时,负责处理OCI运行时规范中的所有命令,并启动 kata-shim 进程。

6、待Stream Server获取指定数量的Stream之后,依次调用Container Manager的CreateExec和startExec方法,对目标容器执行exec操作并将IO流转发至对应的各个stream中。

关键的OCI命令实现create

7、最后,ApiServer将各个stream的数据转发至用户,开启用户与目标容器的IO交互。

kata-runtime 处理 OCI create 命令步骤:

事实上,在引入CRI之前,Kubernetes对于IO的处理方式和我们的预期是一致的,Kubelet会直接对目标容器执行exec命令,并将IO流转发回ApiServer。但是这样会让Kubelet承载过大的压力,所有的IO流都需要经过它的转发,这显然是不必要的。因此上述的处理虽然初看较为复杂,但是有效地缓解了Kubelet的压力,并且也让IO的处理更为高效。

创建虚拟机与shim进程的network namespace。执行 pre-start hook ,回调中负责创建 veth 网络设备,用于连接主机网络与新创建的network namespace。扫描新创建的network namespace,在其中的veth设备上创建一个macvtab设备。在新的network namespace中创建虚拟机,并将tab设备传递给虚拟机。等待虚拟机启动完成。启动kata-proxy,kata-proxy负责代理所有发送给虚拟机的请求,每个虚拟机一个kata-proxy进程。调用kata-agent接口配置虚拟机内的沙箱。调用kata-agent接口创建容器,使用kata-runtime提供的默认的OCI配置文件 config.json 。启动kata-shim进程,kata-shim连接到kata-agent的gRPC socket端口。kata-shim会创建几个Go routine阻塞式调用 ReadStdout(), ReadStderr(), WaitProcess()ReadStdout(), ReadStderr() 以死循环方式执行直到虚拟机内的容器进程中止。 WaitProcess() 返回虚拟机内容器进程的 exit code。 kata-shim运行在虚拟机的network namespace中,通过kata-shim进程可以找到创建了哪些namespace。启动 kata-shim 进程还会创建一个新的PID namespace,对应到同一个container的所有kata-shim进程都在同一个PID namespace,这样当容器进程终止时候很容器将所有kata-shim进程终止掉。

本文从引入CRI的缘由而起,简要描述了CRI的架构,重点叙述了PouchContainer对CRI各个核心功能模块的实现。CRI的存在让PouchContainer容器加入Kubernetes生态变得更为简单快捷。而我们也相信,PouchContainer独有的特性必定会让Kubernetes生态变得更加丰富多彩。

此时容器进程在虚拟机内部运行起来了,在Host主机上对应到kata-shim进程。

PouchContainer CRI的设计与实现,是阿里巴巴-浙江大学前沿技术联合研究中心的联合研究项目,旨在帮助PouchContainer 作为一种成熟的容器运行时(container runtime),积极在生态层面拥抱 CNCF。浙江大学 SEL 实验室的卓越技术力量,有效帮助 Pouch 完成 CRI 层面的空白,未来预计在阿里巴巴以及其他使用PouchContainer的数据中心中,创造不可估量的价值。

start

本文作者:allencloud

传统容器的 start 会在容器namespace中启动容器进程。Kata容器中, start 会在虚拟机内启动容器的工作负载,步骤如下;

阅读原文

调用kata-agent接口在虚拟机内启动容器负载命令。如,容器内负载命令为 top ,kata-shim 进程的 ReadStdout() 会读取到 top 的输出, WaitProcess() 会一直等待到 top 命令结束。执行 post-start 回调,当前 post-start 实现为空。exec

本文为云栖社区原创内容,未经允许不得转载。

OCI 的 exec 命令允许在已有的容器中执行命令。在Kata容器中, exec 执行步骤如下:

调用kata-agent接口在已有容器中执行命令。一个新的kata-shim进程会创建出来,被放置到已有容器对应的kata-shim所在的PID namespace中。

此时通过 exec 命令启动的新的容器负载已经在虚拟机内运行,共享已有容器的namespace (uts, pid, mnt, ipc) 。

kill

OCI kill 命令通过发送 UNIX 信号,如 SIGTERM, SIGKILL ,来终止容器进程。在Kata容器中, kill 命令会终止虚拟机内的容器进程与虚拟机。

调用kata-agent接口请求kill容器进程。等待 kata-shim 进程退出。调用kata-agent接口请求强制kill容器进程,如果 kata-shim 进程在超时时间内未退出。等待 kata-shim 进程退出,如果等待超时则报错。调用kata-agent接口删除虚拟机内的容器配置。调用kata-agent接口删除虚拟机内的沙箱配置。停止虚拟机。删除network namespace中的网络配置,删除network namespace。执行 post-stop 回调。delete

delete 指令删除容器所有相关的资源,正在运行中的容器不能不删除,除非通过 --force 指令强制删除。

如果虚拟机内的沙箱未停止,但是沙箱内的容器进程已经推出,kata-runtime会先执行一次kill流程,之后如果沙箱已经停止,kata-runtime执行如下动作:

删除容器相关资源:目录 /var/{lib,run}/virtcontainers/sandboxes/sandboxID/containerID 下的所有文件。删除沙箱:目录 /var/{lib,run}/virtcontainers/sandboxes/sandboxID 下的所有文件。

此时,所有容器相关内容都已经在Host主机上被删除,没有任何相关进程在运行。

state

state 返回容器的运行状态。在Kata容器中,state 需要检测容器进程是否在运行,通过检查对应容器进程的 kata-shim 进程的状态。

通过存储在磁盘上的信息获得容器状态。检查kata-shim进程。如果kata-shim进程不存在,但是磁盘上的容器状态文件还是ready或running,那么意味着在得到容器返回状态之前,容器进程已经被正常停止了。Proxy

Host主机与虚拟机通信可以通过 virtio-serialvirtio-socketvirtio-socket 需要内核版本 4.8 以上。默认使用 virtio-serial

虚拟机内可能运行多个容器进程,在使用 virtio-serial 场景下,需要Host主机上运行 multiplexed 与 demultiplexed 进程;使用 virtio-socket 则不需要。

kata-proxy 进程提供代理访问虚拟机内的 kata-agent ,对应到多个 kata-shim 进程与 kata-runtime 客户端。 kata-proxy主要功能是负责代理IO流与信号量到 kata-agentkata-proxy 通过Unix domain socket方式连接 kata-agent

Shim

容器进程回收器,如Docker的 containerd-shim 或 CRI-O 的 common ,其设计假设是基于能够监控并回收实际的容器进程。在Kata容器中,由于容器进程运行在虚拟机中,Host主机上的容器进程回收器不能直接监控到虚拟机内的容器进程,至多能看到QEMU进程,这个是远远不够的。Kata容器中的kata-shim进程是主机上对应到虚拟机内容器进程的映射,因此kata-shim进程需要处理容器进程的I/O流并负责转发信号到容器进程。

kata-shim 通过Unix domain socket连接到kata-proxy,socket URL是由kata-runtime在启动kata-shim进程时候传递给kata-shim的,一并传递的参数还有containerID与execID,containerID与execID用来标识虚拟机内的容器进程。转发来自容器进程回收器的标准输入流,通过kata-proxy的gRPC的 WriteStdin API。从容器进程读取标准输出与错误输出。转发来自容器进程回收器的信号,通过kata-proxy的 SignalProcessRequest API。监控终端的变化并转发,通过kata-proxy的 TtyWinResize API。Networking

容器进程一般被放置在独立的network namespace中,在容器生命周期的某个点,容器引擎会创建network namespace并将容器进程加入到network namespace中。容器进程网络与主机Host网络隔离。 在实现技术上,通常使用veth技术,veth的两端分别放置到容器的network namespace与主机Host的network namespace中。这种方式是以namespace为中心的,而一些虚拟化技场景下不支持veth,特别是QEMU,这种情况下使用TAP技术替代。

为了消除虚拟化场景下网络隔离与容器场景下网络隔离的不兼容,kata-runtime使用veth+tab(MACVLAN)方式实现网络隔离与互通。

主机Host网络与容器网络通过network namespace隔离,使用veth互通。容器网络network namespace内的veth网络设备上创建MACVLAN设备。在创建虚拟机时候,将TAP网络设备作为虚拟机网卡。虚拟机内的容器进程使用虚拟机网卡作为主网卡与外部通信,虚拟机内部的多个POD共享虚拟机网络,不再隔离。

Kata容器支持 CNM 与 CNI 两种容器网络标准。

CNMCNM lifecycleRequestPoolCreateNetworkRequestAddressCreateEndPointCreateContainerCreate config.jsonCreate PID and network namespaceProcessExternalKeyJoinEndPointLaunchContainerLaunchRun containerCNM 网络配置过程读取 config.json创建network namespace: netns回调 prestart 钩子扫描netns命名空间下的,由prestart回调创建的网络接口创建bridge/tap并通过veth与主机连接网络热插拔

Kata容器开发了一套命令与api支持添加/删除/查看 guest网络。下面流程图展示了Kata容器热插拔的流程。

存储

Kata容器的虚拟机与Host通过9pfs共享文件,对于虚拟机内的容器,不建议使用主流的overlay2存储驱动,overlay2存储驱动是基于文件系统的。

Kubernetes 集成

Kubernetes目前是容器编排的事实标准,Kubernetes为了解耦Kubelet与各种容器runtime,抽象了CRI(Container Runtime Interface)接口规范,Kubelet相当于是一个CRI客户端,不同的CRI实现提供gRPC接口与Kubelet对接,接入到Kubernetes生态。当前基于OCI标准容器提供的CRI接口实现有CRI-O与Containerd CRI Plugin。

Kata容器的runtime是CRI-O与Containerd CRI Plugin的官方runtime之一,因此Kata容器可以很容易的集成到Kubernetes生态中。

但是由于Kubernetes最小调度单元是Pod而非容器进程,一个Pod可以有多个容器进程,而Kata容器又是将容器进程跑在虚拟机内的,因此Kata容器需要Kubernetes在创建容器进程传递更多信息,用来告知Kata runtime是要创建一个新的虚拟机,还是在已有虚拟机内启动容器进程。

Containerd CRI Plugin 集成kata-runtime

在Kata1.5版本,对应containerd1.2.0,通过shimv2实现了Kata runtime与Kubernetes集成,具体指导参考链接。CRI-O的实现也正在开发中,跟踪此Issue。

CRI-O 集成Kata-runtimeOCI annotations

为了让kata-runtime区分是要创建一个虚拟机还是仅在虚拟机内创建容器进程,CRI-O在OCI配置文件中增加了一个annotation来告知这个区分给kata-runtime。

在执行runtime之前,CRI-O会增加一个io.kubernetes.cri-o.ContainerType的annotation,这个注解由Kubelet生成,取值范围是sandbox, container,kata-runtime将sandbox对应到创建虚拟机,container对应到在已有Pod中创建容器进程。

containerType, err := ociSpec.ContainerType()if err != nil { return err}handleFactory(ctx, runtimeConfig)disableOutput := noNeedForOutput(detach, ociSpec.Process.Terminal)var process vc.Processswitch containerType {case vc.PodSandbox: process, err = createSandbox(ctx, ociSpec, runtimeConfig, containerID, bundlePath, console, disableOutput, systemdCgroup) if err != nil { return err }case vc.PodContainer: process, err = createContainer(ctx, ociSpec, containerID, bundlePath, console, disableOutput) if err != nil { return err }}

虚拟机容器与namespace隔离容器混合管理

一个有趣的演进是在一个Kubernetes集群中混合管理虚拟机容器与namespace隔离的容器。 现在Kubernetes集群运维人员可以对工作负载打trusted, untrusted标签,trusted标签表示工作负载是安全的,untrusted表示工作负载存在潜在风险,在支持kata容器的Kubernetes集群中,会自动根据标签,将trusted工作负载以runc方式运行,untrusted工作负载以runv方式运行。

CRI-O默认行为是认为所有的工作负载在都是trusted,除非设置了注解io.kubernetes.cri-o.TrustedSandbox=false,CRI-O默认的trust配置在configuration.toml中。

综合来看,CRI-O是选择runc还是runv,由Pod的Privileged参数,CRI-O trust配置trusted/untrustedio.kubernetes.cri-o.TrustedSandbox注解三个值确定。 如果Pod是Privileged,那么只能是runc。如果Pod不是Privileged,那么runtime的选择方式如下:

io.kubernetes.cri-o.TrustedSandbox 未设置

io.kubernetes.cri-o.TrustedSandbox=true

io.kubernetes.cri-o.TrustedSandbox=false

默认的CRI-O turst设置: trustedruncruncrunv(kata-runtime)默认的CRI-O turst设置: untrustedrunv(kata-runtime)runv(kata-runtime)runv(kata-runtime)原文链接

虚拟机容器Kata架构

关注公众号获得更多云最佳实践

本文由澳门威斯尼人平台登录发布于服务器&运维,转载请注明出处:虚拟机容器Kata架构,让你了解

相关阅读