为什么我们要对 YAML 进行模板化?(2019)
工程师们认为,用模板化 YAML 来描述基础设施和 Kubernetes 配置已经变成一种反模式:原本简单的声明式文件不断叠加循环、条件和供应商特定 DSL,最终变成脆弱的“把 YAML 当编程语言”的系统,难以验证、调试和保障安全。许多人更倾向于用真正的编程语言(TypeScript、Python、Ruby)或专门的配置语言(Jsonnet、CUE、Dhall、Nix)来生成 JSON/YAML,并结合 CDK、Pulumi 或 Tanka 等工具,让逻辑留在可测试的代码里,而集群最终接收的仍是普通 manifests。其背后更大的担忧是:每一种新的配置格式和模板引擎都在重复同样的错误——重新发明一种受限语言,而不是拥抱现有语言以及关于类型、schema 和组合的成熟工具链。
为什么使用 YAML,然后再对它进行模板化
- 评论者一致认为,YAML 之所以占主导地位,很大程度上是出于惯性和熟悉度:到处都有示例和工具链,尤其是在 Kubernetes 和 CI 中。
- 人们通常从“只是一个简单的 YAML”开始,然后加上一些变量,再加上条件/循环,最后变成了一种事实上的编程语言。
- 从文档和博客里复制粘贴片段,是人们继续使用原始/模板化 YAML,而不是更高层工具的一个主要原因。
对 YAML 本身的批评
- 许多人认为 YAML 具有误导性的“对人友好”:缩进很脆弱,在长文件里很容易丢失上下文,规范也很复杂。
- 类型系统和隐式转换(例如
no→ false / “Norway” 问题、1.1 与 1.2 的差异)是反复出现的抱怨。 - 模板化 YAML 被描述为“字符串类型的编程”:很难验证、调试和推理,而且有供应商特定的语义。
- 也有人为 YAML 辩护,认为它适合小到中等复杂度的配置,以及非程序员(例如 front-matter、简单的 docker-compose),尤其是在配合 schema 校验和 lint 工具时。
模板化 vs 用真正的语言生成
- 一个强烈的观点是:别再发明半吊子的模板 DSL;直接用真正的语言(TypeScript、Python、Ruby、Go 等)来构建数据结构并输出 JSON/YAML。
- 被提到的好处包括:可以复用库和工具,进行类型检查,编写单元测试,更容易重构,以及更清晰地区分“纯数据”和逻辑。
- 反对意见:
- 让任意语言在 CI/IaC 中运行会增加攻击面和复杂度。
- 配置应该是声明式且受限制的;完全图灵完备的能力会引入意大利面代码,并让策略/验证更困难。
替代的配置语言 / 系统
- 经常被提到的有:Jsonnet、Dhall、CUE、Nix、Nickel、基于 Starlark 的工具(ytt、Bazel/Starlark、Kurtosis)、CDK8s、AWS CDK、Pulumi、Tanka、Kustomize。
- 看法分歧:一些人喜欢 Jsonnet/CUE/Nix/Dhall,因为它们是函数式、总函数或类型安全的配置;另一些人则觉得它们太小众、难学,或者缺少库。
- 也有人建议“带类型的 config as code”,再配上一个简单、经过校验的数据格式作为编译产物(通常是 JSON,也可以渲染成 YAML)。
Kubernetes、Helm、CI 和 operators
- Helm chart 常被说很痛苦:文本替换、缩进折腾、错误信息糟糕,而且还需要通过
values.yaml重新暴露 Kubernetes 的功能。 - 有些人更喜欢 Kustomize 或直接写原生 manifests;另一些人认为 operators 或更高层的 CDK,比越来越多的 YAML 模板化更合适。
- 对于 CI(GitHub Actions 等),很多人建议使用最小化的 YAML,只负责
exec真正的脚本,避免在配置文件里写复杂逻辑。
更深层的配置哲学
- 反复出现的主题是:“所有配置都会向图灵完备性漂移。”
- 争论的焦点在于逻辑应该放在哪里、配置应该拥有多大能力,以及随着复杂度增长,如何让系统保持可观测、可测试和可维护。