关于后端:虚拟机与容器的混合管理实践

17次阅读

共计 6605 个字符,预计需要花费 17 分钟才能阅读完成。

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 数智技术] 公众号

正文完
 0