1. 背景

以后容器曾经成为企业上云的支流抉择,通过2019年下半年的深度研发和推广,2020年OPPO根本实现了基于kubernetes的容器的大规模应用和全业务上云。容器的劣势是麻利和高性能,然而因为须要共享宿主机内核,隔离不彻底等起因,当用户须要批改很多定制的内核参数或者在低版本的 Linux 宿主机上运行高版本的 Linux 容器,或者只是须要隔离性更高时,在容器上都是难以实现的。而因为历史起因,公司外部也依然有一些业务须要应用强隔离的虚拟机,因而提供虚拟机服务,势在必行。
通过调研,咱们发现对于曾经建设有容器平台的公司,虚拟机的治理计划大部分是保护一套OpenStack或者相似的零碎。然而OpenStack宏大且沉重,保护老本较高,且底层资源不可能对立治理,将会为混合调度带来很多不便。因而咱们将对立的管制面治理,实现容器与虚拟机的对立调度和治理,作为选型的次要方向。

2. 计划选型 Kubevirt or Virtlet

虚拟机与容器通过k8s平台进行混合治理,业界比拟好的我的项目有kubevirt和virtlet等。
Kubevirt是Redhat开源的以容器形式运行虚拟机的我的项目,以k8s add-on形式,利用k8s CRD减少资源类型VirtualMachineInstance(VMI), 应用容器的image registry去创立虚拟机并提供VM生命周期治理。
Virtlet是一个Kubernetes(Container Runtime Interface)的实现,可能在Kubernetes上运行基于虚机的Pods。(CRI可能令Kubernetes运行非docker的容器,例如Rkt)。
上面这张图是咱们2020年初做选型时做的一个Kubevirt和Virtlet的比照图的局部。能够看到,Virtlet应用同一种资源类型Pod形容容器和虚拟机,因而如果应用原生的形式,虚拟机也只能有Running和删除两种状态,无奈反对pause/unpause, start/stop等虚拟机专属状态,显然这是无奈满足用户需要的。如果想要反对这些状态,又得深度定制kubelet,会导致虚拟机治理和容器治理耦合太重;另外思考到过后virtlet社区不如kubevirt社区沉闷,因而咱们最终抉择的计划是Kubevirt。

3. Kubevirt介绍

3.1 VmiCRD/Pod/Domain对应关系

3.2 组件介绍

kubevirt的各组件服务是部署在k8s上的,其中virt-api和virt-controller是deployment,能够多正本高可用部署,virt-api是无状态的,能够任意扩大;virt-controller是通过选举的形式选出一个主台提供服务;virt-handler以daemonset的形式部署,每一台虚拟机节点都运行一个virt-handler;而一个virt-launcher服务则对应一个虚拟机,每当创立一个虚拟机,都会创立一个对应的virt-launcher pod。
virt-api:
1)kubevirt API服务,kubevirt是以CRD的形式工作的,virt-api提供了自定义的api申请解决,能够通过virtctl命令执行同步命令 virtctl vnc/pause/unpause/stop/start vm等。
virt-controller:
1)与k8s api-server通信监控VMI资源创立删除等事件,并触发相应操作
2)依据VMI定义创立virt-launcher pod,该pod中将会运行虚拟机
3)监控pod状态,并随之更新VMI状态
virt-handler:
1)运行在kubelet的node上,定期更新heartbeat,并标记”kubevirt.io/schedulable”
2)监听在k8s apiserver当发现VMI被标记的nodeName与本身node匹配时,负责虚拟机的生命周期治理
virt-launcher:
1)以pod模式运行
2)依据VMI定义生成虚拟机模板,通过libvirt API创立虚拟机
3)每个虚构机会对应独立的libvirtd
4)与libvirt通信提供虚拟机生命周期治理

4. Kubevirt架构革新

4.1 原生架构


原生架构中治理面与数据面耦合。在virt-launcher pod中运行虚拟机,当因为不确定起因(比如说docker的起因或物理机起因或者virt-launcher自身的挂掉降级等起因),造成virt-launcher容器退出后,会导致虚拟机也退出,从而会影响用户应用,减少了虚拟机的稳定性危险。因而咱们在原有架构的根底上做了革新。

革新点:
1)将数据面kvm及libvirtd等过程移出治理面的virt-laucher容器,物理机上的libvirtd过程治理此物理机上的所有虚拟机。
2)新增virt-start-hook组件用以对接网络组件、存储组件及xml的门路变动等。
3)重构虚拟机镜像制作和散发形式,借助于OCS的对象存储管理,实现镜像的疾速散发。

除了实现治理面与数据面的拆散,咱们还在稳定性加强等方面做了很多工作。比方实现了kubevirt的每个组件不论在任何工夫任何状况下生效、故障、异样了,都不会影响到失常虚拟机的运行,并且要求测试笼罩到这些组件异常情况下的测试;物理机重启后虚拟机能够失常复原生命周期治理等生产级要求,进一步保障了整个虚拟机管理系统的稳定性。

4.2 革新后架构

4.3 架构革新后创立虚拟机流程

1)用户创立vmi crd,kubectl create -f vmi.yaml
2)virt-controller watch到新的vmi对象,为vmi创立对应的virt-launcher pod
3)virt-launcher pod创立好后,k8s的调度器kube-scheduler会将其调度到符合条件的kubevirt node节点上
4)而后virt-controller会将virt-launcher pod的nodeName更新到vmi对象上
5)kubevirt node节点watch到vmi调度到本节点后,会将虚拟机的根底镜像mount到指定地位,而后调用virt-launcher的syncVMI接口创立domain
6)virt-launcher承受到创立申请后,将vmi对象转变为domain对象,而后调用virt-start-hook,依据backingFile创立qcow2虚拟机增量镜像磁盘,将domain xml中的相干门路转变为物理机上门路,申请网络,配置xml,而后将最终配置好的xml返回virt-launcher
7)virt-launcher收到virt-start-hook的返回后,调用物理机上的libvirtd来define domain xml和create domain

4.4 架构革新后删除虚拟机流程

1)用户执行删除vmi命令,kubectl delete -f vmi.yaml
2)virt-handler watch到vmi的update事件,并且vmi的deletionTimeStamp不为空,调用virt-launcher shutdownDomain,virt-launcher调用virt-start-hook开释网络而后调用libvirtd关机
3)domain shutdown的音讯由virt-launcher watch到并发送给virt-handler,virt-handler依据vmi和domain已停机的状态调用virt-launcher deleteDomain,virt-launcher调用virt-start-hook删除网络而后调用libvirtd undefineDomain
4)domain undefine的音讯由virt-launcher watch到并发送给virt-handler,virt-handler依据vmi和domain已删除的状态更新vmi增加domain已删除的condition,而后清理该domain的垃圾文件及门路
5)virt-controller watch到vmi状态deleteTimeStamp不为空,并且vmi的condition DomainDeleted为True,则删除virt-launcher pod,而后等pod删除后,清理vmi的finalizer,使vmi主动删除

5. 存储计划

5.1 原生镜像存储计划

kubevirt中虚拟机的原始镜像文件会ADD到docker根底镜像的/disk门路下,并推送到镜像核心,供创立虚拟机时应用。

创立虚拟机时,会创立一个vmi crd,vmi中会记录须要应用的虚拟机镜像名称,vmi创立好后virt-controller会为vmi创立对应的virt-launcher pod,virt-launcher pod中有两个container,一个是运行virt-launcher过程的容器compute,另一个是负责寄存虚拟机镜像的容器container-disk,container-disk容器的imageName就是vmi中记录的虚拟机镜像名称。virt-launcher pod创立后,kubelet会下载container-disk的镜像,而后启动container-disk容器。container-disk启动好后会始终监听在—copy-path下的disk_0.sock文件,而sock文件会通过hostPath的形式映射到物理机上的门路/var/run/kubevirt/container-disk/vmiUUID/ 中。

virt-handler pod会应用HostPid,这样virt-handler 容器内就能够看到物理机的pid和挂载信息。在创立虚拟机时,virt-handler会依据vmi的disk_0.sock文件找到container-disk过程的pid,标记为Cpid,而后依据/proc/Cpid/mountInfo找到container-disk容器根盘的磁盘号,而后依据container-disk根盘的磁盘号和物理机的挂载信息(/proc/1/mountInfo)找到container-disk根盘在物理机上的地位,再拼装上虚拟机镜像文件的门路/disk/xxx.qcow2,拿到虚拟机原始镜像在物理机上的理论存储地位sourceFile,而后将sourceFile mount 到targetFile上,供前面创立虚拟机时作为backingFile应用。

5.2 本地盘存储

原生kubevirt中依据根底镜像backingFile创立的增量镜像文件xxx.qcow2只反对放到emptydir中,而咱们的容器的数据盘个别应用的是lvm的形式,如果保留两种应用形式的话,在虚拟机容器混合部署的场景中,不利于物理机磁盘的统一规划对立调度,因而咱们在原生的根底上也反对了虚拟机增量镜像文件寄存到由virt-launcher容器申请的lvm盘中,从而放弃了虚拟机与容器磁盘应用形式的一致性。此外咱们还反对了为虚拟机独自创立一个qcow2空盘挂载为数据盘应用,也寄存在virt-launcher容器申请的另外的lvm盘中。

5.3 云盘存储

咱们为虚拟机的系统盘和数据盘对接了云存储,不便用户在迁徙或者某些其余场景下应用。

5.3.1 系统盘接入云盘

系统盘对接云存储,首先须要将虚拟机的根底镜像上传到basic ns下的pvc中,而后依据此pvc创立volumesnapshot。而在某个namespace下创立虚拟机时,须要从basic ns下拷贝根底镜像的volumesnapshot到本人的namespace下,而后根据拷贝的volumesnapshot创立出新的pvc给虚拟机应用。其中上传虚拟机根底镜像到basic namespace下的pvc及做snapshot的步骤,咱们做了一个上传镜像的工具来对立治理;而创立虚拟机时须要的系统盘pvc及将pvc挂载到 vmi中的一系列操作,咱们则是通过一个新定义的crd,及新的crd controller来实现对立的自动化治理。

5.3.2 数据盘接入云盘

数据盘对接云存储,则是先在虚拟机所在namespace下创立pvc,而后将pvc配置到vmi的yaml中,virt-controller在创立vmi对应的virt-launcher pod时,会依据vmi中pvc的配置,将pvc volume配置到virt-launcher pod中,而后存储组件会挂载一个带有pvc信息的目录给pod,之后virt-start-hook会依据virt-launcher pod中pvc目录中的信息将云盘配置到domain的xml,供虚拟机应用。

6. 扩大性能

6.1 反对虚拟机停机/启动/重启

原生kubevirt提供了一些同步接口,比方pause和unpause,别离的作用是将虚拟机挂起和唤醒。原生的stop和start须要操作vm crd会导致虚拟机销毁再重建,这无奈满足咱们的需要。另外因为本来的架构不反对虚拟机的shutdown和start,因而也并未提供间接stop和start及reboot本虚拟机的接口(stop即对应shutdown)。而咱们的用户有这个需要,因为通过架构革新后的kubevirt反对了虚拟机的shutdown和start,因而咱们也在pause/unpause vmi的根底上定义开发了虚拟机的stop/start/reboot等接口,并减少了stopping,starting,rebooting等中间状态,不便用户查看应用。

6.2 反对虚拟机动态扩容缩容CPU/内存/本地盘

停机扩容缩容CPU/Mem/本地盘,提供的也是同步接口。此性能在扩容时,最终批改虚拟机的xml配置之前,须要先动静扩容virt-launcher pod的相干资源以便于查看虚拟机所在节点上是否有足够的资源进行扩容,如果所在节点资源有余须要拦挡本次扩容申请,并回滚对于vmi及pod等相干配置的相干批改。而动静扩容pod配置,原生kubernetes是不反对的,这是咱们在外部的k8s中提供的另外一套解决方案。

6.3 反对虚拟机Cpu绑核及大页内存

cpu绑核性能次要是联合了kubelet的cpuset性能来实现的,须要kubelet配置—cpu-manager-policy=static开启容器的绑核性能。流程大略是这样的,vmi配置上cpu的相干绑核配置dedicatedCpuPlacement=”true”等,而后创立guarantee的virt-launcher pod,virt-launcher pod调度到开启了绑核配置的kubelet节点上,kubelet为virt-launcher pod调配指定的cpu核,而后virt-launcher过程从本人的container中查看本人有哪些核,再将这些核配置到虚拟机xml中,从而通过kubelet治理cpu的形式实现了虚拟机和容器的cpuquota和cpuset的调配形式的对立治理。而虚拟机大页内存也是与k8s资源管理联合的思路,即通过应用k8s中已存在的大页内存资源,通过pod占用再调配给虚拟机的形式实现的。

6.4 其余性能

除了以上介绍的扩大性能外,咱们还实现了反对虚拟机动态和动静减少或缩小云盘、重置明码、查看虚拟机xml、反对云盘只读限度、反对直通GPU、直通物理机磁盘、virtionet反对多队列、IP显示优化等其余需要,供用户应用。

总结

以后咱们已在多个集群中,同时提供了虚拟机和容器服务,实现了混合集群治理。基于此计划生产的虚拟机在咱们的公有云畛域曾经提供给了泛滥业务应用,在稳定性及性能等方面都提供了强有力的保障。下一步次要的工作在于将容器和虚拟机在节点上可能实现混合部署,这样不仅在管制面上可能进行对立调度,在数据面上也可能进行混合治理。

另外除了本文介绍的工作之外,咱们也实现了虚拟机快照,镜像制作和散发,动态迁徙等计划,后续咱们团队也会持续发文分享。

作者简介
Weiwei OPPO高级后端工程师
次要从事调度、容器化、混合云等相干方向的工作。

获取更多精彩内容,扫码关注[OPPO数智技术]公众号