状态:草稿 (2026-11-14),实测。 Dynamo v1.6.2(Chart
dynamo-platform-1.6.2,运营商镜像kubernetes-operator:1.6.2)于 2026-11-13 直接安装到 Kubernetes v1.34、Helm v3.18 时所写的文章。资源数量从helm template的输出中统计,错误提示为实际输出原文。全局方式仅作绘制查看,并未安装。全局方式的 Pod Webhook 对其他命名空间的 Pod 创建造成的延迟未进行实测。 编写规则:以实测数据为基础,未能测量的数据标记为「未实测」,无法确认的数据标记为「未确认」。
目录
- 1. 仅使用 vLLM 时没有的东西
- 2. 全局方式:所有 Pod 创建都会经过 Webhook
- 3. 无法通过参数收窄 Webhook
- 4. 单独使用限制方式时缺少的三样东西
- 5. 分离命名空间后,运营商崩溃了 161 次
- 6. 要引入隔离环境的内容
- 7. 总结:安装前需确定的事项
- 参考
1. 仅使用 vLLM 时没有的东西
vLLM 只要有 Deployment 和 Service 就能启动。它不会创建任何集群范围的资源。
Dynamo 则不同。它通过名为 DynamoGraphDeployment 的单个自定义资源来编写工作线程和前端,运营商读取该资源后会创建 Deployment 和 Service。为此,必须先在集群中放置 CRD、运营商、准入 Webhook 以及相应的权限。如果多个团队共用一个集群,这一部分将成为比引擎性能更优先审查的对象。
以下从 Chart 的默认值开始写起。在 1.6.2 中,etcd、NATS、Grove、KAI 调度器和 snapshot 都是关闭的。因为默认的发现机制使用 Kubernetes API,请求平面使用 TCP,KV 事件使用 ZMQ。虽然仓库中的多篇文档仍然将 NATS 和 etcd 列为必需项,但在没有这四项的情况下进行安装,也能顺利跑完整篇系列的第三部分测试。
2. 全局方式:所有 Pod 创建都会经过 Webhook
默认安装采用单个运营商监控整个集群的全局方式。在安装前,通过 helm template 进行渲染并统计了所创建的资源(关闭了检查点功能的值)。
| 种类 | 全局方式 | 命名空间限制方式 |
|---|---|---|
| ClusterRole | 12 | 7 |
| ClusterRoleBinding | 10 | 7 |
| Role | 1 | 6 |
| RoleBinding | 1 | 6 |
| ServiceAccount | 2 | 5 |
| Webhook(变异 4 + 校验 4) | 8,无选择器 | 8,目标命名空间选择器 |
| PodMonitor | 5 | 5 |
全局方式的八个 Webhook 中既没有 namespaceSelector 也没有 objectSelector 。其中七个只监控 Dynamo 的自定义资源,因此对其他团队没有影响。剩下的一个才是问题所在。
mpodcheckpointrestore.kb.io resources: pods operations: CREATE
failurePolicy: Ignore timeoutSeconds: 10
集群中所有 Pod 的创建都会经过这个 Webhook。 即使关闭检查点功能(checkpoint.enabled: false),它也会被注册。由于 failurePolicy 是 Ignore ,所以即使运营商宕机,也不会阻塞 Pod 的创建。不过在这种情况下,API 服务器等待 Webhook 响应的时间可能会附加到每个 Pod 的创建过程中。由于未安装全局方式,该延迟实际上是多少并未测得(未实测)。
我们不想把用于测试的运营商塞进其他团队的 Pod 创建路径中。因此,我们试图将 Webhook 缩减到测试命名空间中。
3. 无法通过参数收窄 Webhook
Chart 中有一个名为 webhook.namespaceSelector 的参数。如果在其中写入选择器进行安装,Helm 会拒绝。
VALIDATION ERROR: webhook.namespaceSelector must be empty. Helm manages admission
scope: cluster-wide webhooks cover every namespace and namespace-restricted
webhooks cover only their target namespace.
这意味着 Webhook 的范围由安装方式决定,用户无法单独选择。剩下的唯一途径就是命名空间限制方式(namespaceRestriction.enabled: true)。用这种方式渲染时,所有八个 Webhook 都会附带以下选择器:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: <대상 네임스페이스>
限制方式不能与 CRD 自动更新一起使用。如果同时开启这两项,同样会被拒绝。
VALIDATION ERROR: namespaceRestriction.enabled=true is incompatible with
upgradeCRD=true. Set upgradeCRD=false so the namespaced release does not install
or update cluster-wide CRDs.
并且,当运营商以限制方式启动时,日志中会打印一条大横幅:「DEVELOPMENT AND TESTING ONLY: Namespace-restricted mode is not supported for production.」Chart 的校验文本也将限制方式解释为「与全局运营商并排放置以供开发和测试使用」。 生产环境中支持的唯一途径是全局方式,如果选择该方式,就必须接受第 2 节中的 Pod Webhook。
4. 单独使用限制方式时缺少的三样东西
在没有全局运营商的情况下仅安装限制方式时,有三样东西是空的。因为这些原本是全局运营商做的事情。
缺少 CRD。 upgradeCRD: false 由于需要 deploy/operator/config/crd/bases/ ,必须手动添加 CRD。我们从仓库的 kubectl apply 中获取了六个。最大的文件为 3,038,832 字节。客户端侧的 kubectl apply --server-side 会将完整应用内容重新包含在 annotation 中,因此无法超过此大小。我们通过
进行了添加。 转换 Webhook 指向的服务名称不同。 dynamo-system CRD 原本的转换 Webhook 指向 dynamo-operator-webhook-service 命名空间中的 <릴리스 전체 이름>-webhook-service 。Chart 创建的服务名称为 dynamo-platform ,如果将发布名称设为 dynamo-platform-dynamo-operator-webhook-service ,则会变成
。在添加 CRD 之前,必须将此名称和命名空间修改为实际值。 未包含转换 Webhook 的 CA。 caBundle Webhook 证书由运营商自行生成并放入 Secret 中,但将该 CA 放入 CRD 的 ca.crt 中的权限仅归全局运营商所有。安装后,从 Secret 中取出dynamocomponentdeployments, dynamographdeploymentrequests, dynamographdeployments, dynamographdeploymentscalingadapters)中直接进行了输入。
直到填满这三者之后, v1beta1 才能创建资源。
5. 划分命名空间后,Operator 崩溃了 161 次
我们将 Operator 放在 dynamo-system 中,而将工作负载放在另一个命名空间中。这是一个将具有权限的进程与模型服务器分离开来的常见部署。在使用限制模式的部署中,Operator 无法存活太久。
failed to list *v1.Secret: secrets is forbidden: User "system:serviceaccount:
dynamo-system:dynamo-platform-dynamo-operator-controller-manager" cannot list
resource "secrets" in API group "" in the namespace "dynamo-system"
...
problem running manager {"error": "failed to wait for cert-rotator caches to sync
kind source: *v1.Secret: timed out waiting for cache to be synced ..."}
Operator 内部的证书轮换器会监视其自身命名空间的 Secret。为了进行监视,它需要列表查询(list)和监视(watch)权限。Chart 在发布命名空间中创建的 Role 如下所示:
rules:
- apiGroups: [""]
resources: [secrets]
resourceNames: [webhook-server-cert]
verbs: [get, list, watch, update, patch]
resourceNames 以 命名的规则不允许对未指定名称的列表查询进行放行。 这是 Kubernetes RBAC 的一个已知特性。因此,如果缓存同步未完成且超时,进程将自行终止。
在全局模式下,ClusterRole 以及在发布命名空间与目标命名空间相同的限制模式下,目标命名空间的 Role 允许进行 Secret 列表查询,因此这个问题被掩盖了。只有在两个命名空间不同时才会暴露出来。
实际看到的情况如下。
- 容器启动约 35 秒后,控制器开始工作。进程在大约 2 分 30 秒时终止(观察到的一个周期:15:17:27 开始,15:19:58 结束)。
- 等待 Kubernetes 重新启动需要 5 分钟(CrashLoopBackOff 上限)。
- 在 20 小时内重启了 161 次。
- 在存活的大约 2 分钟内,Webhook 和控制器运行正常。在此期间输入的
DynamoGraphDeployment在 25 秒内变成了 4 个 Deployment 和 4 个 Service。 - 在处于死亡状态的 5 分钟内,Dynamo 资源的创建和修改将被拒绝。因为验证 Webhook 的
failurePolicy是Fail。在本文中,我们等待其存活时进行了应用,并未在它处于死亡状态时故意尝试(未实际测量)。 - 已经运行的 Worker 和前端没有受到影响。 第 3 部分中的部分测量是在 Operator 处于死亡状态时运行的,并且没有出现错误。因为 Worker 注册是通过 Worker 直接写入的
DynamoWorkerMetadata资源完成的,不经过 Operator。
由于原因是权限不足,解决方法是在发布命名空间中添加另一个 Role,允许对 Secret 进行 get·list·watch 。或者,将 Operator 和工作负载放在同一个命名空间中。由于本文中的测量是通过等待 Operator 存活时应用而结束的,因此并未通过应用这两种方法来进行确认(未实际测量)。
在 Running 和 CrashLoopBackOff 之间切换的 Pod 乍一看可能很正常。安装 Operator 后,必须以几分钟为间隔检查两次重启次数。
6. 引入离线/封闭环境
从外部获取的东西有 4 个。
| 内容 | 名称 | 大小 | 是否必需 |
|---|---|---|---|
| 运行时镜像 | nvcr.io/nvidia/ai-dynamo/vllm-runtime:1.6.2 |
10,203,035,478 字节 | 是。Worker 和前端共用 |
| Operator 镜像 | nvcr.io/nvidia/ai-dynamo/kubernetes-operator:1.6.2 |
94,673,132 字节 | 是 |
| Chart | dynamo-platform-1.6.2.tgz |
516,410 字节 | 是 |
| 模型 | Qwen/Qwen3-0.6B,固定版本 | 1,519,209,243 字节 | 是。每个 Worker 节点都需要 |
| 前端专用镜像 | dynamo-frontend:1.6.2 |
约 3.76 GiB | 否。用运行时镜像代替 |
| NATS, etcd, Grove, KAI | 否。默认关闭 |
运行时镜像中包含 vLLM 0.31.0、 ai-dynamo 1.6.2、PyTorch 2.13.0+cu130、NIXL 1.4.1。负载工具 AIPerf 不在运行时镜像中,仅在前端专用镜像中。
在引入过程中遇到了三个问题。
10GB 的镜像会阻塞同一节点上的其他部署。 每个节点拉取运行时镜像大约花费了 11 分钟。在容器运行时被配置为一次只能拉取一个镜像的节点上,其他镜像在此期间需要排队。实际上,本应在同一节点上启动的另一个部署在等待了 12 分钟 11 秒后超时失败。对于体积庞大的镜像,应在没有部署任务的时间段提前拉取。
如果通过哈希值拉取版本,则在离线状态下找不到模型。 snapshot_download(..., revision="<커밋 해시>") 如果通过获取,缓存中将不会生成 refs/main 。在这种状态下,如果给定 HF_HUB_OFFLINE=1 作为 --model Qwen/Qwen3-0.6B ,将无法以快照形式解压仓库名称。我们在获取之后,将该哈希值写入了 refs/main 文件中。不建议不固定版本的做法。如果只写名称,下个月可能会指向不同的文件。
模型目录可以以只读方式挂载。 Worker 尝试写入一个缓存索引文件时会留下 Permission denied 警告,但加载可以正常完成。
7. 总结:安装前需确定的事项
| 问题 | 全局模式 | 命名空间限制模式 |
|---|---|---|
| 生产环境是否支持 | 是 | 否(日志中记录为仅限开发和测试使用) |
| 是否会影响其他团队的 Pod 创建 | 是。Pod CREATE 变更 Webhook, Ignore, 10秒 |
否 |
| 谁来添加 CRD | Operator | 人工手动 |
| 转换 Webhook 地址和 CA | Operator 进行配置 | 人工手动 |
| 分离 Operator 与工作负载的命名空间 | 可能 | 需要额外添加一个 Role |
| 集群范围权限 | 12 个 ClusterRole | 7 个 ClusterRole + 6 个 Role |
在尝试添加时,限制方式不会影响其他团队,因此比较安全,但需要较多人工操作。在投入生产环境时属于全局方式,在此之前,必须与集群管理员达成共识,即 Pod Webhook 的范围是整个集群。无论哪种方式,我们都确认过,即使 Operator 宕机,已经运行的服务仍将继续运行。
参考
- Dynamo 仓库 v1.6.2:
deploy/helm/charts/platform/components/operator/templates/的_validation.tpl,webhook-rbac.yaml,manager-rbac.yaml,deploy/operator/cmd/main.go. - Kubernetes 文档,Using RBAC Authorization 的
resourceNames说明。 - 安装所使用的 Values 文件和命令位于仓库的
docs/experiments/2026-11-13-dynamo-adoption/中。