5月28日 03:56

YAML 锚点和别名是什么?如何避免重复配置?

YAML 锚点(&)给一个节点打标签,别名(*)引用标签指向的内容,合并键(<<:)把锚点里的键值对铺开到当前映射——三个符号组成 YAML 内置的 DRY 机制,在 Docker Compose、GitLab CI、Kubernetes 配置里到处都在用。

语法就三条:

  • &anchor_name — 打标记,写在值后面
  • *anchor_name — 引用,完整复制那个值
  • <<: *anchor_name — 合并,把映射里的键值对铺进当前位置
yaml
defaults: &defaults timeout: 30 retry: 3 log_level: info service_a: <<: *defaults port: 8000 service_b: <<: *defaults port: 8001 retry: 5 # 覆盖 defaults 的 retry

service_a 最终拿到 {timeout: 30, retry: 3, log_level: info, port: 8000}service_bretry 被覆盖成 5。<<: 只合并映射,列表和标量用 * 直接引用。

追问

*anchor 直接引用和 <<: *anchor 合并引用有什么区别?

*anchor 是整块替换——锚点是什么,别名就是什么,原样搬过来,不能改也不能加。<<: *anchor 是展开合并——把锚点映射里的每个键值对铺进当前映射,当前映射已有的键不会被覆盖,还能加新键。所以 * 适合复用整个结构(比如一组标签、一个连接配置),<<: 适合继承并扩展(比如一套默认参数覆盖几个字段)。

YAML 合并键的多重继承怎么写?合并顺序是什么?

yaml
base1: &b1 timeout: 30 debug: false base2: &b2 debug: true verbose: true service: <<: [*b1, *b2] name: my-service

<<: [*b1, *b2] 列表里靠后的锚点优先级更高。debugb1 里是 falseb2 里是 true,最终 service.debugtrue

注意不是所有解析器都支持多重合并——PyYAML 就不支持,Go 的 gopkg.in/yaml.v3 支持。如果你的 YAML 要过多个解析器,逐个测试。

锚点能引用列表和嵌套结构吗?

都能。列表用 * 直接引用:

yaml
common_tags: &tags - monitoring - logging server1: tags: *tags

嵌套锚点也可以,在映射内部再打锚点引用子结构。但嵌套超过两层时,追引用链比直接看配置还费劲,团队协作慎用。

实际项目里 YAML 锚点有什么坑?

解析器兼容性是最大的坑。Ruby Psych 和 Go gopkg.in/yaml.v3 支持完整,Python PyYAML 不支持多重合并键,yaml-cpp 对嵌套锚点的行为也不一致。CI/CD 工具(GitHub Actions、GitLab CI、Docker Compose)基本都支持,但自定义解析管线要验证。

别名是引用不是深拷贝——某些解析器修改引用对象会影响其他使用同一锚点的地方,这在程序化操作 YAML 时容易踩坑。

安全风险常被忽略。恶意构造的锚点循环引用或超深嵌套可以导致解析器 DoS。处理不可信 YAML 输入时建议禁用锚点解析(Python 可以用 yaml.SafeLoader)。

什么时候该用独立文件替代锚点?

三个信号:

  1. 锚点名称要写注释才能看懂——复用逻辑太隐晦
  2. 嵌套超过两层——追引用链比直接读配置还费劲
  3. 有人改了锚点内容却不知道哪些地方在引用——缺少文档约定

这时候把公共配置拆成独立文件,用工具在构建时合并更可靠:yq 做 YAML 合并,Helm 用 tpl 模板,Docker Compose 用 extends 关键字。

写段代码

Docker Compose 复用环境变量和日志配置:

yaml
x-common: &common restart: unless-stopped logging: driver: json-file options: max-size: "10m" services: api: <<: *common image: myapp-api:latest worker: <<: *common image: myapp-worker:latest

x- 前缀是 Docker Compose 约定,表示这个键不对应服务定义,纯粹用来放锚点模板。

标签:YAML