课程目录(第 30 章 / 共 33 章)
计算与虚拟化:KubeVirt、Kamaji 与 vcluster
当集群不只要跑容器,还要跑虚拟机、要给每个租户一套独立控制面时,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 并发、每人一套集群 | 不是强隔离,集群级能力受宿主与版本限制 |
宿主(管理)集群
┌──────────────────────────────────────────────────────────────┐
│ 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 替换):
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:
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 pods 里 virt-api、virt-controller、virt-handler、virt-operator 全部 Running;kubectl get crd | grep kubevirt 能看到 virtualmachines.kubevirt.io;此时 kubectl get vmi 返回空列表(而不是报错)就说明 API 已可用。
下面这份清单用 dataVolumeTemplates 自动创建 20Gi 的 rootdisk PVC,再用 cloudInitNoCloud 注入登录方式;runStrategy: Manual 表示创建后不自动开机。
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,accessModes 与 volumeMode 要匹配后端 CSI 能力,选择思路见生产存储。
virtctl 是 KubeVirt 的命令行插件(macOS 上 brew install virtctl,或 kubectl krew install virtctl):
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-launcher。virt-launcher-dev-box-xxxxx 的 Requests 就是那 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、logs、exec |
前置依赖是 cert-manager(签发控制面证书)和一个可用的 StorageClass(给内置 etcd 用):
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 的关键字段:
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: systemd 与 kubernetes.admissionControllers 也是常用字段);dataStore: default 用内置 etcd,换外部存储要先创建 Datastore 对象再填名字;controlPlane.service.serviceType 决定 apiserver 怎么暴露,LoadBalancer 让集群外能连、ClusterIP 只给管理集群内部用;networkProfile.certSANs 填错这里 worker 一定 join 不上;podCidr / serviceCidr 不能与管理集群或机房内网冲突,规划思路同集群网络。
租户接入先取控制面 kubeconfig:
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)。
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。
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 形式出现在宿主命名空间」:
kubectl create deployment web --image=nginx:1.27 # 在虚拟集群里
vcluster disconnect
kubectl get pods -n vcluster-demo # 在宿主集群里宿主命名空间里会有两类 Pod:虚拟控制面自己的(形如 demo-0、coredns-...),以及 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 均可)、kubectl、helm。
brew install loft-sh/tap/vcluster # 或按上一节用 curl 安装
vcluster create demo --namespace vcluster-demo
kubectl get pods -n vcluster-demo # 虚拟控制面的 Pod 在这里连进虚拟集群创建负载,再回宿主集群找同一批负载:
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 里没有残留。
怎么选:隔离强度、开销与运维成本
选型先问两个问题:我要隔开的是什么(内核?节点?控制面?还是一堆对象),我能不能接受它的运维代价。
| 维度 | KubeVirt | Kamaji | vcluster |
|---|---|---|---|
| 隔离强度 | 最强,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)。
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 与科学计算的用户手里是 Slurm(sbatch / 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.md、reference/k8s-in-action/compute/soperator/README.md
常见坑与排错
虚拟化类问题的共同特点是症状出现在上层、原因往往在下层(节点、存储、网络)。
| 现象 | 原因 | 怎么确认 | 怎么办 |
|---|---|---|---|
kubectl get vmi 一直 Scheduling / Pending | 节点没开硬件虚拟化,或设备插件没上报 devices.kubevirt.io/kvm | virt-host-validate qemu;kubectl describe vmi <name> 报 Insufficient devices.kubevirt.io/kvm | 在 BIOS / 宿主开启虚拟化或换支持嵌套虚拟化的实例;开发环境可临时开 useEmulation: true |
VMI 卡住或 DataVolume 长时间 Pending | StorageClass 不存在、访问模式与 CSI 不匹配、容量不足 | kubectl get pvc、kubectl describe pvc <name>、kubectl get dv、kubectl get sc | 指定真实存在的 storageClassName,按后端能力改 accessModes / volumeMode |
worker kubeadm join 卡住或超时 | 租户 apiserver 没暴露(serviceType 是 ClusterIP)、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-kubeconfig;kubectl --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 -A、kubectl 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-launcherPod 承载,因此 VM 继承 Pod 的调度、网络与存储;前提是节点支持 KVM,实时迁移需要共享存储。 - Kamaji 把租户控制面作为 Pod 跑在管理集群里,worker 用
kubeadm join独立接入;省的是控制面机器,代价是网络、证书与故障域。 - vcluster 在命名空间里跑虚拟控制面,syncer 把对象同步成宿主资源;创建快、开销低,但不是安全边界。选型先问「我要隔开什么」:能靠命名空间解决的不要上虚拟化,需要强隔离就走真边界。
- 另外两条路线方向不同:k3k 在 K8s 里跑真的 K3s 集群(比 vcluster 真、比 Kamaji 省事,代价是控制面占宿主资源),Soperator 把 Slurm 搬进 K8s(前置硬、两套调度器并存,且上游标注仍在施工中)。
练习
- 团队有 5 个开发小组,每组需要一套独立测试环境,偶尔要装自定义 CRD。用本章决策表判断该选 vcluster 还是各自独立集群,并写出理由。
- 如果一台宿主机上跑 20 台 KubeVirt 虚拟机,节点的内存、磁盘 IO 与调度分别会遇到什么问题?对照调度入门与生产存储列一份检查清单。
- 把本章的
TenantControlPlane改成「租户版本固定 v1.32.x、控制面 3 副本、用外部 etcd」的形态,写出你需要的Datastore关键字段(不用真的部署)。
虚拟化与多租户控制面搭起来之后,平台还缺最后一块拼图:把数据库与中间件也做成可自助申请的 Operator 能力。这些能力的交付与备份回到备份与恢复与安全加固,避免平台越做越大、越做越脆。