1. 为什么在容器时代还要折腾虚拟机
先聊一个很多刚接触云原生的人都会有的疑惑:Kubernetes 不是已经把基础设施“容器化”了吗?为什么还要在上面跑虚拟机?这不是开历史倒车吗?
这个问题我经常被问到。实际情况是,容器和虚拟机从来不是非此即彼的关系。容器适合无状态、弹性伸缩的微服务,但还有大量存量业务跑在虚拟机里:老旧的单体应用、需要特定内核版本的遗留系统、依赖硬件特性(比如嵌套虚拟化、直通设备)的中间件、以及那些“不能轻易动”的数据库。把这些系统强行容器化,往往要付出巨大的改造代价。
KubeVirt 要解决的正是这个痛点:让虚拟机像 Pod 一样被 Kubernetes 统一调度和管理。开发者可以用写 YAML 的方式定义虚拟机,用kubectl命令操作虚拟机,用 Kubernetes 的 API 直接创建、删除、迁移虚拟机实例。基础设施团队不需要维护两套管理平台——OpenStack 管虚拟机、K8s 管容器——而是把虚拟机和容器放进同一个资源池,由同一个调度器统一分配计算、存储和网络资源。
这套方案的核心价值在于纳管存量,统一入口。你不需要把老系统改造成微服务,只需要把它的虚拟机镜像“搬”进 Kubernetes 就能管理起来。从运维视角看,网络插件、存储插件、监控告警、日志采集,全都复用 K8s 既有生态,不用另起炉灶。
这篇内容适合两类读者:一类是已经在生产环境运行 Kubernetes、但手上还有一堆虚拟机甩不掉的运维团队;另一类是想要了解云原生虚拟化未来演进方向的技术决策者。我会从架构原理、部署要点、实操细节到问题排查,把 KubeVirt 这套方案的核心脉络拆开来讲。
2. 架构原理:KubeVirt 如何把虚拟机“包装”成工作负载
2.1 VMI 与 CRD:让 K8s 听懂“虚拟机”的语言
KubeVirt 的核心设计思想,是把虚拟机抽象成 Kubernetes 的一等公民。它没有修改 Kubernetes 的任何代码,而是通过 CRD(Custom Resource Definition)扩展了 API。
其中最核心的自定义资源叫 VMI(VirtualMachineInstance)。一个 VMI 代表一个正在运行的虚拟机实例。但用户日常操作的不是 VMI,而是更高一层的资源叫 VirtualMachine(简称 VM)。这两者的关系有点像 Deployment 和 Pod:VM 是声明式的模板,VMI 是实际跑起来的实例。你创建、删除、重启的都是 VM,KubeVirt 控制器会自动维护对应的 VMI 生命周期。
还有几个比较常用的 CRD 资源:
- VirtualMachineInstanceReplicaSet:对应 ReplicaSet 的语义,可以定义多副本虚拟机组,滚动升级时用得上。
- VirtualMachineInstancePreset:类似 PodPreset,给虚拟机的配置项注入默认值,适合批量设置同一批 VM 的公共参数。
- VirtualMachineInstanceMigration:触发虚拟机热迁移的 CRD,后面实战部分会详细讲。
这些资源全部描述在 KubeVirt 自带的 CRD 清单里,安装 KubeVirt 时自动注册到集群。K8s 会把这些 CRD 当成内建资源一样处理:走 API Server 的认证授权、进 etcd 持久化、支持 label selector 过滤查询。
2.2 控制面与数据面:KubeVirt 的组件怎么协作
KubeVirt 的架构可以用四个核心组件概括,它们职责边界很清楚:
virt-controller:控制面的大脑。它监听 VM、VMI 等资源的状态变化,负责调度决策、创建/删除 Pod、维护 replica 数量。从功能上类比,它做的事和 K8s 自带的 ReplicaSet 控制器几乎一样,只不过管理的对象是虚拟机。
virt-handler:运行在每个节点的 DaemonSet。它负责本节点上的虚拟机实际管理:把 virt-launcher 启动的虚拟机进程绑定到正确的 CPU/内存资源上,配置网络接口,对接存储卷,并且实时上报虚拟机状态给 API。virt-handler 与 virt-controller 之间的通信通过 K8s watch 机制完成,不走独立的消息通道,这套设计天然继承了 K8s 的事件驱动模型。
virt-launcher:这个组件可能是最容易让人困惑的地方。每个 VMI 的背后实际上是一个普通的 Pod,Pod 里第一个容器就叫 virt-launcher。virt-launcher 通过 libvirt 库直接拉起一个 QEMU 进程,QEMU 就是真正模拟硬件、运行客户机操作系统的程序。也就是说,KubeVirt 的做法是在 Pod 里跑 QEMU 进程,而不是在每个节点上单独装一套虚拟化平台。
virt-api:提供 REST API 接口,专门处理 VMI 相关的操作请求,比如virtctl命令连接 VNC/console 就是走这个组件。同时它还负责 webhook 的校验、子资源的管理。
整个数据流的链路是:用户提交 VM 清单 → virt-controller 感知状态变化 → 创建对应的 virt-launcher Pod → 调度器把 Pod 调度到某个节点 → virt-handler 收到新 Pod 分配到本节点的事件 → 通过 libvirt 启动 QEMU 进程 → 虚拟机开始运行。这个过程和 Deployment 滚动创建一个 Pod 的链路相似,只是终点从容器运行时换成了 QEMU。
2.3 为什么选 QEMU 而不是其他虚拟机监控器
KubeVirt 早期版本也做过其他尝试,最终还是采用 QEMU 作为默认的虚拟机监控器。这个选择的背后有几个实际考虑。
第一,QEMU 对硬件虚拟化(KVM)的支持非常成熟。KVM 内核对 QEMU 进程的性能损耗极小,直通设备(PCI Passthrough)、NUMA 拓扑绑定、CPU 型号固化这些虚拟化特性,QEMU 都支持并经过了大规模生产验证。
第二,libvirt 作为管理中间层,屏蔽了底层差异。你以后如果想把底层换成 LXD 或 VirtualBox 系方案,改动范围会相对可控。虽然实际生产大家基本全用 QEMU,但 libvirt 这一抽象层让架构层面留了回旋余地。
第三,镜像格式兼容性极好。QEMU 原生支持 qcow2、raw、vmdk(VMware 格式)、vhd 等常见磁盘格式,意味着从其他虚拟化平台迁移虚拟机,镜像格式转换的兼容性问题会小很多。
3. 环境准备与部署:从零开始跑起 KubeVirt
3.1 部署前置条件:硬件虚拟化是硬门槛
KubeVirt 虽然是在软件层模拟虚拟机,但性能要过得去,必须依赖节点的硬件虚拟化特性。部署前先确认节点 CPU 支持虚拟化扩展,Intel 的机器检查 VT-x(即 CPU 虚拟化技术标志),AMD 的机器检查 SVM。Linux 系统下可以用:
grep -E "(vmx|svm)" /proc/cpuinfo如果没有任何输出,大概率是 BIOS 里没开虚拟化,或者当前环境的嵌套虚拟化没透传。各云厂商的云主机默认不一定开启嵌套虚拟化,需要单独确认。KubeVirt 官方文档里列了不同云商的启用方法,顺序差别不小,我建议在你购买或申请资源之后第一时间验证这一点,省得部署到一半才发现节点不支持。
节点上还需要确认 KVM 内核模块已经加载:
ls /dev/kvm如果这个设备节点不存在,说明 KVM 模块未加载,或者用户态的访问权限没放开。virt-launcher 里的 QEMU 进程需要能读写/dev/kvm,通常通过特权容器的方式解决。如果/dev/kvm不可用,KubeVirt 会退化为软件模拟(TCG 模式),除非你只是测试语法,否则生产环境千万别这么干,性能差距可能达到数量级。
3.2 使用 operator 方式安装
KubeVirt 官方推荐通过 Operator 方式部署。整个过程可以概括为两步:安装 KubeVirt Operator,然后创建 KubeVirt CR 实例。
第一步,部署 operator 本身:
RELEASE=v1.2.0 kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/kubevirt-operator.yaml第二步,创建 KubeVirt 自定义资源:
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/kubevirt-cr.yaml然后观察部署进度:
kubectl -n kubevirt get pods kubectl -n kubevirt wait --for=condition=Ready \ kubevirt/kubevirt --timeout=120s安装完成后检查一下各组件的状态。正常情况下kubevirt命名空间下会有 virt-api、virt-controller、virt-handler、virt-operator 这四组 Pod 各就各位。
这中间有一个反复需要确认的点:版本选择。KubeVirt 对 Kubernetes 版本是有严格兼容矩阵的,比如新版本可能要求集群至少是 1.23 或更高。如果你用的是比较老的 K8s 发行版,先查一下兼容矩阵再决定装哪个 release,不然 API 资源版本对不上,CRD 注册会失败。
3.3 安装 virtctl:管理工具准备
virtctl 是 KubeVirt 的命令行工具,作用等同于kubectl之于 Pod。主要用途是虚拟机开机、关机、重启、打开串口控制台、VNC 控制台等操作。
下载方式:
RELEASE=v1.2.0 wget https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/virtctl-${RELEASE}-linux-amd64 chmod +x virtctl-${RELEASE}-linux-amd64 sudo mv virtctl-${RELEASE}-linux-amd64 /usr/local/bin/virtctl装完之后验证一下:
virtctl version这里注意,virtctl 操作集群需要 kubeconfig 认证,默认读取与 kubectl 相同的配置文件,如果多集群环境航别搞混了,先确认kubectl config current-context指向的是目标集群。
4. 存储与网络:虚拟机在 K8s 里的“命脉”
4.1 磁盘方案选型:从容器磁盘到 PVC
虚拟机的操作系统和业务数据得有个地方放。KubeVirt 支持多种存储方案,重点讲几个我用下来觉得最实用的。
ContainerDisk(容器磁盘):把虚拟机镜像打包进容器镜像。本质上是在镜像里放了一个 qcow2 或 raw 格式的磁盘文件,virt-launcher 启动时从镜像仓库拉取并挂载给 QEMU。这个方案的好处是分发特别方便,配合 Registry 就能实现镜像的版本管理和全网分发;坏处是写操作是临时的,重启后修改就不在了,所以只适合做只读的系统盘或测试环境。
PVC(PersistentVolumeClaim):生产环境最常用的方案。PVC 直接挂载给 virt-launcher Pod 的数据卷,KubeVirt 在 QEMU 进程里把这个卷当块设备使用。优点是数据持久化,PVC 的底层存储可以是 NFS、Ceph RBD、本地盘等任意存储供应商提供的方案;缺点就是比较绕,磁盘格式、大小、访问模式都得你自己维护好,等于把存储运维的活接过来了。
实际生产建议:boot 磁盘(操作系统所在盘)用 PVC,因为操作系统需要做持久化修改;如果有临时缓存类数据,可以用 emptyDisk 或者挂一个普通临时目录给虚拟机,性能好且不占存储配额。
创建 PVC 的 YAML 大概长这样:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: vm-disk spec: storageClassName: ceph-rbd accessModes: - ReadWriteOnce resources: requests: storage: 50Gi然后创建虚拟机的 YAML 里引用这个 PVC:
spec: domain: devices: disks: - name: rootdisk disk: bus: virtio bootOrder: 1 volumes: - name: rootdisk persistentVolumeClaim: claimName: vm-diskblock volume 和 filesystem volume:这个差异容易被新手忽略。PVC 如果是指向裸设备(如 Ceph RBD)的 block volume,KubeVirt 直接把它作为块设备给 QEMU,性能好;如果 PVC 是 filesystem 类型(比如 NFS 挂载),KubeVirt 会创建镜像文件放在上面,再以文件方式给 QEMU 使用。二者在 YAML 上的区别主要体现在 storageClassName 配置的 provisioner 类型,但这直接影响性能上限。建议:追求性能的统一用 block 类型。
4.2 网络模式:默认 Pod 网络与多网络支持
默认 Pod 网络:KubeVirt 创建的 virt-launcher Pod 默认接入同一个 Pod 网络,虚拟机拿到的是 Pod 的 IP 地址,和容器一样。这意味着虚拟机能直接利用 K8s 集群内部的 DNS 解析、网络策略、Service 负载均衡能力。举个例子,你可以创建一个 Service 指向 VM Pod,流量能自动负载均衡到后端的虚拟机上。
默认网络有个限制:KubeVirt 在 Pod 网络里创建的接口是 virtio 类型,接收进来的流量会经过节点上的 cni 插件和 Kube-proxy,网络路径比较长,性能不是极限优化的选择。
多网络方案(Multus):如果要让虚拟机接入独立于 Pod 网络的二层网络(比如 VLAN 隔离的旧网络),需要安装 Multus CNI。Multus 解决的是一“网”多用问题:Pod 可以有多个网卡,每个网卡由不同的 CNI 插件管理。KubeVirt 通过 network 配置为虚拟机附加第二块网卡,将物理网络或现有 VLAN 直接桥接进虚拟机。
生产实践里我见得多的是这个组合:KubeVirt + Multus + bridge CNI 插件,让虚拟机接入企业已有网络基础设施,虚拟机的 IP 由传统 DHCP 分发,和物理机没有任何区别,可以绕过 K8s Service 层直接对外提供服务。
4.3 CPU 与内存配置:别让虚拟化打折扣
KubeVirt 对虚拟机的 CPU 配置支持得很细。YAML 里常见的配置包括:
spec: domain: cpu: cores: 4 sockets: 1 threads: 1 resources: limits: memory: 8Gi这里有一个特别容易踩的坑:cores、threads、sockets 三个参数是乘数关系。客户机操作系统看到的总逻辑 CPU = cores × sockets × threads。如果三者之和大于节点物理 CPU 数,调度器会直接调度失败。更麻烦的是,如果这两个参数设得不对,客户机里的应用看到的核数和预期不一致,性能问题排查起来极其隐蔽。
内存方面,有个guest字段值得多说一句。它定义的是客户机操作系统实际可见的内存大小,而 resources 定义的是 virt-launcher Pod 预留的内存。如果 guest 和 resources 不完全一致,可能出现 Pod 内存使用率看起来很低,但虚拟机里已经内存吃紧的情况。建议设置成接近的资源配额,避免过度分配造成的不可预期问题。
5. 实战案例:创建并管理一台虚拟机
5.1 从 PVC 启动云镜像虚拟机的完整流程
以一个生产常用的场景为例:从 cloud image 创建一台 Ubuntu 虚拟机。
先创建 PVC:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ubuntu-pvc spec: storageClassName: ceph-rbd accessModes: - ReadWriteOnce resources: requests: storage: 30Gi将云镜像导入 PVC 有两条路线。简单粗暴的路线:先用一个 Job 或者直接用virtctl image-upload把镜像推进 PVC,官方提供这个子命令,支持本地镜像文件或 URL:
virtctl image-upload --pvc-name=ubuntu-pvc --image-url=https://cloud-images.ubuntu.com/... --size=30Gi这条命令会创建对应的 UploadPod,然后把镜像数据写入 PVC。注意:目标 PVC 的容量需要高于镜像解压后的大小,如果镜像比 PVC 大,上传会直接失败。
接着创建 VirtualMachine 资源:
apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: ubuntu-vm spec: running: true template: metadata: labels: app: ubuntu-vm spec: domain: devices: disks: - name: rootdisk disk: bus: virtio resources: requests: memory: 4Gi volumes: - name: rootdisk persistentVolumeClaim: claimName: ubuntu-pvc创建后确认状态:
kubectl get vms kubectl get vmisrunning: true会让虚拟机立即启动。之后用 virtctl 连接控制台:
virtctl console ubuntu-vm如果是 cloud image,需要注入 cloud-init 配置。KubeVirt 支持把 cloud-init 配置通过启动脚本传递:
spec: domain: devices: disks: - name: cloudinitdisk disk: bus: virtio volumes: - name: cloudinitdisk cloudInitNoCloud: userData: | #cloud-config password: YourPassword chpasswd: expire: false ssh_pwauth: true这个配置会让虚拟机在首次启动时设置初始密码,并允许 SSH 密码登录。生产环境记得换用 ssh_authorized_keys 配合公钥注入,密码方式仅限测试使用。
5.2 串口控制台与 VNC:虚拟机“窗口”怎么开
KubeVirt 对虚拟机的操作接口有两个:串口控制台(serial console)和图形控制台(VNC)。
串口连接对应客户机运行virtctl console vm-name。它依赖客户机的串口配置,Linux 一般内核默认开启;Windows 客户机的串口配置则需要额外特判。平时维护服务器负载的虚拟机,串口就够了,命令行操作速度快、带宽占用低。
VNC 连接用virtctl vnc vm-name。这个命令不走浏览器,需要配合一个 VNC 客户端。virtctl 会在本机建立一个 VNC 代理并打印端口信息,连接方式通常类似于:
virtctl vnc ubuntu-vm --port=59485然后本地 VNC 客户端连127.0.0.1:59485。Linux 下可以用virt-viewer,Windows 下可以用 RealVNC 或 TightVNC。
值得注意:VNC 初始访问往往要求输入密码,KubeVirt 默认的 VNC 密码机制依赖 libvirt 的 per-VM 授权,如果你第一次连接看到密码提示,检查 VM 的 spec 里有没有配置相关选项。如果没有特殊安全需求,生产环境建议直接禁用 VNC,只保留串口,减少攻击面。
5.3 虚拟机的启停、重启与删掉重来
日常运维操作看起来简单,但有几个细节容易出问题。
virtctl start vm-name/virtctl stop vm-name控制启停。stop 操作默认执行的是“优雅关闭”流程:KubeVirt 先发送 ACPI 关机信号给客户机,客户机执行系统关闭流程,等待一段时间后如果还没关完,再强制断电。这个行为由spec.terminationGracePeriodSeconds控制,默认是 300 秒。有些业务系统关机特别慢,建议把超时调大;反之如果需要频繁重启且业务允许,可以调小,自动化流程里能省不少等待时间。
virtctl restart vm-name执行的是重启:VMI 被关闭,再被新建。这一步有个注意点:重启之后虚拟机的 IP、hostname 等都会重新分配,如果你的应用依赖固定 IP,得靠 DHCP 保留或者静态地址配置来保证。
删除虚拟机:
kubectl delete vm ubuntu-vm这条命令删掉 VM 资源,但默认不会删除 PVC,所以数据是安全的。如果连数据盘一起删,需要先删除 PVC。这里奉劝一句:生产环境的虚拟机删除操作,建议用 IaC(比如 ArgoCD 或 Terraform)来管理,否则一次手滑删错 VM 配合默认保留数据的机制,找数据可比重建 VM 麻烦得多。
6. 热迁移:不中断业务的虚拟机搬家
6.1 热迁移的基本原理与前提条件
KubeVirt 的热迁移(Live Migration)是把运行中的虚拟机从节点 A 平滑迁移到节点 B,迁移过程中业务几乎无感。原理上和传统虚拟化平台的热迁移类似:QEMU 先把内存状态增量同步到目标节点,期间源节点的虚拟机继续运行,等内存状态同步到接近一致时,短时间暂停源虚拟机,把最后一遍增量数据同步过去,然后在目标节点恢复运行。这个“最后一跳”的停顿时间通常在几百毫秒之内。
要能迁移成功,有几个前置条件必须满足:
- 集群必须有至少两到三个可用节点。
- 虚拟机不能使用宿主机直通设备(PCI Passthrough)或 untagged passthrough 网卡,这类设备迁移不切实际。
- 虚拟机磁盘必须使用持久化存储(PVC),且两个节点都能访问同一份数据。本节点本地存储(localstorage)是不行的。
- 客户机的驱动需支持 virtio 标准设备,运行状态里显示使用了 virtio 的磁盘和网卡。如果客户机用的是模拟 IDE 或旧式设备,部分状态无法安全迁移。
- 节点内存和 CPU 资源要足够承载迁移目标虚拟机的资源需求。
多个迁移任务并行存在时,node 上会限制同时迁移的 VM 个数,避免网络带宽和 CPU 过载影响业务。
6.2 迁移操作与状态监控
手动触发热迁移有两种方式。
第一种,创建 Migration 对象:
kubectl apply -f - <<EOF apiVersion: kubevirt.io/v1 kind: VirtualMachineInstanceMigration metadata: name: migration-ubuntu-vm spec: vmiName: ubuntu-vm EOF第二种,快捷指令:
virtctl migrate ubuntu-vm迁移过程中查看状态:
kubectl describe vmis ubuntu-vm输出的 Conditions 里会看到 MigrationState 相关的字段,包括源节点、目标节点、迁移进度等。如果迁移失败,Events 里通常会带上失败原因——常见的失败原因包括资源不足、存储连接超时、节点网络分片等。
迁移成功后,VMI 会保留在新节点上运行,virt-controller 会更新 VMI 的状态以及对应的节点关联关系。
6.3 节点维护场景:优雅腾挪虚拟机
生产环境遇到节点要升级内核或维修硬件时,热迁移是减少业务中断的最佳方式。
操作流程很简单:给节点打上维护标签:
kubectl label node node-01 kubevirt.io/unsafe-evict=evict这个标签是 KubeVirt 的自动驱逐机制入口。配合KubeVirt配置中的nodeSelector和evictionStrategy: LiveMigrate参数,运行在该节点的所有可迁移虚拟机都会自动触发热迁移到其他节点。
如果虚拟机设置了evictionStrategy: LiveMigrate,即使没显式触发迁移,在节点被标记不可调度时,KubeVirt 也会尝试自动热迁移。这个字段强烈建议给所有生产虚拟机上设置,尤其是数据库这类有状态的在线服务,能避免很多运维事故。
spec: evictionStrategy: LiveMigrate这个配置可以在 VirtualMachine 的 spec 里全局设置,对模板内所有 VMI 生效。
7. 常见问题与排查技巧实录
7.1 虚拟机长时间处于 Scheduling 状态
这个问题出现频率很高。可能的原因分几类:
第一,节点资源不足。虚拟机的 requests 资源无法被任何节点满足,调度器又找不到其他可用节点。用kubectl describe vmi查看事件,一般能看到0/3 nodes are available之类的调度提示。解法是扩容节点、释放资源或降低虚拟机的资源配置。
第二,节点没法运行虚拟机。最常见的是/dev/kvm不存在,或者节点带上了不能被调度的污点(taint)。比如节点被打了kubevirt.io/unsafe-evict维护标签后,新虚拟机不会调度上去。
第三,镜像拉取失败。如果 virt-launcher 容器镜像没有拉取下来,或者镜像拉取策略配置不当,Pod 会一直卡在ImagePullBackOff。检查本地镜像仓库的连通性以及镜像 tag 是否正确。
排查口诀:先kubectl describe vmi看事件,再kubectl get pods找到对应的 virt-launcher Pod,最后kubectl logs看 virt-launcher 的日志输出。
7.2 virt-launcher Pod 启动后立刻崩溃
这类问题通常能从日志里看到端倪。
如果是 libvirt 初始化失败,检查节点上的/dev/kvm权限是否正常;如果 QEMU 进程启动直接退出,检查资源限制是否足够(例如 memory 申请过大导致 OOM);如果报设备直通失败,确认 PCI 设备是否已被其他虚拟机占用。
virt-launcher 的日志路径:
kubectl -n <namespace> logs virt-launcher-xxxx注意 virt-launcher 进程所在的容器有多个,需要通过-c参数指定容器名。一般核心日志都在名为compute的容器中。
7.3 性能不对:虚拟机比物理机慢一截
性能问题排查的方向比较多,但按优先级排,最容易出问题的往往是这几个:
CPU 型号问题。客户机看到的 CPU 型号如果和宿主机差异大,某些指令集用不了,应用性能会明显抖动。KubeVirt 支持配置 CPU model,生产环境建议显式指定为host-model或者host-passthrough,让客户机直接使用宿主机的 CPU 特性集。
spec: domain: cpu: model: host-passthroughhost-passthrough性能最好,但牺牲了迁移兼容性;跨代 CPU 节点池,建议用host-model折中。
磁盘 IO 路径。确认虚拟机磁盘总线类型是 virtio,而不是模拟 IDE 或 SATA。virtio 是半虚拟化设备模型,性能远优于全模拟。查看 VMI 的 domain 描述即可确认。
网络路径。默认 Pod 网络的性能受限于 CNI 插件和网络策略的处理能力。如果压测发现网络吞吐明显低,考虑用 Multus 附加 bridge 类型网卡直通给虚拟机,绕开额外的封装和转发点。
7.4 VNC 无法连接或黑屏
VNC 连不上先检查三件事:虚拟机是否处于 running 状态、virtctl 的 VNC 端口映射是否正确、VNC 客户端协议是否兼容。黑屏通常意味着客户机的显示设备没有配置正确。部分精简 Linux 镜像没有启用图形终端,需要配置DISPLAY或者安装图形组件;服务器版镜像建议直接用 console。
7.5 热迁移失败
迁移失败常见的报错集中在存储不可达、内存数据同步超时、客户机设备不支持三块。
- 存储:确认 PVC 的存储后端支持跨节点访问(ReadWriteMany 或底层支持裸设备挂载),NFS 类存储注意挂载参数。
- 超时:网络带宽不足或节点负载过高导致迁移进度推进缓慢,KubeVirt 有超时阈值。优化节点间网络,或限制同节点并发迁移数。
- 设备:检查虚拟机是否使用全模拟设备(如 e1000 网卡),替换成 virtio 后再试。
8. KubeVirt 周边生态与生产落地建议
KubeVirt 不是孤岛,真正要把这套体系落地,还需要配合周边工具链。
虚拟机模板:直接用 YAML 创建 VM 可以,但生产环境的虚拟机要配置启动盘、网卡、资源规格、cloud-init,YAML 会写得很长。用 Helm Chart 或者 KubeVirt 官方模板功能(Common Templates)减少重复劳动。轮子别重复造。
备份与容灾:虚拟机的数据在 PVC 里,备份方案主要围绕存储层做。Ceph RBD 有快照功能、可以做块级备份;如果是应用层备份,velero 配合 KubeVirt 插件也能把 VM 和 PVC 整组备份。多集群容灾场景,虚拟机迁移配合存储复制需要提前设计好恢复窗口,不是简单两条命令的问题。
监控告警:KubeVirt 官方暴露 Prometheus 指标,包括虚拟机数、迁移时长、磁盘 IO。配合 Grafana 的 KubeVirt dashboard 快速搭建虚拟化监控大屏。建议除指标外,关注 VM 的租户扩容告警,比如 PVC 使用率超过 80% 就触发通知。
虚拟化安全:虚拟机的隔离性弱于容器?不一定。KubeVirt 里 virt-launcher Pod 本身运行在普通容器环境里,但 QEMU 进程通过 libvirt 和宿主机的 KVM 交互,逃逸面比普通容器要大。务必关注 CVE 公告,及时升级 KubeVirt 和 QEMU 版本;避免给虚拟机直接映射节点的大页内存而不做隔离控制;对 VNC、console 的访问做好 RBAC 权限控制。
生产落地的节奏建议:先在测试集群跑一个小规模的试点(比如三五台非核心业务虚拟机),跑通存储、网络、迁移、备份这一条完整链路,再逐步扩容。千万别一上来就把全部存量 VM 一次性迁移过去,否则排障和回退两头烧。
我个人在这一堆实践里最大的感受是:KubeVirt 的成熟度比很多人想象中要高,但它终究不是万能的。它带给你的价值主要在于“用一套 Kubernetes 逻辑统一了虚拟机和容器的管理入口”,而那些底层虚拟化、网络、存储的坑一个都不会少。好消息是,这些坑都有清晰的路径可以排掉,只要把资源配额、存储模型、热迁移策略这些基础设计做扎实,日常运营会顺很多。最后再给一个小建议:KubeVirt 升级前一定先看更新日志,它的 API 版本偶有破坏性调整,升级流程务必先备后升,测试环境过一遍再上生产,切莫直接在生产集群升级。