KubeVirt 实战:用 Kubernetes 统一编排容器与虚拟机

KubeVirt 让你在已有的 Kubernetes 集群上直接运行 虚拟机,把容器与 VM 放进同一套调度、网络与存储体系里统一编排。本文从架构到落地,手把手演示如何用 KubeVirt 把传统负载平滑搬上 K8s,而不必另起一套 OpenStack。

一、KubeVirt 是什么:架构与核心概念

KubeVirt 是 CNCF 的沙箱(已毕业为孵化)项目,本质是一组运行在 K8s 里的自定义控制器与 CRD。它利用 K8s 的声明式 API 扩展机制,把「虚拟机」变成和 Pod 平级的头等公民。底层虚拟机仍由每个节点上的 libvirt + QEMU/KVM 承载,但调度、生命周期、网络、存储全部交给 K8s 控制面统一管理。

理解三个核心对象,就掌握了 KubeVirt 的骨架:

  • VirtualMachineInstance(VMI):等价于「运行中的一台虚拟机」,类似 Pod,是真正被调度到节点上跑起来的实体。
  • VirtualMachine(VM):VMI 的「模板 + 状态机」,定义这台机器的规格,并维护开机/关机状态,类似 Deployment 与 Pod 的关系。
  • VirtualMachineInstanceReplicaSet(VMIRS):虚拟机的多副本集合,适合无状态、可水平扩展的 VM 负载。

控制面关键组件是 virt-controller(负责把 VM 调成 VMI)、virt-api(暴露 KubeVirt 的 API)、以及每个节点上的 virt-handler(真正调用 libvirt 起 QEMU)。K8s 自身的 etcd 与调度器依然是大脑,想深入底层存储与高可用可参考 etcd 分布式键值存储实战。

一个反直觉但重要的细节:每台 VMI 在底层都会对应一个同名 Pod(由 virt-launcher 承载)。调度器其实是在给这个 Pod 安排节点,Pod 里再起 QEMU 进程。因此 K8s 的全部调度能力——节点亲和、污点容忍、资源配额、优先级抢占——对虚拟机同样生效,你不用学一套新调度语法。

二、环境准备与安装

KubeVirt 要求节点支持硬件虚拟化(/dev/kvm 存在)。云上 ECS 通常需在实例规格里开启「嵌套虚拟化」或选用带 VT-x 的机型;轻量边缘环境可参考 k3s 轻量 Kubernetes 实战 搭建低资源集群。安装只需两条 kubectl 命令:

# 1) 部署 KubeVirt Operator 与 CR(以 v1.2 为例)
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v1.2.0/kubevirt-operator.yaml
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v1.2.0/kubevirt-cr.yaml

# 2) 等待控制面就绪
kubectl -n kubevirt wait kv kubevirt --for condition=Available --timeout=300s

# 3) 安装 virtctl 命令行(VM 控制台/热迁移依赖它)
export VERSION=$(kubectl get kubevirt.kubevirt.io/kubevirt -n kubevirt -o=jsonpath='{.spec.installedVersion}')
echo "KubeVirt 版本: $VERSION"

三、运行你的第一台虚拟机

下面定义一个最简 VM:2 核 4G、从容器镜像中的云初始化磁盘启动。提交后 KubeVirt 会自动派生出 VMI 并调度到节点。

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: test-vm
spec:
  runStrategy: Always          # Always/RerunOnFailure/Manual
  template:
    metadata:
      labels: { kubevirt.io/vm: test-vm }
    spec:
      domain:
        cpu: { cores: 2 }
        resources:
          requests: { memory: 4Gi }
        devices:
          disks:
          - name: rootdisk
            disk: { bus: virtio }
      volumes:
      - name: rootdisk
        containerDisk:
          image: quay.io/containerdisks/fedora:39

创建并进入控制台:

kubectl apply -f test-vm.yaml
kubectl get vmi                                  # 查看虚拟机实例运行状态
virtctl console test-vm                          # 进入串行控制台
virtctl ssh fedora@test-vm                       # 通过 virtctl 内置 SSH 登录

三(续)、用 cloud-init 注入初始化配置

裸镜像启动后还需装软件、写用户,生产环境应交给 cloud-init。在 VM 模板里挂一个 cloudInitNoCloud 卷,即可注入主机名、SSH 公钥与开机脚本:

volumes:
- name: cloudinit
  cloudInitNoCloud:
    userData: |
      #cloud-config
      hostname: test-vm
      users:
      - name: fedora
        ssh_authorized_keys:
        - ssh-rsa AAAAB3...your-key
      runcmd:
      - dnf install -y nginx
      - systemctl enable --now nginx

这样 VM 一开机就已经带好账号与基础服务,配合 GitHub Actions CI/CD 流水线 还能把 VM 模板的渲染也纳入自动化。

四、镜像与存储:用 DataVolume 导入磁盘

生产环境很少用 containerDisk(它每次启动都重新拉镜像、不可持久化)。更规范的做法是用 DataVolume(DV) + CDI 把 qcow2/raw 镜像导入 PVC,实现持久化启动盘。Kubernetes Pod/Deployment/Service 基础 里的 PVC 概念在这里完全复用。

除了从容器镜像仓库拉取,CDI 还支持从外部 http/https 直链或对象存储(S3)导入现成 qcow2,也支持基于已有 PVC clone 快速派生多台同构机器:

# 方式 A:从公网镜像直链导入
spec:
  source:
    http: { url: "https://cloud.centos.org/centos/9-stream/x86_64/images/..." }
---
# 方式 B:克隆已有 PVC 派生新盘
spec:
  source:
    pvc: { name: golden-image, namespace: default }
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: fedora-dv
spec:
  source:
    registry:
      url: docker://quay.io/containerdisks/fedora:39
  pvc:
    accessModes: [ ReadWriteOnce ]
    resources:
      requests: { storage: 10Gi }
---
# 在 VM 的 volumes 里改用 dataVolume 引用
volumes:
- name: rootdisk
  dataVolume: { name: fedora-dv }

五、网络与对外访问

默认 VMI 接入 K8s 的 Pod 网络,拥有独立 Pod IP,可直接被集群内 Service 转发。若要从外部 SSH 或暴露 Web 服务,给 VMI 套一个 Service 即可,原理与 Nginx 反向代理与负载均衡 中暴露后端一致。需要对外域名可配合 ExternalDNS 自动管理 DNS 解析 实现 Ingress 域名自动注册。

当一台 VM 需要多张网卡(例如管理网与业务网隔离),可借助 Multus 挂第二个网络接口;临时调试则直接用 virtctl port-forward 把本地端口映射到 VMI 内部,免去每次改 Service:

# 本地 8080 转发到 VM 的 80 端口
virtctl port-forward vm/test-vm 8080:80

# 多网卡:在 spec.domain.devices.interfaces 增加 bridge 类型接口
interfaces:
- name: net1
  bridge: {}
networks:
- name: net1
  multus: { networkName: business-net }
apiVersion: v1
kind: Service
metadata:
  name: test-vm-ssh
spec:
  selector: { kubevirt.io/vm: test-vm }
  ports:
  - protocol: TCP
    port: 22
    targetPort: 22

六、生命周期运维:快照与热迁移

KubeVirt 把 VM 的运维动作也声明式化。快照用 VirtualMachineSnapshot,跨节点搬家用 Live Migration(内存热迁移,业务几乎无感)。

# 快照
kubectl apply -f - <

六(续)、节点亲和与资源规划

VM 对节点资源更「挑剔」:它独占分配的内存,错误的调度会让节点内存超卖。建议通过节点亲和把虚拟机集中到带 KVM 的专用节点池,并用 nodeSelector 隔离;对延迟敏感的业务可开启大页内存(HugePages)降低 TLB 缺失:

spec:
  template:
    spec:
      nodeSelector: { node-role.kubernetes.io/vm: "true" }
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: feature.node.kubernetes.io/virtualization-kvm
                operator: In
                values: ["true"]
      domain:
        memory:
          hugepages: { pageSize: 2Mi }

资源规划不当会引发节点压力与驱逐,相关排障思路可对照 K8s 节点 NotReady 排障 一文。

七、融入 K8s 可观测与治理生态

KubeVirt 的精髓在于「一套底座管两种负载」。VM 的 CPU、内存、网络指标由节点导出器统一采集,直接接入 Prometheus + Grafana 监控告警;安全基线可用 Kyverno 策略即代码 或 OPA 统一策略授权 约束 VM 规格(例如禁止特权、限制镜像来源);整套 VM 定义还能纳入 ArgoCD 的 GitOps 流水线,实现虚拟机配置的版本化与一键回滚。

八、典型排障清单

现象常见根因处理命令
VMI 一直 Pending节点无 /dev/kvm 或资源不足kubectl describe vmi 看 Events
启动报软虚拟化嵌套虚拟化未开主机开启 VT-x 后重启
DataVolume 卡 ImportCDI 存储类不支持kubectl get dv -o yaml 查 phase
控制台连不上virtctl 未装 / 串口未配确认 Virtio 串行设备

九、适用边界与选型建议

适合 KubeVirt不适合 / 需谨慎
遗留单体应用难容器化超大规模 VM(数千台需评估调度压力)
同一团队既要容器又要 VM强隔离多租户(考虑独立集群)
VM 需 GitOps 版本管理对启动延迟极度敏感(冷启动慢于 Pod)

结语

KubeVirt 的价值不是「用 K8s 跑虚拟机」这一个炫技动作,而是把容器与虚拟机的运维范式彻底统一:同一套调度、网络、存储、监控与策略体系,覆盖从云原生新应用到历史遗留系统的全部负载。对正在推进 Kubernetes 化的团队,它是平滑迁移、降低运维分裂成本的关键拼图。

上一篇 Vitest 实战:Vite 原生前端单元测试框架
下一篇 CompletableFuture 异步编排实战
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]