Capabilities 从何而来,crds 为何在发布之外
一句话总结
.Capabilities 的值,不同命令的来源不同,而 crds/ 既不是模板也不是 release 清单,而是有着独立生命周期的东西。
为什么需要它
一旦开始用一个 Chart 支持多个集群,很快就会有这样的代码进来。
{{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }}
apiVersion: policy/v1
{{- else }}
apiVersion: policy/v1beta1
{{- end }}
这是个分支:旧集群里没有 policy/v1,所以使用 beta 版本。看起来运转得很好,但如果在 CI 中用 helm template 做渲染检查,出来的永远只有 else 一侧。而实际部署时出来的却是 if 一侧。同一个 Chart、同样的值,结果却不同。
原因是能力信息的来源不同。
| 命令 | 是否向集群查询 | KubeVersion |
|---|---|---|
helm template |
否 | Helm 内置的默认值 |
helm template --validate |
是 | 真实集群 |
helm install --dry-run |
是 | 真实集群 |
helm install --dry-run=server |
是 | 真实集群 |
helm install |
是 | 真实集群 |
只有 helm template 是离线的。而且它的默认能力列表非常小,连真实集群里理所当然存在的 networking.k8s.io/v1/Ingress 也显示为没有。不知道这一点,就会看着“渲染一看,Ingress 分支没走”,开始去改 Chart。
想在没有集群的情况下测试分支,就让 Helm 说谎。--kube-version 1.21.0 追加版本,--api-versions "demo.labhub.io/v1/Widget" 追加 API 列表。重要的是“追加”——它不是替换默认列表,而是在其上增加。
crds 目录有五点不同
CRD 有先有鸡还是先有蛋的问题。如果同一个 Chart 想同时创建由该 CRD 定义的自定义资源,CRD 就必须先进入集群。Helm 用名为 crds/ 的专用目录来解决这个问题。这个目录与普通模板有五点不同。
- 不经过模板引擎。 即使写了花括号,也只是普通字符。所以不能按值对 CRD 做条件分支。
- 默认的渲染结果中不会出现。 必须加上
helm template --include-crds才会出现。 - 安装时比其他一切都先进入。 所以同一个 Chart 的模板可以同时创建使用该 CRD 的对象,即使在第一次安装时,
.Capabilities.APIVersions.Has也会为真。 - 升级时不会动它。 即使修改了 Chart 的
crds/并运行helm upgrade,集群里的 CRD 也原封不动。官方文档把这一点明确写为限制,并说明 CRD 的更新要由人手动完成。 - 删除 release 后仍然保留。 删除 CRD 会让用它创建的所有自定义资源一起消失,所以 Helm 把这个决定交给人。
用 helm get manifest 取出 release 清单来看,里面没有 CRD。意思是 release 并不拥有 CRD,上面的第 4、5 点就由此而来。
替代方案——把 CRD 放在模板中
如果 crds/ 的限制(升级时不更新、不能条件分支)让人为难,还有把 CRD 放进 templates/ 的选择。这样它就成了普通对象,会随升级而更新,也可以加条件。代价是 release 会拥有这个 CRD,所以一旦删除 release,CRD 及其资源会全部消失。 而且多个 release 使用同一个 CRD 时,所有权会重叠而发生冲突。
实际工作中常用的界线是这样的。把 CRD 做成与 Operator Chart 分开的独立 Chart,由集群管理员只安装一次,应用 Chart 则只用 .Capabilities.APIVersions.Has 确认其是否存在。大型项目另外提供 <이름>-crds(占位符为项目名称)Chart,原因就在这里。
在现场相遇的样子
最常出的事故是“改了 CRD 并升级,新字段却不生效”。helm history 里 revision 在累积,部署也显示成功,但集群里的 CRD 仍然是旧 schema。使用了新字段的自定义资源,会把 schema 中没有的字段当作未知字段,悄悄被截掉。这个组合特别糟的是:没有任何地方报告失败。正统的应对是在部署流水线中另设一步单独 kubectl apply CRD,或者另设 CRD 专用 Chart。
第二种常见情况是 CI 的渲染检查与生产给出不同的结果。如果只运行 helm template,能力分支永远固定在一边。如果 CI 中可以使用真实集群,就用 --validate 或 --dry-run=server;如果不能,就用 --kube-version 和 --api-versions 模拟目标集群,把两种情形都渲染一遍会更安全。
下一项实验要做什么
做一个把能力原样输出的 Chart,确认离线渲染的值,再用 --kube-version 和 --api-versions 假装成另一个集群来渲染。根据能力做出加入或排除自定义资源的分支,把 CRD 放进 crds/,安装到真实的 kwok 集群后,确认清单中没有 CRD。最后亲眼看到:即使修改 CRD 并升级,集群也原封不动,并整理 --validate 和 --dry-run=server 与离线渲染有何不同。