4、YAML高级特性:锚点(&)与别名(*)、合并键(<<)、类型标签、多文档分隔符(---)

YAML 的基础语法,说白了就是键值对和缩进。但真正让 YAML 在 DevOps 领域大放异彩的,是它那套高级特性。我刚开始接触时也觉得这些符号花里胡哨的,直到在项目中遇到几百行的重复配置……嗯,真香。

这一节,我们来啃下 YAML 的四个硬核特性:锚点与别名、合并键、类型标签、多文档分隔符。掌握了它们,你的配置文件会从「能跑」变成「优雅」。

YAML 高级特性 锚点(&) & 别名(*) 合并键(<<) 类型标签 多文档分隔符(---) 复用数据块 合并映射 强制类型转换 多文档组合

4.1 锚点(&)与别名(*):告别重复劳动

你想想看,一个 Kubernetes 部署文件里,同一个镜像标签要写七八遍。改版本号时,漏改一个就是事故。锚点和别名就是来解决这个痛点的。

锚点用 &名称 定义,别名用 *名称 引用。就这么简单。

# 定义锚点
default_config: &default
  timeout: 30
  retries: 3
  log_level: info

# 引用别名
service_a:
  <<: *default
  name: service-a

service_b:
  <<: *default
  name: service-b
  timeout: 60  # 覆盖默认值

核心要点:锚点定义的是整个节点,不是单个值。别名引用的是整个结构,然后可以用合并键覆盖部分字段。

我在项目中遇到过这样的场景:一个微服务架构有 12 个服务,每个服务的健康检查配置几乎一模一样。用锚点之前,改一次超时时间要改 12 个文件。用锚点之后,改一个地方就行。你想想看,这省了多少事。

个人习惯:我通常把锚点定义放在文件顶部,用 defaults 或 _base 作为键名。这样一眼就能看到所有可复用的配置块。

4.2 合并键(<<):把配置「拼」起来

合并键 << 是锚点的最佳搭档。它的作用是把一个映射(字典)合并到当前映射中。说白了,就是「继承」。

database_defaults: &db_defaults
  host: localhost
  port: 5432
  pool: 10

development:
  <<: *db_defaults
  database: dev_db
  debug: true

production:
  <<: *db_defaults
  database: prod_db
  host: db.prod.example.com  # 覆盖 host
  pool: 50                   # 覆盖 pool

合并键可以同时合并多个锚点:

base_app: &app
  version: 1.0
  author: team

base_web: &web
  port: 8080
  ssl: true

my_service:
  <<: [*app, *web]
  name: my-service
  port: 9090  # 覆盖 web 的 port

我曾经踩过的坑:合并键只能合并映射(字典),不能合并列表。如果你试图 <<: *some_list,解析器会直接报错。另外,合并的顺序很重要——后面的会覆盖前面的。

4.3 类型标签:让 YAML 更「严谨」

YAML 默认会做类型推断。比如 123 会被解析成整数,true 会被解析成布尔值。但有时候,我们需要强制指定类型。这时候就用到了类型标签。

类型标签的格式是 !!类型:

# 强制类型转换
port: !!str 8080          # 强制转为字符串 "8080"
enabled: !!bool "yes"     # 强制转为布尔值 true
count: !!int "42"         # 强制转为整数 42
price: !!float "3.14"     # 强制转为浮点数 3.14

# 时间与日期
start_time: !!timestamp 2024-01-01T10:00:00Z
expire_date: !!timestamp 2024-12-31

# 空值与集合
items: !!set
  ? apple
  ? banana
  ? cherry

# 二进制数据
icon: !!binary |
  R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
  OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+

实际场景:在 CI/CD 配置中,我经常用 !!str 来确保版本号不被解析成数字。比如 version: !!str "1.10",如果不加标签,YAML 会把它当成浮点数 1.1,这显然不是我们想要的。

常用的类型标签:

标签 类型 示例
!!str 字符串 !!str 123
!!int 整数 !!int "42"
!!float 浮点数 !!float "3.14"
!!bool 布尔值 !!bool "yes"
!!null 空值 !!null ~
!!timestamp 时间戳 !!timestamp 2024-01-01
!!set 集合 !!set {? a, ? b}
!!binary 二进制 !!binary |

我的建议:除非有明确的类型转换需求,否则不要滥用类型标签。YAML 的自动类型推断在 95% 的场景下都够用。加太多标签反而让文件变得臃肿。

4.4 多文档分隔符(---):一个文件,多个配置

一个 YAML 文件里可以包含多个文档,用 --- 分隔。每个文档都是独立的 YAML 结构。

# 第一个文档:API 配置
---
api_version: v1
endpoint: /api/v1/users
timeout: 30

# 第二个文档:数据库配置
---
database:
  host: localhost
  port: 5432
  name: myapp

# 第三个文档:缓存配置
---
cache:
  type: redis
  host: redis.example.com
  port: 6379

多文档分隔符在以下场景特别有用:

  • Kubernetes 资源清单:一个文件里放多个资源(Deployment、Service、ConfigMap)
  • CI/CD 流水线:不同阶段的配置放在同一个文件的不同文档中
  • 测试数据:多个测试用例的数据集

注意:解析多文档 YAML 时,解析器会返回一个文档列表。在 Python 中可以用 yaml.load_all() 来读取。每个文档之间完全独立,不能跨文档引用锚点。

我记得有一次在写 GitLab CI 配置时,把 staging 和 production 的部署配置放在同一个 .gitlab-ci.yml 里,用 --- 分隔。结果同事 review 时一脸懵:「这文件怎么有多个 ---?」后来我加了个注释说明,大家才明白。嗯,这里要注意:多文档分隔符虽然强大,但也要考虑团队的习惯。

我曾经踩过的坑:锚点不能跨文档引用。如果你在第一个文档定义了 &default,第二个文档里用 *default 是无效的。每个文档的锚点作用域仅限于当前文档。

4.5 综合实战:一个完整的 DevOps 配置

把今天学的四个特性串起来,写一个真实的 CI/CD 配置:

# 文档 1:全局默认配置
---
global_defaults: &global
  image: node:18-alpine
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/

# 文档 2:构建阶段
---
build:
  stage: build
  <<: *global
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - dist/

# 文档 3:测试阶段
---
test:
  stage: test
  <<: *global
  script:
    - npm install
    - npm run test
  coverage: !!str "/Coverage: \d+\.\d+%/"

# 文档 4:部署阶段
---
deploy_staging:
  stage: deploy
  <<: *global
  script:
    - npm run deploy:staging
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - develop

deploy_production:
  stage: deploy
  <<: *global
  script:
    - npm run deploy:production
  environment:
    name: production
    url: https://example.com
  only:
    - main

这个配置里,我们用 &global 定义了公共配置,用 <<: *global 复用到每个阶段,用 !!str 强制了覆盖率正则表达式的类型,用 --- 把不同阶段分成了独立文档。你看,四个特性全用上了。

最后说一句:这些高级特性用好了,你的 YAML 配置会像代码一样优雅。但别过度设计——如果一个锚点只被引用一次,那还不如直接写。我个人的原则是:重复三次以上才考虑用锚点。

可可精品资料库