5月28日 04:08

编写 YAML 配置文件有哪些最佳实践?

YAML 是 Kubernetes、Docker Compose、GitHub Actions、Ansible 等主流工具的配置语言——不会写 YAML,基本上跟云原生开发绝缘。但"会写"和"写得好"之间隔着一条鸿沟:缩进错了整个文件解析失败,密码硬编码推到 GitHub 炸安全审计,六个服务复制同一份超时配置改一处忘一处。

以下是经过多个生产项目验证的 YAML 编写实践,覆盖缩进规范、命名策略、类型陷阱、环境隔离、复用机制和自动验证六个方面。

缩进:2 空格,没有商量余地

YAML 用缩进表示层级关系,缩进错了不是"不规范"的问题,是直接解析报错。一条规则就够了:统一用 2 个空格

yaml
server: host: localhost port: 8080 ssl: enabled: true cert: /etc/ssl/cert.pem

容易踩的两个坑:

  • Tab 字符混入:大多数编辑器默认 Tab 是 4 空格或 8 空格,跟 YAML 的 2 空格不兼容。在编辑器设置里把 Tab 映射成空格,或者装一个 .editorconfig
ini
# .editorconfig [*.{yaml,yml}] indent_style = space indent_size = 2
  • 列表缩进对齐的是短横线后的内容,不是短横线本身:
yaml
items: - name: first value: 1 - name: second value: 2

别相信手感,跑一下 yamllint 就行。配置文件里加个 .yamllint

yaml
extends: default rules: line-length: max: 120 indentation: spaces: 2 indent-sequences: true

键名:六个月后还能看懂

键名是配置文件的"变量名",跟写代码一个道理——命名清晰比节省字符重要。

yaml
# 反面教材 db: h: db.example.com ct: 30 mc: 100 # 正面教材 database: host: db.example.com connection_timeout: 30 max_connections: 100

三条原则:

  1. 整份文件统一风格snake_case 是 YAML 生态的主流——Kubernetes、Ansible、GitHub Actions、GitLab CI 都用它。团队已有 camelCase 惯例就跟着走,但别混
  2. 用嵌套代替前缀server_http_port 是扁平思维,改成嵌套结构清晰得多:
yaml
server: http: port: 8080 grpc: port: 9090
  1. 嵌套别超 4 层。超过 4 层说明概念没拆分干净,该考虑拆文件了

类型陷阱:YAML 的自动推断会咬人

YAML 会"聪明地"推断值类型,但这种聪明经常闯祸:

yaml
# 你以为是字符串,其实是布尔值 enabled: yes # → true disabled: no # → false active: on # → true inactive: off # → false # 你以为是字符串,其实是数字 version: 1.0 # → 浮点数 1.0,不是字符串 "1.0" port: 8080 # → 整数 8080

Python 用 PyYAML 加载这份配置,version 拿到的是 1.0 而不是 "1.0",如果下游代码当字符串处理就会翻车。

拿不准就加引号

yaml
version: "1.0" # 就是字符串 enabled: "yes" # 就是字符串 port: "8080" # 就是字符串(如果你确实要字符串)

引号没有任何副作用,除了让意图更明确。

还有一个容易忽略的坑:字符串里包含冒号或特殊字符时必须引号保护:

yaml
description: "key: value" # 冒号在字符串里 path: "/usr/local/bin" # 安全起见也加上

环境隔离:敏感信息别写进文件

把数据库密码直接写进 YAML 推到 Git 仓库——这是真实发生过的安全事故。

方案一:环境变量替换

yaml
database: host: ${DB_HOST:-localhost} port: ${DB_PORT:-5432} password: ${DB_PASSWORD} # 没有默认值,缺失时启动报错

${VAR:-default} 是标准语法:变量存在用变量值,不存在用默认值。省略 :-default 表示必须提供,否则报错。

方案二:按环境拆文件

shell
config/ base.yaml # 所有环境共享 development.yaml # 开发环境覆盖 staging.yaml # 预发环境覆盖 production.yaml # 生产环境覆盖(.gitignore 排除或加密存储)

应用启动时按顺序加载,后者覆盖前者。这样生产环境的密码只出现在 production.yaml 里,开发环境干净无敏感信息。

方案三:密钥管理服务

对于 Kubernetes 环境,用 Sealed Secrets 或 External Secrets Operator 管理敏感配置,代码仓库里只存加密后的密钥。

重复配置:锚点和别名消灭复制粘贴

多个服务共享相同的超时和重试策略时,复制粘贴是最差的选择——改一处忘一处,迟早出事。

yaml
defaults: &server_defaults timeout: 30 retry: 3 log_level: info service_a: <<: *server_defaults host: api-a.example.com port: 8080 service_b: <<: *server_defaults host: api-b.example.com port: 9090

& 定义锚点,* 引用别名,<< 把键值合并进来。改 defaults 里的 timeout,所有引用它的服务都生效。

但锚点不是万能药。当配置间的关系变复杂(条件引用、动态合并),就该上模板引擎了:

  • Kubernetes:用 Helm 的 {{ .Values }} 模板
  • 通用场景:用 Jinja2 或 envsubst 做预渲染

满屏都是 &* 的时候,就是该换工具的时候。

验证和 Schema:部署前拦截错误

YAML 没有编译器帮你查错。一个多余的空格、一个拼错的键名,都可能到生产环境才炸。所以验证必须自动化。

语法检查——最低要求:

bash
yamllint config.yaml

结构验证——进阶要求。为配置文件定义 JSON Schema,所有字段都受约束:

json
{ "type": "object", "required": ["server"], "properties": { "server": { "type": "object", "required": ["host", "port"], "properties": { "host": { "type": "string", "format": "hostname" }, "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, "ssl": { "type": "boolean", "default": false } } } } }

check-jsonschema --schemafile schema.json config.yaml 一行命令验证。拼错键名、类型不对、必填缺失——全部在 CI 阶段拦住。

Kubernetes 专用

bash
kubeval deployment.yaml # 校验资源定义是否符合 API 规范 kubectl apply --dry-run=client -f deployment.yaml # 模拟提交

配置即代码:Git + Code Review

YAML 配置文件就是代码,该走代码的全部流程:

  • 版本控制:所有变更可追溯,出问题秒级回滚
  • Code Review:配置变更必须有人审。一个缩进错误、一个端口配错,都可能搞垮服务
  • 语义化提交chore: increase database pool limit from 20 to 50fix config 有意义得多

有些团队把配置文件排除在 PR 审查之外,这是危险的。配置变更的影响范围往往比业务代码更广,更需要第二双眼睛。

标签:YAML