alltools.one
YAML
2025-06-19
7 min
alltools.one Team
YAMLAnchorsAliasesDRYConfiguration

YAML 锚点与别名:DRY 配置模式

在 YAML 文件的多个部分重复相同的配置违反了 DRY(Don't Repeat Yourself)原则,并导致维护困难。YAML 锚点和别名通过允许你定义一次值并多次引用来解决这个问题。

基本语法

锚点&):标记一个值以供复用 别名*):引用一个已锚定的值

# 定义锚点
defaults: &default_settings
  timeout: 30
  retries: 3
  log_level: info

# 使用别名引用它
development:
  <<: *default_settings
  log_level: debug

production:
  <<: *default_settings
  retries: 5

<< 合并键将引用的锚点中的所有键值对合并进来。合并之后定义的属性会覆盖锚定的值。

解析后的结果:

development:
  timeout: 30
  retries: 3
  log_level: debug  # 已覆盖

production:
  timeout: 30
  retries: 5        # 已覆盖
  log_level: info

锚点类型

标量锚点

复用单个值:

max_connections: &max_conn 100

database:
  pool_size: *max_conn
cache:
  pool_size: *max_conn

序列锚点

复用整个列表:

common_ports: &ports
  - 80
  - 443

web_server:
  ports: *ports
load_balancer:
  ports: *ports

映射锚点

复用整个对象(最常见的模式):

logging: &log_config
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

services:
  api:
    logging: *log_config
  worker:
    logging: *log_config

合并键(<<

合并键有特定的行为:

  1. 它从引用的映射中复制所有键值对
  2. 显式设置的键优先于合并的键
  3. 多个合并按顺序处理(冲突时先到先得)
# 多个合并
base: &base
  a: 1
  b: 2

extra: &extra
  b: 3
  c: 4

combined:
  <<: [*base, *extra]
  # 结果:a=1, b=2(base 优先),c=4

实际示例

Docker Compose

x-common: &common
  restart: unless-stopped
  networks:
    - app-network
  logging:
    driver: json-file
    options:
      max-size: "10m"

services:
  api:
    <<: *common
    image: myapp/api:latest
    ports:
      - "8080:8080"
    environment:
      DATABASE_URL: postgres://db:5432/myapp

  worker:
    <<: *common
    image: myapp/worker:latest
    environment:
      QUEUE_URL: redis://cache:6379

  scheduler:
    <<: *common
    image: myapp/scheduler:latest

Docker Compose 使用 x- 前缀约定来定义包含锚点的扩展字段。更多模式请参见我们的 YAML 用于 Docker Compose 指南。

GitHub Actions

jobs:
  test:
    runs-on: ubuntu-latest
    env: &common_env
      NODE_VERSION: '20'
      CI: true
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      - run: npm test

  build:
    runs-on: ubuntu-latest
    needs: test
    env:
      <<: *common_env
      BUILD_TARGET: production
    steps:
      - uses: actions/checkout@v4
      - run: npm run build

Kubernetes

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          resources: &resources
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
        - name: sidecar
          resources: *resources  # 相同的资源限制

局限性与陷阱

1. 无跨文件引用

锚点只在单个 YAML 文件内有效。你不能引用另一个文件中定义的锚点。对于跨文件复用,请使用模板工具如 Helm、Jsonnet 或 Kustomize。

2. 无法部分覆盖序列

你不能合并并部分覆盖列表 — 你只能替换整个列表:

base_ports: &ports
  - 80
  - 443

service:
  ports: *ports  # 你得到的恰好是 [80, 443]
  # 无法通过合并向此列表添加端口 8080

变通方案:单独定义扩展的列表。

3. 顺序很重要

锚点必须在引用之前定义:

# 错误 - 别名在锚点之前
service:
  settings: *defaults

defaults: &defaults
  timeout: 30

# 正确 - 锚点在别名之前
defaults: &defaults
  timeout: 30

service:
  settings: *defaults

4. 并非所有工具都支持合并键

<< 合并键不是 YAML 核心规范的一部分 — 它是一个特定类型的扩展。大多数流行的 YAML 解析器支持它,但某些严格的解析器可能不支持。

使用我们的 YAML 验证器 验证你的 YAML 锚点用法。

常见问题

将 YAML 转换为 JSON 时锚点会保留吗?

不会。当 YAML 被解析时,锚点和别名会被解析为它们的值。生成的 JSON 将内联展开这些值。这意味着转换为 JSON 再转回来会丢失 DRY 结构。锚点只存在于 YAML 源格式中。

我可以在复杂的嵌套覆盖中使用锚点吗?

合并键(<<)只执行浅合并 — 它从锚定的映射中复制顶层键。嵌套对象会被完全替换,而不是深度合并。对于深度合并行为,你需要特定工具的解决方案(Helm 的 merge 函数、自定义 YAML 处理器)。

相关资源

Published on 2025-06-19
YAML Anchors and Aliases: DRY Configuration Patterns | alltools.one