跳到主内容
d.devtul.fun
EN
配置 · 2026-08-16

什么时候该用 YAML(以及什么时候不该)

YAML 在基础设施里无处不在:Kubernetes、CI 流水线、Docker Compose、GitHub Actions、Ansible。它也是开发者又爱又恨的格式。关于「什么时候该用 YAML」的诚实答案,不是「永远」也不是「绝不」,而是「当文件由重视注释和结构的人手写和阅读、而你又不需要文件去计算的时候」。这篇文章给你一个决策框架、具体对比,还有一条退路。

YAML 擅长什么

YAML 设计出来就是讨人喜欢的:有注释,用缩进而不是花括号,能干净地映射成嵌套数据。对一个要人手改的配置——一份部署 manifest、一条流水线定义——这些属性很重要。队友读一个 YAML 文件,不用在脑子里跑一个解析器就能看懂。

database:
  host: db.prod.internal
  port: 5432
  pool:
    max: 20
    idle: 5

和同样意图的 JSON 比,差别主要就是花括号和引号;YAML 的赢面是可读性,以及你在 JSON 里丢掉的 # 注释。YAML 还支持锚点和别名(& 和 *),可以定义一次再多处引用,对重复结构确实方便——只要你记得引用是复制,不是之后能各自偏离的指针。

同一个配置写成四种格式

要看出取舍,把同一份小配置用四种格式各写一遍。数据完全一样,体验不同。

// JSON —— 无注释、严格、对解析器友好
{
  "database": {
    "host": "db.prod.internal",
    "port": 5432,
    "pool": { "max": 20, "idle": 5 }
  }
}

# TOML —— 扁平、好注释、适合应用配置
[database]
host = "db.prod.internal"
port = 5432
[database.pool]
max = 20
idle = 5

# CUE —— 同时是 schema 也是数据,写的时候就校验
database: {
  host: string | *"db.prod.internal"
  port: int & >= 1 & <= 65535 | 5432
  pool: { max: int | 20, idle: int | 5 }
}

JSON 在消费者是程序、你从不需要手改时胜出,而且它的严格是优点:少个逗号就 loudly 报错,而不是被悄悄强转。TOML 在应用设置文件上胜出(Cargo、pyproject),因为它扁平又好读。CUE 在你希望配置在编写时就被 schema 校验时胜出,能在坏端口抵达服务器之前就拦下。YAML 站在中间:对人友好,但藏了坑。

YAML 的坑

缩进模型藏着真风险。最有名的是挪威小镇 NO 被解析成布尔 false,还有不括起来的 012 有时被读成八进制。看起来像时间戳的字符串(2026-09-14)被某些解析器强转成日期。解法是给一切非数字的字符串加引号,但这又抵消了可读性。

# 这些意外在多种解析器里真实存在
active: no        # 变成布尔 false
value: 012        # 可能变成八进制 10
date: 2026-09-14  # 可能变成 Date 对象

还有更安静的陷阱。多行字符串用 | 或 >,多一个空格就改变块级与折叠语义。缩进混用 tab 和空格在多数解析器里直接失败,因为 YAML 明确禁止 tab。锚点是按引用复制:改一个被引用的块,所有别名跟着动,这坑了那些以为它们是独立副本的人。防御:字符串值加引号、固定 YAML 版本和解析器,并用 schema 校验解析结果,而不是信任推断出来的类型。

配置语言该不该图灵完备

这是模板化背后更深的争论。一方说配置只该是数据——纯声明、可 diff。另一方说你需要计算:跨环境循环、派生值、条件。支持方指着 DRY:没有循环,你就得为 dev、staging、prod 重复同一块,而且副本会漂移。反对方指着灾难:一个会调函数、碰网络、每次运行行为不同的配置,再也谈不上可审查——审查者不执行就不知道它会产出什么,也就意味着 CI 没法给出最终文件的干净 diff。

务实的中间路线是两层模型:基础配置保持纯数据,把计算放到一个独立、受限的步骤(模板化)里,它在到达系统之前产出纯数据。产出的 YAML 在制品库里仍可审查;计算留在热路径之外,还能单独做单元测试。你在需要的地方拿到 DRY,又不至于把每个配置都变成程序。

大 manifest 的痛

规模一大,YAML 就不再友好了。一个 Kubernetes deployment 加上它的 service、configmap、ingress 动辄几百行;一个有几个组件的真实应用能到几千行。审一个 4000 行的 diff 很要命,环境间复制粘贴会漂移,一个缩进错了就搞挂一次发布。这正是团队去碰模板化的时机——不是因为 YAML 错了,而是手写维护几千行几乎一模一样的行本身就不对。

模板化:Helm 还是 Kustomize

工具模型强项弱项
Helm在 YAML 上套 Go 模板可复用 chart、逻辑丰富模板可能产出非法 YAML;逻辑藏在模板里
Kustomize基于 patch 的 overlay纯 YAML、没有新语言overlay 变复杂;计算能力有限
CUE/Jsonnet产出数据的代码有类型和校验学习曲线;多一个要跑的工具

Helm 给你完整计算,却把你推图灵完备的坑里,而且一个产出语法错误的 Helm 模板只在 apply 时才失败。Kustomize 让你靠每个环境的 overlay 更贴近纯 YAML,保持可审查,坏 patch 也会快速失败。多数团队的问题不是「哪个最好」,而是「我们到底需要多少逻辑」——从 Kustomize 起步,只有当你要把 chart 发给别人时才上 Helm,而当你更看重校验胜过模板时再去碰 CUE。

JSON 或 TOML 更合适的时候

配置由程序生成、只被程序解析时(构建产物、API 负载),选 JSON,而且你要的就是严格无歧义的解析。应用设置小而扁平、要人手改时,选 TOML。人类要改嵌套、带注释的定义、且你能接受校验开销时,选 YAML。如果你开始在 YAML 里写循环或条件,那就是你已经超出它能力范围的信号,该把逻辑挪到生成器里。

YAML 与替代格式一览

需求YAMLJSONTOMLCUE
注释有无有有
人手编辑好差好好
schema 校验外部外部外部内建
计算能力无无无有(受限)

一张决策表

如果你需要……用
人手改的嵌套配置、带注释YAML
程序生成、只被解析的配置JSON
扁平应用设置、简单类型TOML
配置要被 schema 校验CUE 或 Jsonnet
一个源出多个环境Kustomize(YAML)或 Helm

从 YAML 迁出

如果 YAML 的代价已经超过收益,分步走。第一步,加一层 schema(CUE 或 K8s 原生校验),让现有文件至少被检查,这也给你一份可以对照着迁移的契约。第二步,对最痛的那部分,用带类型的源去生成 YAML——你的 CI 可以把 Jsonnet 或 CUE 渲染成集群照常消费的 YAML,运行时不变,你还能 diff 生成的产物。像 yq 和 kpt 这类工具,能帮你重构和流水线化 YAML,而不必手改几千行。第三步,一次迁一个组件,别一个周末重写整个仓库。在让团队其余人也跟进前,先量一量新格式是不是真把 diff 体积和审查时间降下来了。目标是把痛处的 YAML 减少,而不是到处都归零。如果团队里没人熟悉 CUE 或 Jsonnet,先从给现有 YAML 加校验这条最便宜的路走起,等痛点真的撑不住再引入新工具,避免为了迁移而迁移、反而增加认知负担。

总结

人类手改、嵌套、要注释的配置用 YAML,并接受你必须给字符串加引号、校验结果。程序独占的文件用 JSON,扁平设置用 TOML,想要 schema 用 CUE。manifest 过了几百行,就上 Kustomize 或 Helm,而不是手拷,且让产出的 YAML 保持可审查。痛感持续上升,就把最糟的组件逐个迁到带类型、代码生成的源上。

继续阅读