课程目录(第 30 章 / 共 33 章)
课程/平台化与规模化

计算与虚拟化:KubeVirt、Kamaji 与 vcluster

30 章 / 共 33·22 分钟·进阶平台化虚拟化KubeVirtKamajivcluster

当集群不只要跑容器,还要跑虚拟机、要给每个租户一套独立控制面时,KubeVirt、Kamaji、vcluster(以及 k3k 与 Soperator)分别在 Kubernetes 里再叠一层,这一章讲清它们各自解决什么、代价是什么。

学完这一章,你将能够

  • 说得出「跑虚拟机 / 托管控制面 / 虚拟集群」三种诉求的区别与代价
  • 用 virt-operator 装好 KubeVirt,跑起一台 VirtualMachine 并连上控制台
  • 写出一份 TenantControlPlane 清单,并取到租户集群的 kubeconfig
  • 用 vcluster 创建虚拟集群,验证虚拟集群里的 Pod 出现在宿主命名空间

为什么要在 Kubernetes 里再放一层

前面二十几章里,Kubernetes 一直是「容器编排器」:你给它 Pod,它给你调度、网络、存储、自愈。但当集群变成团队共用的平台,会出现三类它原生不回答的诉求。

有些东西塞不进容器:十年前的遗留系统、只能跑 Windows 的应用、需要自己内核模块或 eBPF 的开发环境,要求「一台带内核的机器」,而不是共享宿主内核的进程。控制面本身太贵:每个团队一套集群很干净,但每套都要 3 台控制面、一个 etcd、一套证书与监控,10 个团队就是 30 台机器空转。每租户一个集群、但只想隔离不想付全价:开发、测试、CI 想要「一人一套集群」,真集群的成本不可接受。这三种诉求对应三个成熟方案,它们都在 Kubernetes 之上叠了一层,但叠的位置完全不同

方案一句话解决什么问题主要代价
KubeVirt把 KVM 虚拟机变成 CRD容器装不下的负载(遗留系统、Windows、要内核的开发环境)资源开销最大,节点要支持硬件虚拟化,存储与迁移有额外要求
Kamaji把租户控制面托管到管理集群多租户、按租户计费、控制面版本独立,且省掉大量控制面机器网络与证书复杂度高,控制面与宿主共享故障域
vcluster在命名空间里跑一个虚拟控制面开发/测试环境隔离、CI 并发、每人一套集群不是强隔离,集群级能力受宿主与版本限制
text
                    宿主(管理)集群
┌──────────────────────────────────────────────────────────────┐
│ KubeVirt:VM 跑在 Pod 里                                      │
│   VirtualMachine(CRD) ─▶ virt-launcher Pod ─▶ qemu-kvm + PVC  │
│ Kamaji:控制面在管理集群,worker 独立                          │
│   TenantControlPlane Pod ◀──隧道── 租户 worker(kubeadm join) │
│ vcluster:虚拟控制面在命名空间内                                │
│   ns/vcluster-demo:虚拟控制面 Pod ──syncer──▶ 宿主 Pod        │
└──────────────────────────────────────────────────────────────┘
三者的共同点只有一句:都在 Kubernetes 之上提供「像集群一样」的东西。区别在于那层边界画在哪——VM 的边界、控制面的边界、命名空间的边界。

KubeVirt:把虚拟机变成 CRD

KubeVirt 给 Kubernetes 补上虚拟化 API,注册两个 CRD:VirtualMachine(简称 VM,期望状态,类似 Deployment,记录这台机器该开机还是关机)与 VirtualMachineInstance(简称 VMI,运行实例,类似 Pod)。

VMI 不会凭空运行,KubeVirt 会为它创建一个 virt-launcher Pod,Pod 里跑 qemu-kvm 进程。于是虚拟机天然继承 Pod 的一切:调度、CNI 网络、PVC、NetworkPolicy、配额、RBAC。这也是理解 KubeVirt 的关键一句:虚拟机在 Kubernetes 眼里就是一个 Pod,只是这个 Pod 里装着另一个内核。代价是节点必须支持硬件虚拟化(KVM),因为设备插件要向调度器上报 devices.kubevirt.io/kvm 资源。

先在候选节点上执行 virt-host-validate qemu(没有该命令就先 apt install libvirt-clients),输出里 QEMU: Checking for hardware virtualization: PASS 才算过关;云主机要选支持嵌套虚拟化的实例类型,BIOS 里要打开 VT-x / AMD-V。

安装方式是先装 operator,再创建 KubeVirt CR 让它拉起组件(版本按最新 release 替换):

bash
export KUBEVIRT_VERSION=v1.8.2
kubectl create namespace kubevirt
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml
kubectl -n kubevirt wait kv kubevirt --for=condition=Available --timeout=10m

虚拟机磁盘要放在 PVC 上还需要 CDI(Containerized Data Importer),它负责导入镜像与自动创建 PVC:

bash
export CDI_VERSION=v1.65.0
kubectl apply -f https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-operator.yaml
kubectl apply -f https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-cr.yaml

怎么确认成功:kubectl -n kubevirt get podsvirt-apivirt-controllervirt-handlervirt-operator 全部 Runningkubectl get crd | grep kubevirt 能看到 virtualmachines.kubevirt.io;此时 kubectl get vmi 返回空列表(而不是报错)就说明 API 已可用。

下面这份清单用 dataVolumeTemplates 自动创建 20Gi 的 rootdisk PVC,再用 cloudInitNoCloud 注入登录方式;runStrategy: Manual 表示创建后不自动开机。

yaml
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: dev-box
spec:
  runStrategy: Manual
  dataVolumeTemplates:
    - metadata:
        name: dev-box-root
      spec:
        source:
          blank: {}
        storage:
          storageClassName: standard
          accessModes: [ReadWriteOnce]
          resources: {requests: {storage: 20Gi}}
  template:
    spec:
      domain:
        resources: {requests: {memory: 2Gi}}
        devices:
          disks:
            - {name: rootdisk, disk: {bus: virtio}}
            - {name: cloudinitdisk, disk: {bus: virtio}}
      volumes:
        - name: rootdisk
          dataVolume:
            name: dev-box-root
        - name: cloudinitdisk
          cloudInitNoCloud:
            userData: |
              #cloud-config
              user: kube
              ssh_authorized_keys:
                - ssh-ed25519 AAAA... 你的公钥

storageClassName 换成集群里真实存在的 StorageClass,accessModesvolumeMode 要匹配后端 CSI 能力,选择思路见生产存储

virtctl 是 KubeVirt 的命令行插件(macOS 上 brew install virtctl,或 kubectl krew install virtctl):

bash
kubectl apply -f dev-box.yaml
kubectl get vm                 # 期望状态:Stopped
virtctl start dev-box          # 开机
kubectl get vmi                # 期望:Running
virtctl console dev-box        # 串口控制台,退出按 Ctrl+]
virtctl stop dev-box           # 关机

想看清「VM 就是 Pod」,去宿主侧看一眼承载它的 Pod:kubectl get pods | grep virt-launchervirt-launcher-dev-box-xxxxxRequests 就是那 2Gi 加上一点开销,它挂在你的 CNI 网络里、绑着那个 PVC。VM 的调度、网络、存储问题,排查方法和 Pod 完全一样,见排障手册

适合 KubeVirt不适合
无法容器化的遗留系统、需要完整 OS 的中间件本来就跑得很好的无状态服务
Windows 工作负载、与物理网络或许可绑定的老软件需要秒级扩缩容的流量型业务
需要自己内核 / 内核模块 / eBPF 的开发与测试环境资源紧张、想把密度压到极限的集群

代价要提前算清:资源开销最大,每台 VM 一套完整操作系统,内存与磁盘底线都比容器高一个数量级;节点要分组,跑 VM 的节点需要 KVM 与设备插件,通常单独打标签、加污点,配合调度入门的 nodeSelector 与 tolerations;存储是硬约束,磁盘 IO 直接决定体验,且实时迁移要求共享存储,本地盘上的 VM 只能停机迁移;技能栈不同,出问题要看 libvirt/QEMU 侧现象,不能只靠 kubectl logs

Kamaji:把控制面搬进管理集群

Kamaji 实现的是 Hosted Control Plane(托管控制面) 模式:租户集群的 apiserver、controller-manager、scheduler 不再跑在租户自己的机器上,而是作为 Pod 跑在管理集群里;租户侧只有 worker 节点,用 kubeadm join 接到自己的控制面上。一套管理集群可以托管很多个租户控制面,控制面资源被复用,租户仍拿到一个符合上游标准、版本可以各自独立的 Kubernetes。

组件作用
kamaji-operator监听 TenantControlPlane,负责租户控制面的创建、升级与证书轮转
TenantControlPlane(CRD)一个租户集群的控制面声明,kamaji.clastix.io/v1alpha1
Datastore(CRD)租户集群的数据存储后端:内置 etcd、外部 etcd、MySQL、PostgreSQL、NATS
konnectivity管理集群到租户 worker 的反向隧道,让 apiserver 能访问 kubelet、logsexec

前置依赖是 cert-manager(签发控制面证书)和一个可用的 StorageClass(给内置 etcd 用):

bash
helm repo add clastix https://clastix.github.io/charts
helm repo update
helm install kamaji clastix/kamaji --namespace kamaji-system --create-namespace
kubectl get pods -n kamaji-system          # 期望 Running
kubectl get crd | grep kamaji              # 期望看到 tenantcontrolplanes / datastores

一份 TenantControlPlane 的关键字段:

yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
  name: dev-team
  namespace: dev-team
spec:
  kubernetes:
    version: v1.33.4
  dataStore: default
  controlPlane:
    deployment:
      replicas: 3
    service:
      serviceType: LoadBalancer
  networkProfile:
    port: 6443
    certSANs:
      - dev-team.example.com
    serviceCidr: 172.23.0.0/16
    podCidr: 172.24.0.0/13
  addons:
    coreDNS: {}
    kubeProxy: {}
    konnectivity: {server: {port: 8132}}

几个字段值得单独说:kubernetes.version 是租户控制面版本,可以与管理集群不同,这是 Kamaji 最有价值的自由度(kubernetes.kubelet.cgroupfs: systemdkubernetes.admissionControllers 也是常用字段);dataStore: default 用内置 etcd,换外部存储要先创建 Datastore 对象再填名字;controlPlane.service.serviceType 决定 apiserver 怎么暴露,LoadBalancer 让集群外能连、ClusterIP 只给管理集群内部用;networkProfile.certSANs 填错这里 worker 一定 join 不上podCidr / serviceCidr 不能与管理集群或机房内网冲突,规划思路同集群网络

租户接入先取控制面 kubeconfig:

bash
kubectl get tcp -n dev-team          # 期望 STATUS=Ready,CONTROL-PLANE ENDPOINT 是租户 apiserver 地址
kubectl get secret dev-team-admin-kubeconfig -n dev-team \
  -o jsonpath='{.data.admin\.conf}' | base64 -d > tenant-kubeconfig.yaml

再生成 worker 的 join 命令,在真实机器上执行;worker 装好 containerd、kubelet、kubeadm 后跑这条命令,节点就会 Ready(装机与规划思路见集群部署)。注意租户集群还要自己装 CNI,否则节点一直是 NotReady;worker 与 apiserver 之间要放通 konnectivity 的端口(默认 8132)。

bash
kubeadm --kubeconfig=tenant-kubeconfig.yaml token create --print-join-command
kubectl --kubeconfig=tenant-kubeconfig.yaml get nodes    # 加入后应看到 Ready 节点
适合 Kamaji代价
多租户 SaaS,按租户计费、需要租户级控制面控制面与宿主共享故障域:管理集群一挂,所有租户控制面都受影响
租户需要独立的 Kubernetes 版本与升级节奏网络与证书复杂:apiserver 暴露、konnectivity 隧道、certSANs 都要规划
想省掉每租户 3 台控制面机器worker 仍是真实机器,节点成本一点没省

一句话总结边界:Kamaji 让「控制面」共享,但「节点」仍然独立。租户之间的隔离强度接近独立集群,但管理集群是新的单点。

vcluster:命名空间里的虚拟集群

vcluster 更轻:在宿主集群的一个命名空间里跑一个虚拟控制面(完整但轻量的 apiserver + 数据存储),再由一个 syncer 把虚拟集群里的 Pod、Service、ConfigMap、Secret 等对象翻译成宿主命名空间里的真实对象。业务容器真正跑在宿主集群的节点上,虚拟集群只是给它一套独立的 API 视图。

这带来一个很好用的性质:虚拟集群里的 kubectl get pods 和宿主集群里的 kubectl get pods -n <ns> 是同一批负载的两种视角。也正因为如此,它不是安全边界——租户共享宿主节点、内核和 CNI,隔离来自命名空间、RBAC 和 NetworkPolicy。

bash
brew install loft-sh/tap/vcluster            # macOS;Linux 下载 releases 里的 vcluster-linux-amd64,tap 名以官方安装文档为准
vcluster create demo --namespace vcluster-demo
vcluster connect demo --namespace vcluster-demo
kubectl get namespaces        # 看到的是虚拟集群自己的命名空间
kubectl get nodes             # 看到的是从宿主同步来的节点

也可以只用 Helm 装(仓库别名可以自取,仓库地址是 https://charts.loft.sh,chart 名是 vcluster/vcluster)。vcluster connect 会把 kubeconfig 写到当前目录并切换 context(具体路径与行为取决于版本,也可以用 --kube-config 指定);用完 vcluster disconnect 回到宿主 context。

关键验证是「虚拟集群的 Pod 会以 Pod 形式出现在宿主命名空间」:

bash
kubectl create deployment web --image=nginx:1.27   # 在虚拟集群里
vcluster disconnect
kubectl get pods -n vcluster-demo                  # 在宿主集群里

宿主命名空间里会有两类 Pod:虚拟控制面自己的(形如 demo-0coredns-...),以及 web 的副本——名字里会带上虚拟命名空间与虚拟集群名(常见形如 web-xxxxx-x-default-x-demo具体命名规则取决于版本)。也就是说,虚拟集群里的负载在宿主侧就是普通 Pod,能被宿主侧的监控、配额与网络策略看见。

适合 vcluster代价
开发/测试环境隔离:一人一套、一支 feature 分支一套不是强隔离:共享宿主内核与节点,不能当安全边界
CI 并发:每个 job 一套集群,用完即删集群级能力受限:CRD、ClusterRole、Node、PV 等对象的同步行为取决于版本与配置
快速验证 Operator / Helm chart,不污染真集群虚拟控制面版本受宿主版本约束,不能任意超前

什么时候该用哪个层级的隔离

只是想给团队分环境、控权限 → 命名空间与 RBAC 就够。需要「删掉就干净」的环境级隔离 → vcluster。需要租户独立节点与独立控制面版本 → Kamaji。需要内核级隔离或跑 Windows → KubeVirt 或干脆独立集群。需要一套「真的 K3s 集群」或 Slurm 作业队列 → 见下面的 k3k 与 Soperator。

动手:用 vcluster 验证同步

KubeVirt 需要支持 KVM 的裸机节点,Kamaji 需要 cert-manager 与可暴露的 apiserver,只有 vcluster 能在本地的 kind / minikube 上完整跑通,所以动手环节选它。

动手练习:创建虚拟集群并观察资源同步

前提:一个可用的宿主集群(kind / minikube 均可)、kubectlhelm

bash
brew install loft-sh/tap/vcluster          # 或按上一节用 curl 安装
vcluster create demo --namespace vcluster-demo
kubectl get pods -n vcluster-demo          # 虚拟控制面的 Pod 在这里

连进虚拟集群创建负载,再回宿主集群找同一批负载:

bash
vcluster connect demo --namespace vcluster-demo
kubectl get namespaces                     # 只有虚拟集群自己的命名空间
kubectl create deployment web --image=nginx:1.27 --replicas=2
kubectl get pods -o wide                   # 期望两个副本都 Running
vcluster disconnect
kubectl get pods -n vcluster-demo -o wide  # 名字带虚拟集群信息,NODE 是真实节点

怎么算成功:宿主侧能看到那两个 nginx Pod,且 NODE 列是宿主节点——这就是「虚拟集群的 Pod 以 Pod 形式出现在宿主命名空间」。用完执行 vcluster delete demo --namespace vcluster-demo 清理,再确认 helm list -A 里没有残留。

怎么选:隔离强度、开销与运维成本

选型先问两个问题:我要隔开的是什么(内核?节点?控制面?还是一堆对象),我能不能接受它的运维代价

维度KubeVirtKamajivcluster
隔离强度最强,VM 级边界(独立内核与 OS)中高,独立控制面 + 独立 worker 节点中低,命名空间级逻辑隔离
资源开销最高,每 VM 一套 OS 加虚拟化开销中,控制面共享、worker 独占最低,复用宿主 worker
运维复杂度高:KVM 节点、存储性能、实时迁移高:apiserver 暴露、证书、konnectivity、worker 接入低:一条命令创建,用完即删
典型场景遗留/Windows 应用、需要内核的研发环境多租户 SaaS、按租户计费、版本独立开发测试隔离、CI 并发、演示培训
不适合高密度无状态服务想省 worker 成本需要强安全隔离

三条经验判断:能用命名空间解决的,不要上虚拟化要强隔离就走真边界,合规、多组织、互不信任的租户应使用独立集群或独立节点池加 KubeVirt;控制面托管不等于节点免费,Kamaji 省的是控制面机器,worker 依旧要买、要装、要修。

另外两条路线:k3k 与 Soperator

前三节讲的都是「在 Kubernetes 里造出更像集群的东西」。还有两类诉求同样要在 K8s 上再叠一层,方向却不同:把整套 Kubernetes 变轻,和把另一套调度器搬进来

k3k:K3s in K3s

k3k(K3s in K3s) 在现有集群里跑真正的 K3s 集群:每个虚拟集群有自己的 apiserver、controller-manager、scheduler 和数据存储,控制面以 Pod 形式运行。它有两种模式,virtual 给每个虚拟集群一套独立 server Pod,shared 让多个虚拟集群共用一个 server、追求密度。定位上它落在 vcluster 与 Kamaji 之间——比 vcluster 更「真集群」(有自己的控制面与独立版本),比 Kamaji 更省事(不用另外准备 worker 机器,agent 也是宿主上的 Pod)。

bash
helm repo add k3k https://rancher.github.io/k3k     # 仓库地址以官方文档为准
helm install k3k k3k/k3k -n k3k-system --create-namespace
kubectl -n k3k-system get pods                      # 期望 controller / webhook Running

kubectl create namespace k3k-dev
kubectl -n k3k-dev apply -f - <<'EOF'
apiVersion: k3k.io/v1beta1
kind: Cluster
metadata:
  name: dev-cluster
spec:
  mode: virtual
  servers: 1
  agents: 2
EOF
kubectl -n k3k-dev get cluster dev-cluster          # 期望 Ready,再取 kubeconfig 连进去

代价要提前算:控制面 Pod 会占宿主资源,每个 virtual 集群至少一个 server Pod 加若干 agent,密度高时宿主调度压力明显;虚拟集群的 K3s 版本由控制器决定,不能随意超前宿主;它依然不是强安全边界——shared 模式共享控制面,virtual 模式共享宿主节点与内核。清理时删掉 Cluster 对象,再确认命名空间里的 PVC 已被回收。

Soperator:把 Slurm 搬进 Kubernetes

HPC 与科学计算的用户手里是 Slurmsbatch / sinfo / squeue),他们不想改工作习惯。Soperator 在 Kubernetes 里运行完整的 Slurm 控制面(slurmctld / slurmd / slurmdbd 等),用 Operator 管理登录节点与计算节点,让用户继续用 Slurm 命令提交作业,同时复用 K8s 的镜像、存储与 GPU 资源。它的前置条件比前几个都硬:

前置为什么需要
Kubernetes 1.30 及以上Operator 依赖的 API 与特性
CNI 支持保留客户端源 IP(Cilium 经过验证)Slurm 节点之间要按来源识别对端
PVC 支持共享文件系统(NFS / CephFS)计算节点要共享作业目录与 spool
已部署 NFD(Node Feature Discovery)按 CPU / GPU 特性给计算节点打标签

代价与现状:上游明确标注「施工中,不要用于生产环境」,现阶段只适合评估与实验;部署后是两套调度器并存,通常 K8s 管基础设施与 GPU 资源、Slurm 管作业排队,排障要在两边来回看日志,动手前先想清楚这条边界划在哪。

参考:reference/k8s-in-action/compute/k3k/README.mdreference/k8s-in-action/compute/soperator/README.md

常见坑与排错

虚拟化类问题的共同特点是症状出现在上层、原因往往在下层(节点、存储、网络)。

现象原因怎么确认怎么办
kubectl get vmi 一直 Scheduling / Pending节点没开硬件虚拟化,或设备插件没上报 devices.kubevirt.io/kvmvirt-host-validate qemukubectl describe vmi <name>Insufficient devices.kubevirt.io/kvm在 BIOS / 宿主开启虚拟化或换支持嵌套虚拟化的实例;开发环境可临时开 useEmulation: true
VMI 卡住或 DataVolume 长时间 PendingStorageClass 不存在、访问模式与 CSI 不匹配、容量不足kubectl get pvckubectl describe pvc <name>kubectl get dvkubectl get sc指定真实存在的 storageClassName,按后端能力改 accessModes / volumeMode
worker kubeadm join 卡住或超时租户 apiserver 没暴露(serviceTypeClusterIP)、LoadBalancer 没可用 IP、防火墙未放行kubectl get tcp -n <ns>CONTROL-PLANE ENDPOINT;worker 上 curl -k https://<endpoint>:6443/healthz改成 LoadBalancer / NodePort 或用 Gateway API 暴露,放行端口
用租户 kubeconfig 报 forbidden / Unauthorized取错 Secret 或 key、证书过期、certSANs 没包含访问地址确认 Secret 名是 <tcp-name>-admin-kubeconfigkubectl --kubeconfig=... config view重新从 admin.conf 导出;把访问地址补进 networkProfile.certSANs 后重建
vcluster 里创建 NodePort,宿主节点 IP 访问不通端口开在宿主节点上,虚拟集群里的「节点」是同步对象;端口与行为取决于版本与 sync 配置宿主侧 kubectl get svc -n <ns> 看有没有同步出来的 Service;虚拟集群 kubectl get nodes 看到的是宿主节点验证连通优先用 vcluster connect 的端口转发;要对外暴露就在宿主侧写 Ingress 或 LoadBalancer
删除虚拟集群后宿主还有残留Helm release 未清理、PVC/PV 保留策略是 Retain、控制面 Pod 没退干净helm list -Akubectl get all,pvc -n <ns>kubectl get pv先用 vcluster delete(或 kubectl delete tcp),再检查 helm release 与 PV,确认备份后手动回收

三个最容易踩的坑

  • 把 vcluster 当安全边界:它共享宿主节点与内核,租户间隔离强度等同于命名空间隔离。要隔互不信任的租户,用独立节点池或 KubeVirt。
  • `certSANs` 与访问地址不一致:Kamaji 证书里没有你的域名或 IP 时,TLS 校验会失败,报错形如 x509: certificate is valid for ... 而不是连接超时,先核对 certSANs
  • 本地盘上的 VM 指望实时迁移:live migration 需要共享存储,否则会失败或退化成停机;规划 KubeVirt 时先定存储方案。

自测题

自测:KubeVirt 为什么要把虚拟机放进一个 Pod 里,而不是直接在节点上起 QEMU?(点击展开答案)

因为这样能免费继承 Kubernetes 已有的全部机制。调度器看到的是 virt-launcher Pod,于是 VM 能被 requests/limits 约束、能被亲和性与污点影响、能被 kubectl describe 观察;网络交给 CNI,存储交给 PVC,隔离交给 Namespace 与 NetworkPolicy,配额交给 ResourceQuota。代价是 Pod 的资源模型是「预留 + 上限」,虚拟机很难像容器那样精细地动态调整;也正因为它是 Pod,VM 无法被 Deployment 复制成 N 份,扩缩容必须走 KubeVirt 自己的机制。

自测:Kamaji 和 vcluster 的隔离边界差在哪?(点击展开答案)

差在被隔开的东西。Kamaji 里,租户拿到真实的独立控制面,以及独立的 worker 节点:Pod 跑在租户自己的机器上,节点级故障、内核版本、CNI 选择都是租户自己的事,隔离边界落在节点上;代价是控制面仍共享管理集群的故障域与网络。vcluster 里,租户拿到的是一个 API 视图:控制面在宿主命名空间里,业务 Pod 跑在宿主共享的节点上,隔离边界落在命名空间与 RBAC 上;它便宜、创建快,但共享内核意味着不能当安全边界,很多集群级资源也受宿主限制。一句话:Kamaji 隔节点,vcluster 隔对象。

自测:什么时候不该用虚拟集群(vcluster)?(点击展开答案)

四种情况要避开。需要强安全隔离时:多组织、互不信任的租户共享宿主节点与内核,虚拟集群给不了你要的边界。需要集群级对象时:CRD、ClusterRole、Node、PV 这类资源的同步行为受版本与配置限制,装一个依赖自定义 CRD 的重型 Operator 可能直接卡住。需要独立版本或独立节点规格时:虚拟控制面版本受宿主约束,节点也共享,隔离不了资源争抢。只是团队内部想分环境时:Namespace + RBAC + ResourceQuota 更简单,多一层虚拟控制面只是多一层排障成本。判断口诀:先问「我要隔开的是内核、节点,还是一堆对象」,再决定上哪一层。

小结

  • 三种方案叠的位置不同:KubeVirt 叠在内核上,Kamaji 叠在控制面上,vcluster 叠在对象上。
  • KubeVirt 用 VirtualMachine / VirtualMachineInstance 描述 VM,实际由 virt-launcher Pod 承载,因此 VM 继承 Pod 的调度、网络与存储;前提是节点支持 KVM,实时迁移需要共享存储。
  • Kamaji 把租户控制面作为 Pod 跑在管理集群里,worker 用 kubeadm join 独立接入;省的是控制面机器,代价是网络、证书与故障域。
  • vcluster 在命名空间里跑虚拟控制面,syncer 把对象同步成宿主资源;创建快、开销低,但不是安全边界。选型先问「我要隔开什么」:能靠命名空间解决的不要上虚拟化,需要强隔离就走真边界。
  • 另外两条路线方向不同:k3k 在 K8s 里跑真的 K3s 集群(比 vcluster 真、比 Kamaji 省事,代价是控制面占宿主资源),Soperator 把 Slurm 搬进 K8s(前置硬、两套调度器并存,且上游标注仍在施工中)。

练习

  1. 团队有 5 个开发小组,每组需要一套独立测试环境,偶尔要装自定义 CRD。用本章决策表判断该选 vcluster 还是各自独立集群,并写出理由。
  2. 如果一台宿主机上跑 20 台 KubeVirt 虚拟机,节点的内存、磁盘 IO 与调度分别会遇到什么问题?对照调度入门生产存储列一份检查清单。
  3. 把本章的 TenantControlPlane 改成「租户版本固定 v1.32.x、控制面 3 副本、用外部 etcd」的形态,写出你需要的 Datastore 关键字段(不用真的部署)。

虚拟化与多租户控制面搭起来之后,平台还缺最后一块拼图:把数据库与中间件也做成可自助申请的 Operator 能力。这些能力的交付与备份回到备份与恢复安全加固,避免平台越做越大、越做越脆。