基于kubernetes的VM解决方案探讨

一、背景

eBay从2015年就开始适配kubernetes平台并逐渐部署各个团队的产品。然而eBay仍然部署着很大规模的OpenStack集群。同时管理kubernetes集群和OpenStack集群需要耗费更多的人力和物力。但由于eBay内部还有一部分业务无法迁移到容器,我们能否用一套控制平面(control plane)同时管理容器和虚拟机呢

二、已有的方案

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

现在社区有两套相对成熟的基于kubernetes来管理虚拟机的方案,分别是kubevirt和virtlet

1. Kubevirt

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

2. 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实现,可以更容易地加入各种定制。

1. Virtlet概览

图1是来自virtlet社区的架构图。一个节点上virtlet POD包含三个容器

  • virtlet:接收CRI调用,管理VM
  • libvirt:接收virtlet的请求创建、停止或销毁VM
  • VMs:所有virtlet管理的VM都会在这个容器的命名空间里

图1(点击可查看大图)

2. 运行时

图2(点击可查看大图)

01 CRIProxy

如图2所示,因为virtlet有单独的CRI实现,如果在一个节点上既需要支持VM又要支持容器,就需要有一个proxy来分发CRI调用

  • 镜像:CRIProxy通过区分一个可配置的前缀来区分是一个容器镜像还是一个VM镜像。
  • 运行时:CRIProxy通过特定的注解来区分容器或VM。

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

02 改进

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

图3(点击可查看大图)

四、模型

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

图4(点击可查看大图)

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

五、定制

Virtlet已经有了大部分所需要的功能,但还不能完全满足需求,我们对以下方面做了定制和改进

  • VM网络
  • NUMA Pin
  • VM的重启、停止
  • OpenStack镜像兼容
  • Virtlet的可靠性

1. VM网络

01 Virtlet网络管理

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

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

图5(点击可查看大图)

从图5可见,启动一个libvirt实例后,vmwrapper会被首先执行

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

qemu网络参数举例如下

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

02 网络改进

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

图6(点击可查看大图)

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

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

图7(点击可查看大图)

03 Vhost net

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

04 支持TSO(TCP Segment Offload)

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

无论是Open Stack的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旗鼓相当

2. 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-15NUMA 0CPU 16-31NUMA 1,如果申请一个4核的VM,那这个VM只会运行在其中的一个NUMA节点上,但VM能用到的CPU可以是这个NUMA节点上所有的CPU(当然这只是标准的VM,对于有高性能要求的VM,既要有NUMA固定也需要有CPU固定)。
  • 对于NUMA的分配,采取的是CPU、内存最平衡的方式,也就是在一个NUMA节点上,在分配量不超过某个阈值的前提下,计算已经分配的CPU和内存,加上将要被分配的VM需要的CPU和内存,CPU分配量和内存分配量差值的绝对值哪个最小,哪个优先级就最高

3. VM的重启和停止

Virtlet没有提供VM重启(reset)和停止(stop),但eBay有些团队需要这样的功能。前面提到,virtlet的VM就是一个普通的POD,在kubernetes里如果一个POD不是在运行(running)状态,kubernetes就会不断重试,去把POD带回到运行状态。因此需要一个单独的模块来管理VM的状态

  • 综合当前VM的实际状态和用户所期望的状态,做到最后状态一致。
  • 增加一个POD的注解,里面包含所**需要(request)的状态和实际(status)**状态。
  • 在给kubelet汇报VM状态的时候,如果用户显示的是该VM已停止,仍然要向kubelet汇报是运行状态,这样kubelet就不会反复地调用启动的接口。

4. 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使这种过渡成为了可能,既不需要同时维护两套平台,也减少了新老交替带来的风险

本文转载自公众号eBay技术荟(ID:eBayTechRecruiting)

原文链接

https://mp.weixin.qq.com/s/CZzD1qf9QSGW9F5rsg13-Q

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章