KubeVirt 如何自动同步 VM Label 到 Launcher Pod
有些调度、监控或运维组件只能读取 Pod Label,但 KubeVirt 中业务侧维护的是 VirtualMachine。如果 VM 已经运行,只修改 spec.template.metadata.labels,怎样让 Label 继续更新到当前的 VirtualMachineInstance 和 virt-launcher Pod?
KubeVirt 当前 main 提供了 virt-controller 参数 --additional-launcher-labels-sync。它可以按完整 key 或前缀选择 Label,并沿着下面的方向单向同步:
|
|
我在独立的 Kubernetes 集群中用两台 Label 不同的 VM 做了在线修改和删除测试。结果是 VMI 与现有 Pod 都能更新,并且 Pod UID 不变,不需要重建 VM Pod。
本文验证的是 KubeVirt
main的 commit3e83f1130a。不要据此判断某个旧版本已经包含这项能力,使用前应检查目标版本源码或virt-controller --help。
配置要同步的 Label
通过 KubeVirt CR 给 virt-controller 增加参数:
|
|
也可以直接 patch 现有 KubeVirt CR:
|
|
如果只允许固定的几个 key,可以使用逗号分隔:
|
|
如果每台 VM 的 key 也不同,使用末尾带 * 的前缀即可:
|
|
例如两台 VM 可以分别使用:
|
|
它们不需要拥有相同的 Label key。控制器匹配的是前缀,而不是预先枚举每台 VM 的完整 Label 集合。Kubernetes Label 的 prefix/name 格式可以参考 Labels and Selectors。
源码中的参数说明同时给出了精确语义:支持完整 key、支持末尾 * 的前缀,并且同步方向是 VM template 到 VMI,再到 launcher Pod。1
单独配置 "*" 不会同步全部 Label。两段同步实现都会拒绝空前缀,避免一次配置错误把所有 Label 复制到 Pod。23
修改 VM 的 template,而不是 VM metadata
需要修改的是:
|
|
不是:
|
|
下面的命令会在线修改 VM A 的 owner,并增加一个新 Label:
|
|
可以同时查看 VM、VMI 和 Pod:
|
|
确认参数已经进入 virt-controller 时,需要一起检查 container 的 command 和 args:
|
|
控制器如何完成同步
这不是 VM 重启流程,而是两段独立的 reconcile:
flowchart LR
VM["VirtualMachine<br/>spec.template.labels"]
VC1["VM Controller<br/>筛选 exact key 或 prefix"]
VMI["VirtualMachineInstance<br/>metadata.labels"]
VC2["VMI Controller<br/>筛选同一组 key"]
Pod["现有 virt-launcher Pod<br/>metadata.labels"]
VM --> VC1 -->|"Patch VMI"| VMI
VMI --> VC2 -->|"Patch Pod"| Pod
第一段位于 VM Controller 的 syncDynamicAnnotationsAndLabelsToVMI。它会克隆 VMI 当前的 Label,再按配置逐个处理:2
- 完整 key 直接比较 VM template 与 VMI 中的值。
- 前缀模式会遍历 VM 和 VMI 中匹配前缀的 key。
- VM 中存在的 key 会新增或覆盖到 VMI。
- VMI 中存在、但 VM template 已删除的 key 会从 VMI 删除。
- 有变化时使用带
test的 JSON Patch 更新 VMI。
第二段位于 VMI Controller 的 syncDynamicAnnotationsAndLabelsToPod,使用相同规则把 VMI Label patch 到现有 launcher Pod。3
这也解释了为什么删除能够继续传播:前缀匹配不只遍历源对象,也遍历目标对象。目标中多出来的匹配 key 会被识别并删除。
隔离集群中的验证
测试环境如下:
| 项目 | 值 |
|---|---|
| Kubernetes | v1.36.3 |
| KubeVirt | main@3e83f1130a |
| Provider | kubevirtci k8s-1.36 |
| 同步前缀 | codex.test/* |
我使用 make cluster-up 创建独立集群,再通过 make cluster-sync 部署当前源码。没有使用或修改已有 Kubernetes 集群。
测试创建了两台 VM:
| VM | 初始 Label | 在线更新 |
|---|---|---|
| A | owner-a=team-a-v1 |
owner-a=team-a-v2,新增 dynamic-a=enabled |
| B | owner-b=team-b-v1 |
owner-b=team-b-v2,新增 dynamic-b=blue |
VM A 更新后,四个观测值一致:
|
|
更新前后的 Pod UID 都是:
|
|
随后从 VM template 删除 dynamic-a,VMI 和 Pod 上的 dynamic-a 都被删除,Pod UID 仍未变化。
VM B 使用完全不同的 owner-b 和 dynamic-b。更新后的 VM template、VMI 和 Pod 都是:
|
|
Pod 上没有出现 VM A 的 owner-a,更新前后的 Pod UID 都是:
|
|
隔离集群只有一个节点,同时运行两台 VMI 时内存不足,因此测试按 A、B 顺序执行。这个限制影响并发运行数量,不影响每条同步链路的验证。
测试结束后删除了测试 Namespace,并执行 KUBEVIRT_PROVIDER=k8s-1.36 make cluster-down。节点容器和测试网络均已删除。
使用时需要注意的边界
- 同步是单向的。 修改 VMI 或 Pod 不会回写 VM template,后续 reconcile 还可能被 template 覆盖。
- 只同步配置命中的 key。 其他 VM/VMI/Pod Label 不会因为这个参数被统一复制。
- 不要使用裸
*。 当前实现会忽略它;应使用自己的域名前缀,例如ops.example.com/*。 - Label 更新可能改变其他控制器行为。 如果 Service、NetworkPolicy、监控或调度逻辑依赖这些 Pod Label,应先在测试环境验证 selector 的影响。
- 关闭参数不等于清理旧 Label。 从实现看,控制器只遍历当前配置的 key 或前缀。移除配置后,它不再知道哪些旧 Label 应删除;需要先从 VM template 删除并等待同步完成,再关闭参数。
- 先确认版本。 本文只证明 commit
3e83f1130a上的实际行为,没有验证某个正式 release 的首次支持版本。
对于「每台 VM 的 Label 都不同」这个场景,关键不是把所有 key 写进 controller 参数,而是先规划一个专用前缀,再让每台 VM 在这个前缀下自由定义自己的 key。这样既保留了 VM 之间的差异,也把允许传播到 launcher Pod 的范围限制在可审查的边界内。
-
KubeVirt
application.go中的additional-launcher-labels-sync参数定义。 ↩︎ -
KubeVirt VM Controller 的
syncDynamicAnnotationsAndLabelsToVMI。 ↩︎ ↩︎ -
KubeVirt VMI Controller 的
syncDynamicAnnotationsAndLabelsToPod。 ↩︎ ↩︎