基于Kubernetes的VM解决方案探讨


【编者的话】随着Kubernetes的发展壮大,越来越多的公司开始基于Kubernetes构建云平台,eBay也不例外。为了便于同时管理Kubernetes和OpenStack集群,eBay选用了virtlet管理方案,并在其基础上做了相应的定制和改进,以增强其可靠性和性能,从而更好地管理eBay云平台。

背景

eBay从2015年就开始适配Kubernetes平台并逐渐部署各个团队的产品。然而eBay仍然部署着很大规模的OpenStack集群。同时管理Kubernetes集群和OpenStack集群需要耗费更多的人力和物力。但由于eBay内部还有一部分业务无法迁移到容器,我们能否用一套控制平面(control plane)同时管理容器和虚拟机呢?如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

已有的方案

在几年的时间里,Kubernetes不断发展壮大,各个功能逐渐完善,越来越多的公司基于Kubernetes构建云平台。在eBay内部,越来越多的产品基于Kubernetes构建,因此用Kubernetes来统一云平台是大势所趋,我们需要一种方案来基于Kubernetes管理虚拟机。

现在社区有两套相对成熟的基于Kubernetes来管理虚拟机的方案,分别是KubeVirt和Virtlet。

Kubevirt

Kubevirt是RedHat发起的项目,它使用CRD去描述一个VM(Virtual Machine,虚拟机),通过它的控制器(controller)去把CRD转换成一个Pod。由于它使用的是CRD而不是Pod,导致需要额外的控制器来实现Kubernetes里原生的部署和服务(deployment,service)这些功能。VM的实例运行在容器(container)里,一个VM有对应的Libvirt来管理。Kubevirt的社区比较活跃,但版本还在较早的阶段。

Virtlet

Virtlet来自于Mirantis,跟Kubevirt的不同之处在于它使用Pod来描述一个VM。因为kubelet是通过CRI接口跟下面的运行时交互的,Virtlet实现了一套VM的CRI。因为Pod是Kubernetes的一等公民,任何现有的Kubernetes功能都可以用于Virtlet管理的VM,且不需要额外的控制器,比如服务、部署等等,这样几乎不需要额外的学习和维护成本。但因为一些VM特定的信息无法完全用Pod来描述,Virtlet借助了Pod的注解(annotation)来表达更多VM的信息。

我们的选择

根据我们的评估以及eBay现有的架构,选择了Virtlet来作为kube VM方案。首先kube VM入口是基于AZ( Availability Zone,可用区域)的,已经有了一套CRD来描述现有的Kubernetes节点,只需要加一个provider(提供者)就可以重用现有AZ的各种控制器。其次,需要各种定制来达到跟OpenStack VM同样的功能和性能。Virtlet因为有一套单独的CRI实现,可以更容易地加入各种定制。

Virtlet概览

图1是来自Virtlet社区的架构图。一个节点上Virtlet Pod包含三个容器:
  • Virtlet:接收CRI调用,管理VM
  • Libvirt:接收Virtlet的请求创建、停止或销毁VM
  • VMs:所有Virtlet管理的VM都会在这个容器的命名空间里


1.png

图 1

运行时

2.jpg

* 图 2*

CRIProxy

如图2所示,因为Virtlet有单独的CRI实现,如果在一个节点上既需要支持VM又要支持容器,就需要有一个Proxy来分发CRI调用:
  • 镜像:CRIProxy通过区分一个可配置的前缀来区分是一个容器镜像还是一个VM镜像。
  • 运行时:CRIProxy通过特定的注解来区分容器或VM。


默认情况下所有的调用会分发给容器运行时。

改进

因为引入了CRIProxy,多了一层中间调用,使这个架构看起来不够统一,多了一层可能的出错点,增加了出问题的几率以及调试的难度。所以我们下阶段计划开发一个VM shim,如图3所示。它满足containerd的shim规范。这样单个节点的运行时看起来更统一和干净。
3.jpg

图 3

模型

刚有提到,操作VM的入口是在AZ层,而且已经有了CRD来描述一个节点。同一个AZ管理着一个或者多个kubernetes集群。
4.jpg

图 4

这里的provider是Virtlet,专门管理VM的生命周期。如图4所示,它主要的任务是根据ComputeNode,首先挑选一个Kubernetes集群,然后在这个集群里创建一个对应VM的Pod,同步他们之间的状态。对于用户而言,下面的Pod是不可见的,用户通过创建或者删除ComputeNode来管理VM。

定制

Virtlet已经有了大部分所需要的功能,但还不能完全满足需求,我们对以下方面做了定制和改进:
  • VM网络
  • NUMA Pin
  • VM的重启、停止
  • OpenStack镜像兼容
  • Virtlet的可靠性


VM网络

Virtlet网络管理

对于现有的eBay VM,都是桥接(bridge)网络,只需要在libvirt domain指定目标桥接器,libvirt就会在启动VM的时候创建一个TAP接口,并把这个接口连接到指定的桥接器。

然而对于Kubernetes的VM,网络接口是调用CNI插件先配置好,然后才会创建和启动VM。Virtlet引入了vmwrapper,它是所有VM启动的入口,Virtlet会把vmwapper设置成libvirt domain的入口(emulator)。
5.jpg

图 5
从图5可见,启动一个libvirt实例后,vmwrapper会被首先执行:
  • Virtlet侦听在一个unix domain socket上,并且virtlet已经打开了相应VM的TAP接口。
  • vmwrapper发起连接跟virtlet通信,拿到TAP接口的文件描述符(FD)。
  • vmwrapper填写好网络相关的参数,最后启动真正的qemu进程。


qemu网络参数举例如下:
6.jpg

这里涉及到了进程间传递描述符(FD),Virtlet使用的是Linux的SCM(Socket level control messages)方式。

网络改进

每一次CNI插件配置网络,都会建一个新的网络命名空间(network namespace),一般需要有一对veth pair来连接主机的网络和新建的网络空间。Virtlet默认VM网络如图6:
7.jpg

图 6

连通一个VM,需要建一个Bridge,一对veth pair,这样网络的性能会有一定的影响。

VM本身有强隔离性,我们又有自己的CNI插件实现,为了减少网络的路径,创建VM的网络不需要新的网络命名空间。最终一个节点的网络结构如图7所示:
8.jpg

图 7

Vhost net

Virtlet不支持vhost net,而eBay OpenStack上使用Virtio的VM都有vhost net。增加这个功能,对每一个TAP接口,都需要有一个vhost net的描述符,这个描述符是通过打开/dev/vhost-net而来的。描述符的传递跟前面提到过的TAP描述符传递类似。

支持TSO(TCP Segment Offload)

在对Virtlet的VM进行网络性能测试的时候,网络吞吐量只有OpenStack VM的一半,在VM里面,soft IRQ负载很高,最终发现Virtlet VM的网络没有打开TSO和TX/RX校验。

无论是OpenStack的VM还是Virtlet的VM,都没有显示配置这个选项,但为什么OpenStack的VM默认是打开的呢?

前面提到,常规VM的网络接口是libvirt创建和删除的,而Virtlet的VM网络接口是由CNI创建、virtlet管理的。通过阅读libvirt和qemu的源代码,libvirt在打开TAP接口的时候会加一个IFF_VNET_HDR的选项,qemu检查到这个选项后, VM实例的网络接口就会默认打开TSO。通过这个小小的改动,virtlet VM的网络吞吐量跟OpenStack的VM旗鼓相当。

NUMA Pin

eBay数据中心的服务器都有2个或以上的NUMA节点,在这些节点上运行的VM,需要把他们尽量固定在NUMA节点上,否则跨NUMA节点的访问会带来性能问题。Kubernetes有CPU manager这个功能,CPU的分配也顾及到了机器上的NUMA节点,但不能完全满足要求,原因如下:
  • CPU是独占的。也就是一个CPU被分配给一个POD后,其它任何POD就不能再分配到这个CPU了,这样不能做到超售。
  • CPU的分配可能会跨NUMA节点,但并不平衡。比如一个POD需要8个CPU,那就有可能2个CPU分配在一个NUMA节点,另外的6个CPU分配在另一个NUMA节点,导致了不平衡。


基于以上原因,我们没有使用CPU manager这个功能,而是在virtlet里增加了一个模块来管理NUMA节点的分配和释放(这也是Virtlet有自己的CRI实现的好处之一):

固定VM所运行的NUMA节点,但不固定VM运行在NUMA节点对应的CPU。比如一台机器有2个NUMA节点,CPU 0-15在NUMA 0,CPU 16-31在NUMA 1,如果申请一个4核的VM,那这个VM只会运行在其中的一个NUMA节点上,但VM能用到的CPU可以是这个NUMA节点上所有的CPU(当然这只是标准的VM,对于有高性能要求的VM,既要有NUMA固定也需要有CPU固定)。

对于NUMA的分配,采取的是CPU、内存最平衡的方式,也就是在一个NUMA节点上,在分配量不超过某个阈值的前提下,计算已经分配的CPU和内存,加上将要被分配的VM需要的CPU和内存,CPU分配量和内存分配量差值的绝对值哪个最小,哪个优先级就最高。

VM的重启和停止

Virtlet没有提供VM重启(reset)和停止(stop),但eBay有些团队需要这样的功能。前面提到,virtlet的VM就是一个普通的POD,在kubernetes里如果一个POD不是在运行(running)状态,kubernetes就会不断重试,去把POD带回到运行状态。因此需要一个单独的模块来管理VM的状态。
  • 综合当前VM的实际状态和用户所期望的状态,做到最后状态一致。
  • 增加一个Pod的注解,里面包含所需要(request)的状态和实际(status)状态。
  • 在给kubelet汇报VM状态的时候,如果用户显示的是该VM已停止,仍然要向kubelet汇报是运行状态,这样kubelet就不会反复地调用启动的接口。


OpenStack镜像兼容

eBay运行着很大规模的OpenStack集群,有许多已有的VM镜像,Virtlet必须能无缝地使用这些已有的镜像。

在eBay,绝大部分OpenStack VM的网络信息是通过configdrive静态注入的方式,由cloutinit来完成网络的配置的,虽然virtlet的文档里声称支持configdrive,但他们使用的是不同的configdirve的版本,大部分的现有镜像都不能正确拿到configdrive所注入的信息。

因此需要实现另一个版本的configdrive,甚至还能兼容Windows VM。满足这两个需求,有两个关键的地方:
  • Configdrive盘必须有config-2的标签。
  • Configdrive必须是vfat格式的(虽然configdrive可以是vfat或者iso,但某些Windows的cloudinit不能识别ISO格式)。


可靠性

尽管VM的生命周期用Kubernetes来管理,但VM和容器还是有不同的地方:在VM的生命周期之内,不管是Pod重启、Virtlet重启以及节点重启,VM的所有数据不能有任何丢失;而容器在重启之后数据就会消失(不包括host path)。

Virtlet要能用在生产环境,必须做到数据不丢失。还有节点上的VM能够脱离virtlet运行良好,这样才能给virtlet升级。从一开始,我们就非常关注virtlet的可靠性,也找到了Virtlet可靠性所存在的一些问题:
  • 升级Virtlet本身,所有的VM也莫名其妙地消失了。
  • 重启节点,VM所有的数据丢失。
  • 停止VM和virtlet,然后再启动virtlet和VM,VM再也不能启动。


如果以上的问题不能解决,virtlet就不能用在生产环境,需要用其它方案甚至重新开发一套来代替。幸运的是通过积极阅读代码和调试,我们解决了上面的问题,社区也接受了这些补丁。

对于基于virtlet的一些改进和定制,需要保证每次改动不影响已有的功能,从一开始我们就开发了集成测试和端到端测试来保证可靠性。

性能

有了可靠的保障,性能也必须达标,参照物就是已有的OpenStack VM。
  • 我们使用各种基准测试工具对相同CPU、内存以及磁盘的virtlet VM和OpenStack VM进行测试。
  • 使用生产环境的镜像流量来比较两者的CPU和内存使用率,以及在每秒时间内能够处理的事务(TPS Transaction Per Second)。


比较下来的结果是两者没有差距,达到了我们的预期和要求。

结语

Kubernetes愈来愈受各家公司重视,这是未来几年甚至数十年云平台的趋势。但是从老的平台过渡到新的平台需要时间,一刀切的方式会带来无法遇见的风险和额外的耗时,特别是像eBay这样有多样产品和大体量的云平台公司。用Kubernetes来管理VM使这种过渡成为了可能,既不需要同时维护两套平台,也减少了新老交替带来的风险。

原文链接:https://mp.weixin.qq.com/s/CZzD1qf9QSGW9F5rsg13-Q

0 个评论

要回复文章请先登录注册