Skip to content

YAML 速查表

用于配置文件的对人类友好的数据序列化标准。

01

入门

基本结构

YAML 使用缩进(空格,而非制表符)表示嵌套。映射使用 key: value,序列使用 - item。内联语法使用 [] 表示列表,{} 表示映射。注释使用 #。

yaml
# YAML uses indentation for structure
# Comments start with #

# Key-value pairs (mappings)
name: Alice
age: 30

# Nested mappings (use consistent indentation)
person:
  name: Bob
  age: 25
  address:
    city: NYC
    zip: 10001

# Sequences (lists)
hobbies:
  - reading
  - coding
  - music

# Inline syntax
numbers: [1, 2, 3]
coords: {x: 10, y: 20}

注释

注释以 # 开头,前面必须有空白或位于行首。在块标量(| 和 >)内部,# 是字面内容,不是注释。没有多行注释语法——每一行都需要自己的 #。

yaml
# Full-line comment
key: value  # Inline comment (whitespace before # required)

# Comments can appear anywhere
# - before documents
# - inside sequences
list:
  - a  # first item
  - b  # second item

# Block scalar headers DO accept comments
text: |  # literal block
  line one

# But the content of block scalars is literal,
# so # inside is NOT a comment:
literal: |
  # this is part of the string, not a comment

缩进规则

YAML 严格禁止使用制表符缩进——只允许空格。同一层级的缩进必须一致,但不需要固定数量。序列中的 - 是缩进的一部分。混用制表符和空格会抛出解析器错误。

yaml
# Use SPACES only — tabs are forbidden for indentation
# 2 spaces is the most common convention

mapping:
  nested:
    deeply_nested: value

# Sequences can be indented at the same level as the key
items:
  - one
  - two

# Or indented further (both are valid)
items2:
    - one
    - two

# WRONG: tab characters cause parser errors
# bad:	value   (tab before value)

# Hyphen counts as indentation start
- item1
- item2

文档开始与结束

--- 标记 YAML 文档的开始;... 标记结束。一个文件可以包含由 --- 分隔的多个文档(文档流)。对于单文档配置文件,开头的 --- 是可选的,但建议作为给解析器的提示。

yaml
# --- separates documents in a stream
# ... ends a document (optional)

---
name: first document
value: 1
...
---
name: second document
value: 2
...

# A single document needs no markers
key: value

# But --- is recommended at the start of files
# to disambiguate from plain text
---
apiVersion: v1
kind: ConfigMap

常见陷阱

冒号后面需要跟一个空格才能作为映射分隔符。YAML 1.1 与 1.2 在 yes/no/on/off(1.1 中为布尔值,1.2 中为字符串)和八进制字面量上存在差异。空值为 null,与空字符串不同。许多解析器(PyYAML)仍遵循 1.1 规则。

yaml
# Colon needs a space after it
key:value      # WRONG: parsed as a single string "key:value"
key: value     # correct

# Yes/No/On/Off are NOT booleans in YAML 1.2
# (they WERE in YAML 1.1 — common gotcha)
answer: yes    # YAML 1.1: true; YAML 1.2: "yes" string

# Numbers with leading zeros
version: 010   # YAML 1.1: octal 8; YAML 1.2: string "010"

# Unquoted strings with special chars
url: example.com/path   # OK
url: example.com:8080   # WRONG: parsed as mapping with key example.com

# Empty values
empty:       # null
empty2: ~    # explicit null
empty3: ""   # empty string (different from null)

YAML 与 JSON

YAML 1.2 的设计使 JSON 成为严格子集——任何 JSON 文件都是有效的 YAML。YAML 增加了注释、块结构、锚点、多行字符串和标签。JSON 要求所有键和字符串值都加引号,YAML 对普通标量放宽了这一要求。

yaml
# JSON is a strict subset of YAML 1.2
# Every valid JSON document is valid YAML

# JSON:
{"name": "Alice", "age": 30, "hobbies": ["a", "b"]}

# Equivalent YAML:
name: Alice
age: 30
hobbies:
  - a
  - b

# YAML adds: comments, multi-line strings, anchors,
# relaxed quoting, tags, multiple documents

# YAML removes: mandatory quotes around strings,
# commas, braces (in block style)

# YAML is a superset of JSON — not the other way.
# Comments and anchors are not valid JSON.
02

标量(字符串、数字、布尔值、Null)

普通与引号字符串

普通字符串无需引号,除非它们看起来像数字、布尔值、null 或日期。单引号仅通过双写('')来转义 ';没有其他转义。双引号支持完整转义序列(\n、\t、\uXXXX)。加引号可强制字符串类型。

yaml
# Plain strings (no quotes) — most common
name: Alice
path: /usr/local/bin
url: https://example.com

# Single-quoted — escape ' by doubling
msg: 'it''s fine'

# Double-quoted — supports escapes
greeting: "Hello\nWorld"
unicode: "\u03B1"   # Greek alpha
tab: "col1\tcol2"

# Multi-word plain strings are fine
description: This is a plain string with spaces

# Strings that look like other types must be quoted
version: "1.0"      # string, not float
notBool: "true"     # string, not boolean
notNull: "null"     # string, not null
date: "2024-01-01"  # string, not date

数字

YAML 1.2 使用 0o 表示八进制(YAML 1.1 使用裸前导 0)。数字中允许下划线以提高可读性。.inf 和 .nan 是特殊浮点字面量。对于应该是字符串但看起来像数字的值(版本号、电话号码、ID),请加引号。

yaml
# Integers
int: 42
negative: -17
positive: +100
underscored: 1_000_000   # readable grouping (YAML 1.2)

# Octal & hex (YAML 1.2 prefixes)
octal: 0o17     # = 15 decimal
hex: 0xFF       # = 255 decimal
binary: 0b1010  # = 10 decimal

# Floats
pi: 3.14159
exp: 1.0e+3     # 1000.0
neg_exp: -2.5e-4

# Special float values (IEEE 754)
infinity: .inf
neg_infinity: -.inf
not_a_number: .nan

# To force a string that looks numeric:
version: "1.0"
phone: "555-1234"

布尔值

YAML 1.2 将布尔值缩小为仅 true/false(以及 True/False、TRUE/FALSE)。YAML 1.1(PyYAML 使用)将 yes/no/on/off/y/n 视为布尔值——这是著名的 bug 来源(例如挪威 'no' → false)。如果字面意思是字符串,请始终加引号。

yaml
# YAML 1.2 — only these are booleans:
enabled: true
disabled: false

# YAML 1.1 ALSO accepts (common gotcha):
# yes, no, on, off, y, n, Y, N
# These are strings in 1.2 but booleans in 1.1

# To force a string:
answer: "yes"      # always a string
option: "on"
flag: "y"

# PyYAML (1.1 rules) parses these as booleans:
# yes -> True, no -> False, on -> True, off -> False
# This breaks "no" as an answer or country code!

# Safe practice: quote if you mean the literal string
country: "no"   # Norway country code, not False

Null 值

YAML 接受 null、Null、NULL、~ 和空值作为 null。空值(冒号后无内容)是 null,与空字符串 "" 不同。解析器将 YAML null 映射为宿主语言的 null 值(None、null、nil)。

yaml
# All of these represent null/None/null
key1: null
key2: Null
key3: NULL
key4: ~
key5:             # empty value

# Distinguish null from empty string
empty_string: ""
null_value: null

# In sequences
list:
  - value
  - null         # explicit null item
  - another

# In nested structures
config:
  timeout: null   # explicitly unset
  retries: 3

# Parsers convert to language-specific null:
# Python -> None, JavaScript -> null, Go -> nil

日期与时间戳

ISO 8601 日期和时间戳被大多数解析器识别为原生类型。裸 YYYY-MM-DD 成为日期;带时间则成为日期时间。加引号可将值保留为字符串。支持时区指示符(Z、+08:00)。

yaml
# Date (ISO 8601)
date: 2024-01-15

# Datetime (ISO 8601)
datetime: 2024-01-15T14:30:00
datetime_tz: 2024-01-15T14:30:00Z
datetime_offset: 2024-01-15T14:30:00+08:00
datetime_space: 2024-01-15 14:30:00

# To force string (not a date):
not_a_date: "2024-01-15"
not_a_date2: '2024-01-15'

# Parsers typically return datetime objects
# Python: datetime.date / datetime.datetime
# JavaScript: Date (depending on library)

类型推断

YAML 的类型系统通过模式解析未加引号的标量。Failsafe 模式将所有内容视为字符串;JSON 和 Core 模式推断 int、float、bool、null、date。需要时可使用引号或显式标签(!!str、!!int)覆盖解析结果。

yaml
# YAML infers types from unquoted scalars
str: hello             # string
int: 42                # integer
float: 3.14            # float
bool: true             # boolean
null_val: null         # null
date: 2024-01-01       # date
list: [1, 2, 3]        # sequence

# Force string type with quotes
str_version: "42"
str_date: "2024-01-01"
str_bool: "true"

# Or with the !!str tag
forced: !!str 42        # string "42"
forced_int: !!int "42"  # integer 42

# Resolution depends on the schema
# Failsafe: everything is a string
# JSON/Core: numbers, bools, null inferred
03

映射

简单映射

映射是 key: value 对,键通常是字符串。键可以是任何标量(数字、布尔值,甚至 null)。不鼓励重复键,行为因解析器而异(通常后值生效)。使用 linter 捕获意外的重复键。

yaml
# A mapping is a set of key: value pairs
name: Alice
age: 30
email: [email protected]

# Keys can be any scalar type (string, number, bool)
"string key": value
123: numeric key
true: boolean key

# Duplicate keys are technically discouraged
# but some parsers allow them (last wins)
key: first
key: second   # overrides "first" in many parsers

# Values can be any type
mixed:
  string: text
  number: 42
  list: [1, 2, 3]
  nested:
    deep: value

嵌套映射

通过缩进子键来创建嵌套映射。同一层级内的缩进必须一致。没有固定的缩进大小,但 2 个空格是事实上的标准。更深的嵌套会降低可读性——考虑展平非常深的结构。

yaml
# Nest mappings with consistent indentation
server:
  host: localhost
  port: 8080
  ssl:
    enabled: true
    cert: /etc/ssl/cert.pem
    key: /etc/ssl/key.pem

database:
  primary:
    host: db1.example.com
    port: 5432
  replica:
    host: db2.example.com
    port: 5432

# Indentation level is up to you — just be consistent
# 2 spaces is the most common convention

映射包含序列

映射和序列可以自由组合。常见模式是映射的序列(记录列表),其中每个 - 开始一个项,对齐的键属于该项。后续键的缩进必须与 - 后第一个键对齐。

yaml
# Values in a mapping can be sequences
servers:
  - web1.example.com
  - web2.example.com
  - web3.example.com

# Sequence of mappings (very common)
users:
  - name: Alice
    age: 30
  - name: Bob
    age: 25

# Each - starts a new item; aligned keys form one mapping
services:
  - name: api
    port: 8080
    env:
      - dev
      - prod
  - name: web
    port: 3000

# Mixed: mapping containing sequence containing mapping
config:
  endpoints:
    - path: /health
      method: GET
    - path: /users
      method: POST

复杂键

YAML 允许使用 ? 键指示符将任何标量或集合作为键。这在配置文件中很少见,但对查找表(例如按区域+实例定价)很有用。复杂键读起来很别扭——大多数配置文件坚持使用普通字符串键。

yaml
# Keys can be complex objects using ? notation
? key1
: value1

# A mapping as a key
? host: localhost
  port: 8080
: connection1

# A sequence as a key
? [a, b, c]
: indexed_value

# Practical (rare) use: matrix lookup
? [us-east-1, t2.micro]
: 0.0116
? [us-east-1, t2.small]
: 0.023

# In flow style:
{[a, b]: value, {x: 1}: other}

# Most config files just use plain string keys
simple: value

内联(流)映射

流样式使用 {} 表示映射,[] 表示序列,类似于 JSON。适用于单行内简短、紧凑的数据。与 JSON 不同,YAML 不允许尾随逗号。可自由混合流和块样式,但配置文件中优先使用块以提升可读性。

yaml
# Flow style mapping (like JSON)
point: {x: 10, y: 20}
config: {host: localhost, port: 8080}

# Nested flow mappings
nested: {a: {b: {c: value}}}

# Mixed flow and block
block_key:
  flow_value: {x: 1, y: 2}
  another: value

# Flow with sequences
mixed: {nums: [1, 2, 3], name: test}

# Quoting still works in flow style
quoted: {"key with space": "value", count: 3}

# Trailing comma is NOT allowed in YAML flow
bad: {a: 1, b: 2,}   # parse error

键顺序与风格

YAML 本身在规范中保留映射顺序,但宿主语言对象可能不保留(尽管大多数现代字典会保留)。为了配置可读性,将相关键分组,使用一致的命名(snake_case 或 camelCase),并添加注释来划分段落。

yaml
# YAML mappings are ORDERED (unlike JSON spec, though
# most JSON parsers preserve insertion order too)
# But language objects may not preserve order:
# Python dict (3.7+): preserves order
# Ruby Hash: preserves order
# JSON object: technically unordered

# Consistent key style improves readability
config:
  server:
    host: localhost      # snake_case
    port: 8080
    max_connections: 100
  database:
    host: db.example.com
    port: 5432
    connection_pool: 10

# Group related keys together
# Use comments to delineate sections
app:
  # networking
  host: 0.0.0.0
  port: 3000
  # limits
  timeout: 30
  retries: 3
04

序列

块序列

块序列在每个项前使用 -(连字符 + 空格)。项可以是任何类型:标量、映射或嵌套序列。- 算作缩进的一部分,因此映射项中后续的键与连字符后第一个键对齐。

yaml
# A sequence (list) uses - followed by space
fruits:
  - apple
  - banana
  - cherry

# Sequence as top-level document
- item1
- item2
- item3

# Empty sequence
empty: []
or_with_marker: []

# Sequence with mixed types
mixed:
  - string
  - 42
  - true
  - null
  - key: value   # a mapping as an item

嵌套序列

嵌套序列通过进一步缩进 - 层级形成。超过 2 层就变得难以阅读——对内部序列切换到流样式。树结构(带子项的映射序列)比纯嵌套序列更易读。

yaml
# Sequence of sequences
matrix:
  - - 1
    - 2
    - 3
  - - 4
    - 5
    - 6

# More readable with flow style for inner
matrix2:
  - [1, 2, 3]
  - [4, 5, 6]

# Deeply nested
tree:
  - name: root
    children:
      - name: child1
        children:
          - name: grandchild1
      - name: child2

# 3D: sequence of sequence of sequence
cube:
  - - [1, 2]
    - [3, 4]
  - - [5, 6]
    - [7, 8]]

映射的序列

映射的序列是最常见的 YAML 模式(记录列表)。每个 - 开始一个新项;同一缩进下的后续键属于该项。- 后对齐键以提升可读性。空映射项写为 {}。

yaml
# Most common pattern: list of records
users:
  - name: Alice
    email: [email protected]
    roles:
      - admin
      - user
  - name: Bob
    email: [email protected]
    roles:
      - user

# Each - starts a new mapping; aligned keys belong
# to that item. Indent the first key after - by 2 spaces.
servers:
  - host: web1.example.com
    port: 8080
    healthy: true
  - host: web2.example.com
    port: 8080
    healthy: false

# Empty mapping item
list:
  - {}
  - name: real

内联(流)序列

流序列使用 [],等效于块序列。适用于单行内的简短列表。流序列可跨多行以提高可读性。与 JSON 不同,YAML 禁止尾随逗号。可自由地将流序列与块映射混合。

yaml
# Flow sequence (JSON-like)
numbers: [1, 2, 3]
names: [Alice, Bob, Charlie]

# Nested flow sequences
matrix: [[1, 2], [3, 4]]

# Flow sequence of flow mappings
points: [{x: 1, y: 2}, {x: 3, y: 4}]

# Mixed: block mapping with flow sequence value
config:
  ports: [80, 443, 8080]
  hosts: [web1, web2, web3]

# Spread across multiple lines (still flow style)
long_list: [
  item1,
  item2,
  item3
]

# NO trailing comma allowed
bad: [1, 2, 3,]   # error

混合与异构内容

YAML 允许序列和映射中完全异构的内容——任何项都可以是任何类型。这种灵活性很强大,但意味着应用程序必须验证结构。使用模式(JSON Schema、Cue 等)进行严格验证。

yaml
# Sequences can mix types freely
mixed:
  - string
  - 42
  - true
  - null
  - [1, 2, 3]            # nested sequence
  - key: value           # mapping
    another: value2
  - - nested
    - sequence

# Heterogeneous mapping values
config:
  string_val: text
  int_val: 42
  list_val: [a, b, c]
  map_val: {x: 1, y: 2}
  null_val: null
  bool_val: true

# YAML has no schema enforcing uniformity —
# the application must validate structure

序列缩进模式

键下的序列可以缩进(模式 1)或位于同一层级(模式 2,因为 - 算作缩进)。两者都有效;选择一种并保持一致。在映射的序列中,一个项的所有键必须与 - 后第一个键对齐。

yaml
# Pattern 1: sequence indented under key
items:
  - a
  - b

# Pattern 2: sequence at same level as key
# (the - is treated as indentation)
items:
- a
- b

# Both are valid and produce the same result.
# Pick one and be consistent (most style guides prefer #1).

# Mapping item alignment — both keys align with first
users:
  - name: Alice
    age: 30
  - name: Bob
    age: 25

# WRONG: misaligned keys cause parse errors
# users:
#   - name: Alice
#    age: 30    # one space off — error
05

多行字符串(折叠与字面)

字面块标量 (|)

字面块标量 | 原样保留每个换行符。缩进(| 之后的量)从每行剥离。默认截断(clip)保留一个尾随换行符。用于脚本、代码、诗歌、ASCII 艺术——任何换行符重要的地方。

yaml
# | preserves newlines as-is
script: |
  #!/bin/bash
  echo "Hello"
  echo "World"

# Output string:
# #!/bin/bash\necho "Hello"\necho "World"\n

poem: |
  Roses are red,
  Violets are blue,
  YAML is clean,
  And JSON is too.

# Trailing newline is kept by default (clip mode)
# Use |- to strip, |+ to keep all trailing newlines

折叠块标量 (>)

折叠块标量 > 将单个换行符转换为空格,将行连接成段落。空行成为字面换行符。比块内容缩进更多的行被原样保留(适用于散文中的代码示例)。

yaml
# > folds newlines into spaces (paragraphs)
description: >
  This is a long paragraph that
  spans multiple lines in the YAML
  but will be joined into one line
  when parsed.

# Output: "This is a long paragraph that spans multiple
# lines in the YAML but will be joined into one line when
# parsed.\n"

# Blank lines preserve a newline
paragraphs: >
  First paragraph here.

  Second paragraph here.

  Third paragraph here.

# More indented lines are preserved literally
example: >
  This is folded.
    This line is more indented — kept literal.

截断指示符 (+/-)

截断控制尾随换行符:clip(默认,无指示符)保留一个;strip(-)全部移除;keep(+)全部保留。指示符紧跟在 | 或 > 之后(例如 |-、|+、>-、>+)。剥离适用于内联使用的字符串;保留很少见但精确匹配源文件。

yaml
# Default (clip): single trailing newline
text: |
  line1
  line2
# Result: "line1\nline2\n"

# Strip (-): remove ALL trailing newlines
text_strip: |-
  line1
  line2
# Result: "line1\nline2"

# Keep (+): keep ALL trailing newlines
text_keep: |+
  line1
  line2


# Result: "line1\nline2\n\n\n"

# Same applies to folded scalars
folded_keep: >+
  folded
  text

缩进指示符

当块标量内容以空白开头时,必须用数字(例如 |2)显式指定内容缩进。该数字表示多少空格的缩进属于结构而非内容。与截断组合:|2-、>4+ 等。很少需要但在需要时至关重要。

yaml
# When the first line starts with spaces, you must
# tell YAML the content indentation explicitly

# Without indicator (ambiguous):
# content:
#   indented_line   # is the leading space content or not?

# With explicit indentation indicator (digit after | or >)
literal: |2
    hello
      world
# The "2" means: content is indented 2 spaces past the indicator
# So 4 spaces of indentation = 2 content + 2 extra

# Combined with chomping (order: indent then chomp)
text: |2-
    no trailing newline

# Folded with explicit indent
folded: >4
    paragraph with
    explicit indent

# Most of the time you don't need this — only when
# content legitimately starts with spaces

普通多行字符串

普通和引号标量可跨多行,换行符折叠为空格(类似于 > 但是隐式的)。这很脆弱,因为换行取决于上下文和特殊字符。对于任何非平凡的多行文本,优先使用显式 |(字面)或 >(折叠)块标量。

yaml
# Plain (unquoted) scalars can span lines
# Newlines are folded to spaces (like > but no indicator)
message: this is
  a long message
  on multiple lines
# Result: "this is a long message on multiple lines"

# Quotes are required if the string contains special chars
quoted: "this is also
  a long message
  on multiple lines"
# Result: "this is also a long message on multiple lines"

# Plain multi-line breaks at certain chars
# (colons, etc.) — usually fold cleanly
# but be careful with leading colons

# Plain is fragile for multi-line — prefer | or >
safe_literal: |
  Use literal block for
  predictable multi-line text

选择折叠还是字面

经验法则:当换行符重要时(脚本、配置文件、代码)使用 |,当你想要可读的多行源文件但连接成一个段落字符串时(描述、文档)使用 >。当字符串内联使用时,使用 |- 剥离尾随换行符。

yaml
# Use | (literal) when newlines matter:
script: |
  #!/bin/bash
  set -e
  echo "Step 1"
  echo "Step 2"

config_file: |
  [default]
  region=us-east-1
  output=json

# Use > (folded) when you want readable source
# but a single paragraph string:
description: >
  This service handles user authentication
  and session management. It talks to the
  PostgreSQL database and Redis cache.

# Use |- or >- to strip trailing newline
# (useful for inline string usage):
inline_script: |-
  echo hello

# Use plain single-line for short strings
short: hello world
06

锚点与别名

定义锚点 (&)

&name 在节点(映射、序列或标量)上定义锚点。锚点不会更改该位置的值——它只是为重用标记节点。别名(*name)按名称引用锚定节点。锚点必须在文档内使用前定义。

yaml
# &name defines an anchor on a node
defaults: &defaults
  adapter: postgres
  host: localhost
  port: 5432

# The anchor marks the node so it can be reused
# The value is still assigned normally
production:
  database: *defaults
  # *defaults is an alias — references the anchored node

# Anchors on scalars
version: &ver "1.0.0"
current: *ver

# Anchors on sequences
colors: &colors
  - red
  - green
  - blue
primary: *colors

使用别名 (*)

*name 是引用先前锚定节点的别名。别名与原始节点共享标识(在大多数解析器中是同一对象)。锚点/别名是文档作用域的——不能跨 --- 文档边界引用。与 <<(合并键)组合以分层覆盖。

yaml
# Define once, reuse by alias
base: &base
  image: nginx:1.25
  restart: always

web:
  <<: *base
  ports:
    - "80:80"

api:
  <<: *base
  ports:
    - "8080:8080"
  environment:
    - DEBUG=true

# Aliases reference the SAME object (by identity)
# In most parsers, mutating one mutates the other
# (though YAML itself is read-only)

# Aliases can't span documents
# Each document has its own anchor namespace

锚点作用域

锚点作用域限于其文档,必须在任何别名引用之前定义(无前向引用)。多个别名可引用同一锚点。别名引用别名有效——它会传递解析。跨文档引用是错误的。

yaml
# Anchors are scoped to a single document
---
# Document 1
shared: &shared
  key: value
use: *shared   # works
...
---
# Document 2 — &shared is NOT visible here
other: *shared   # ERROR: unknown anchor

# Anchors must be defined before the alias
# (forward references are not allowed)
bad: *later      # ERROR
later: &later value

# Multiple aliases to the same anchor are fine
template: &tpl
  timeout: 30
use1: *tpl
use2: *tpl
use3: *tpl

# Aliasing an alias is allowed
a: &a value
b: &b *a
c: *b   # resolves to "value"

覆盖锚定值

别名(*name)替换整个节点——你不能部分覆盖它。要覆盖锚定映射的单个键,使用合并键 << 合并锚定映射,然后添加或覆盖键。这是配置组合的典型模式。

yaml
# Aliases themselves can't be partially overridden —
# an alias replaces the whole node. Use << (merge)
# to combine an anchored mapping with overrides.
defaults: &defaults
  timeout: 30
  retries: 3
  log_level: info

production:
  <<: *defaults
  log_level: warn      # override
  host: prod.example.com   # add

# Result:
# production:
#   timeout: 30
#   retries: 3
#   log_level: warn
#   host: prod.example.com

# Without <<, an alias replaces the whole node:
wrong:
  thing: *defaults
  # thing is the FULL defaults mapping — can't override

序列中的锚点

锚点可以标记整个序列、单个序列项或序列内的映射。<< 合并模式在相似记录序列(例如服务定义)中特别有用,可共享公共字段。用 - &name value 锚定单个项。

yaml
# Anchors work on sequence items too
common: &common
  enabled: true
  log: stdout

services:
  - name: web
    <<: *common
    port: 80
  - name: api
    <<: *common
    port: 8080
  - name: worker
    <<: *common
    queue: jobs

# Anchoring a whole sequence
endpoints: &endpoints
  - /health
  - /metrics
  - /ready

monitoring:
  checks: *endpoints

# Anchoring individual sequence items
list:
  - &first one
  - two
  - *first   # "one" again

锚点注意事项

锚点有注意事项:别名共享标识(在可变解析器中修改一个会影响所有引用),它们增加认知开销,大多数序列化器(包括 JSON 输出)将它们展开为普通副本。将锚点用于真正的重用(模板、默认值),而非微优化。

yaml
# Caveat 1: shared identity can surprise you
defaults: &defaults
  list: [1, 2, 3]

a: *defaults
b: *defaults
# In mutable parsers, a.list and b.list are the SAME list.
# Editing a.list also changes b.list.

# Caveat 2: anchors add cognitive overhead
# Overuse makes YAML hard to read and trace.

# Caveat 3: not all tools preserve anchors on output
# Many serializers expand aliases to plain copies.

# Caveat 4: JSON output loses anchors entirely
# (JSON has no anchor concept).

# Caveat 5: anchors don't work across documents
---
doc1: &x value
use1: *x
---
doc2: *x   # ERROR

# Prefer anchors for genuine reuse, not micro-optimization.
07

合并键 (<<)

基本合并 (<<)

合并键 << 接受一个映射(通常是别名)并将其键值对合并到当前映射中。当前映射中显式设置的键覆盖合并的键。这是 YAML 中配置继承的典型模式。

yaml
# << merges a referenced mapping into the current one
defaults: &defaults
  adapter: postgres
  pool: 5
  timeout: 5000

development:
  <<: *defaults
  database: myapp_dev
  host: localhost

production:
  <<: *defaults
  database: myapp_prod
  host: db.example.com
  pool: 20      # override

# Result for development:
# adapter: postgres
# pool: 5
# timeout: 5000
# database: myapp_dev
# host: localhost

多重合并

<< 可通过传递别名的流序列([*a, *b, *c])合并多个映射。合并从左到右应用;对于重复键,后面的合并覆盖前面的。映射中的显式键始终优先于任何合并键。也允许多个 << 条目。

yaml
# << can merge multiple mappings (sequence value)
base: &base
  logging: info
  retries: 3

security: &security
  auth: required
  tls: true

service: &service
  port: 8080
  host: 0.0.0.0

# Merge multiple in one go
production:
  <<: [*base, *security, *service]
  environment: prod
  port: 80        # override service.port

# Later merges override earlier ones for the same key.
# Explicit keys always win over merges.

# You can also have multiple << entries:
combined:
  <<: *base
  <<: *security
  custom: value

覆盖与优先级

合并优先级:映射中的显式键始终优先。在合并的映射中,序列中后面的覆盖前面同名的键。利用这一点清晰地分层默认值 → 角色特定 → 实例特定的覆盖。

yaml
# Explicit keys override merged keys
template: &template
  port: 8080
  log: info
  timeout: 30

instance:
  <<: *template
  port: 9090        # overrides template.port
  log: debug        # overrides template.log
  host: example.com # added

# Final: {port: 9090, log: debug, timeout: 30, host: example.com}

# Precedence: explicit > later merge > earlier merge
a: &a
  k: from_a
  x: 1
b: &b
  k: from_b
  y: 2

result:
  <<: [*a, *b]
  k: explicit   # wins over both a and b
# Final: {k: explicit, x: 1, y: 2}

锚点合并模式

合并+锚点模式是 Docker Compose 和类似配置文件的看家本领:定义一个 defaults 锚点,然后每个服务合并它并覆盖单个字段。这让配置保持 DRY,同时不牺牲每个服务的自定义。

yaml
# Common pattern: anchor templates, merge + override
defaults: &defaults
  image: nginx:1.25
  restart: always
  logging:
    driver: json-file

services:
  web:
    <<: *defaults
    ports: ["80:80"]
  api:
    <<: *defaults
    image: node:20      # override
    ports: ["8080:8080"]
    environment:
      NODE_ENV: production
  worker:
    <<: *defaults
    image: worker:latest
    command: ["./run.sh"]

# This is the bread-and-butter of Docker Compose files
# — share base config, override per service.

合并限制

合并键 << 仅适用于映射——序列和标量不能合并。合并是浅层的:当前节点中的嵌套映射完全替换合并的映射(无深度合并)。要进行深度合并,重构数据或在应用代码中处理。

yaml
# << only works for MAPPINGS, not sequences or scalars
list_template: &list [1, 2, 3]

# This does NOT merge list items:
combined:
  <<: *list      # WRONG: *list is a sequence, not a mapping
  extra: value   # parse error or unexpected behavior

# Deep merge is NOT supported
parent: &parent
  nested:
    a: 1
    b: 2

child:
  <<: *parent
  nested:
    c: 3   # REPLACES parent.nested entirely
# Result: {nested: {c: 3}}  — NOT {nested: {a:1, b:2, c:3}}

# To deep-merge, you must do it in application code
# or restructure the data.

合并键状态与弃用

<< 合并键是 YAML 1.1 的扩展,已从 YAML 1.2 核心规范中移除。实际上,大多数解析器仍支持它(PyYAML、js-yaml、ruamel、SnakeYAML),但严格的 1.2 解析器会拒绝它。今天可使用它,但要知道它是非标准的;对于新格式,考虑具有真正继承的配置语言(Cue、Jsonnet、Dhall)。

yaml
# IMPORTANT: the << merge key was REMOVED in YAML 1.2
# It was a YAML 1.1 feature.

# In practice, many parsers still support it:
# - PyYAML: supports (1.1 mode)
# - js-yaml: supports (opt-in)
# - ruamel.yaml: supports
# - SnakeYAML: supports
# - LibYAML: supports

# But strict YAML 1.2 parsers reject it:
# new_yaml_1_2_strict:
#   <<: *template    # ERROR in strict 1.2

# Recommendation:
# - Use << for now (widely supported)
# - Document that your files rely on it
# - For new formats, consider composition in code
#   or a config language with proper inheritance
#   (e.g. Dhall, Cue, Jsonnet)

# Alternative without <<:
# just duplicate or use application-level templating
08

类型标签

内置标签

内置标签使用 !! 前缀(tag:yaml.org,2002: 的简写)。它们强制特定类型,无论模式推断如何。常见标签:!!str、!!int、!!float、!!bool、!!null、!!binary、!!map、!!seq、!!omap、!!set、!!timestamp。当推断可能出错时很有用。

yaml
# YAML tags use !! prefix for built-in types
# (the long form is tag:yaml.org,2002:)

# Common built-in tags
str: !!str hello
int: !!int 42
float: !!float 3.14
bool: !!bool true
null: !!null null
binary: !!binary "SGVsbG8="
map: !!map {a: 1}
seq: !!seq [1, 2]
omap: !!omap
  - a: 1
  - b: 2
set: !!set
  ? item1
  ? item2
timestamp: !!timestamp 2024-01-01T00:00:00Z
merge: !!merge "<<"   # the merge key tag

# Tags force a specific type regardless of inference
forced_str: !!str 42        # "42" string
forced_int: !!int "42"      # 42 integer
forced_bool: !!bool "yes"   # true

强制类型

标签强制特定类型,覆盖模式推断。最常见的用途是 !!str,防止看起来像数字/布尔值/日期的字符串被强制转换。!!int 和 !!float 可解析数字字符串。当宿主应用程序需要特定类型时,标签是必不可少的。

yaml
# Without tags, type depends on schema inference
value: 42          # integer (Core schema)
value: "42"        # string (quoted)
value: !!str 42    # string (forced by tag)

# Common forcing scenarios
version: !!str 1.0       # "1.0" string, not float
phone: !!str 5551234     # string, not int
answer: !!str yes        # string, not bool (1.1)
region: !!str no         # string "no", not False (1.1)

# Force numeric parsing from strings
count: !!int "100"       # 100 integer
ratio: !!float "0.5"     # 0.5 float

# Force bool from strings
enabled: !!bool "yes"    # true
enabled: !!bool "off"    # false

# Force null
empty: !!null ""         # null
empty: !!null "~"        # null

自定义标签

自定义标签使用单个 ! 前缀,由应用程序定义。宿主应用程序必须为每个标签注册构造器。安全加载器(推荐)默认拒绝未知标签——必须显式选择加入。库特定标签(如 PyYAML 的 python/object)可能很危险,被安全加载器禁用。

yaml
# Application-specific tags use ! prefix
# The application's YAML loader must handle them

# Custom tag (application-defined)
person: !person
  name: Alice
  age: 30

# Tag with arguments
color: !color rgb(255, 0, 0)
point: !point [10, 20]

# Library-specific tags (PyYAML example)
# python/object/apply:module.func args
sorted_set: !!python/object/apply:sorted
  args:
    - [3, 1, 2]

# Ruby-specific (Psych)
# !ruby/object:ClassName

# Custom tags require custom constructors in code.
# Unknown tags usually cause errors in safe loaders.

本地与逐字标签

!prefix 用于本地标签(应用程序定义),!! 是 yaml.org 全局标签的简写,!<uri> 是逐字(无解析),!prefix!short 使用 %TAG 声明的前缀以简化。本地标签最常见;逐字标签对跨工具模式很重要。

yaml
# ! prefix — local tag (application-specific, no URI)
data: !myapp/data
  value: 42

# !! prefix — global built-in tag (resolved to yaml.org)
str: !!str hello

# !<full-uri> — verbatim tag (exact URI, no resolution)
binary: !<tag:example.com,2024:binary> "data"

# !tag!short — tag with prefix (resolved via %TAG directive)
%TAG !e! tag:example.com,2024:
data: !e!custom value
# resolves to tag:example.com,2024:custom

# %TAG directives declare prefixes
%TAG !yaml! tag:yaml.org,2002:
value: !yaml!str 42   # same as !!str

# Local tags are most common in app-specific YAML
# Verbatim tags are useful for cross-tool interoperability

标签解析

标签解析取决于模式。Failsafe(最小)将所有内容视为字符串,除非加标签。JSON 模式添加 int/float/bool/null。Core 模式(YAML 1.2 默认)用更宽松的匹配扩展 JSON。显式标签(!!)始终覆盖模式解析。

yaml
# Tags are resolved by the schema (Core, JSON, Failsafe)
# Without explicit tags, the schema infers types

# Failsafe schema — only str, map, seq (no inference)
# Everything is a string unless tagged
failsafe_value: 42       # str "42"
failsafe_tagged: !!int 42  # int 42

# JSON schema — str, int, float, bool, null, map, seq
json_value: 42           # int
json_str: "42"           # str

# Core schema (default for YAML 1.2) — like JSON but
# with more lenient parsing (yes/no not bool, etc.)
core_value: 42           # int
core_bool: true          # bool
core_str: hello          # str

# Explicit tags override schema resolution
forced: !!str true       # str "true" (even in Core)

应用特定标签

应用特定标签允许在 YAML 中嵌入领域对象(Money、DateRange 等)。加载器必须为每个标签注册构造器。始终使用默认拒绝未知标签的 safe_loaders——绝不让不受信任的 YAML 调用任意构造器(典型的反序列化漏洞)。

yaml
# Define your own tags for domain objects
# Example: a config loader for a custom app

# Tag a node as a "Money" object
price: !money "USD 19.99"

# Tag a node as a "DateRange"
range: !dateRange
  start: 2024-01-01
  end: 2024-12-31

# Tag nodes for custom parsing/validation
settings: !strict
  host: localhost
  port: 8080

# PyYAML example: register a constructor
# class MoneyLoader:
#     def __init__(self, value):
#         self.currency, self.amount = value.split()
#         self.amount = float(self.amount)
#
# yaml.add_constructor('!money', lambda l, n:
#     MoneyLoader(l.construct_scalar(n)))

# Safe loaders reject unknown tags — opt-in explicitly
# to avoid processing untrusted input with side effects.
09

文档标记与指令

单个文档

单个 YAML 文档无需任何标记即可工作。开头的 --- 是一种约定,用于表示'这是 YAML'并与纯文本区分开。... 结束标记是可选的,单文档文件中很少使用。

yaml
# A single YAML document needs no markers
key: value
list:
  - a
  - b

# But a leading --- is a common convention
# to mark "this is YAML, not plain text"
---
key: value

# The trailing ... (end marker) is optional
# for a single document
key: value
...

多个文档(流)

YAML 流是由 --- 分隔的文档序列。每个文档独立解析。流在 Kubernetes(每个文件多个对象)、静态站点生成器和日志文件中很常见。在解析器中使用 load_all() 或等效方法迭代文档。

yaml
# A YAML stream is multiple documents separated by ---
---
name: first
value: 1
---
name: second
value: 2
---
name: third
value: 3

# Each document is independent — anchors don't cross
# Use a parser's load_all() / load_multi() to read them
# Python (PyYAML):
#   for doc in yaml.safe_load_all(stream):
#       print(doc)

# Common use cases:
# - Kubernetes: multiple objects in one file
# - Blog generators: front matter + content
# - Log files: one record per document
# - CI/CD: multi-step pipelines

文档结束标记 (...)

... 标记显式结束文档。在大多数情况下是可选的,但当内容可能有歧义时(例如包含 --- 的字面块)很有用。它也向流式解析器发出文档结束信号。手写 YAML 中很少见,但程序输出中常见。

yaml
# ... explicitly ends a document
---
key: value
...
# Anything after ... is a new document or stream end

# Useful when a document's content might be ambiguous
# without an explicit end:
---
message: |
  Some content
  ---
  that looks like a separator
...
---
next: document

# Without ... the parser might think the ---
# inside the literal block is a document separator
# (though block scalars usually prevent this)

# ... is also useful in programmatic output to flush

指令

指令出现在文档内容之前并配置解析器。%YAML 设置版本;%TAG 声明标签前缀快捷方式。指令是文档作用域的——仅应用于下一个文档。大多数配置文件省略指令,依赖解析器默认值(通常是 1.1 或 1.2 双模式)。

yaml
# Directives appear at the very top, before content
# %YAML — declare YAML version
%YAML 1.2
---
key: value

# %TAG — declare a tag prefix
%TAG !e! tag:example.com,2024:
---
data: !e!custom value

# Multiple directives are allowed
%YAML 1.2
%TAG !e! tag:example.com,2024:
%TAG !yaml! tag:yaml.org,2002:
---
value: !yaml!str hello

# Directives are document-scoped — they apply to the
# next document only and must be repeated for each.
# Most config files omit directives and use defaults.

文档流示例

文档流被广泛使用。Kubernetes 用它在一个文件中捆绑多个对象(Namespace、Deployment、Service)。Helm 模板、CI 日志和静态站点生成器(前置元数据 + 内容)也使用流。每个文档独立,可以有自己的指令。

yaml
# Kubernetes multi-object file
---
apiVersion: v1
kind: Namespace
metadata:
  name: production
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: production
spec:
  replicas: 3
---
apiVersion: v1
kind: Service
metadata:
  name: api
  namespace: production
spec:
  ports:
    - port: 80

# Helm chart templates: stream of objects
# CI logs: one event per document
# Static site generator: front matter + body

边界陷阱

以 --- 开头(在第 0 列)的行始终是文档分隔符(块标量外)。在块标量(|、>)内,--- 是字面内容。要在值中包含第 0 列的 ---,缩进块或先用 ... 结束文档。同一行带内容的 --- 是文档开始标记,不是分隔符。

yaml
# A line starting with --- is ALWAYS a document separator
# (when at column 0). This can surprise you:

---
script: |
  echo hello
  ---  # this is INSIDE the block scalar — safe
next: value

# But this is NOT safe:
---
content: |
  line1
---
mistaken: this is a NEW document, not part of content!

# Inside block scalars, --- is literal (good).
# Outside, --- at column 0 always separates documents.

# Workaround for content that must contain --- at col 0:
# indent the block, or use ... to end the doc first.

# Also: a bare --- line with content on the same line
# is the document start marker, NOT a separator
--- key: value   # this is one document
10

流样式

流序列

流序列使用 [],行为类似 JSON 数组。内部空白和换行很灵活。与 JSON 不同,不允许尾随逗号。适用于简短、密集的列表。包含特殊字符(逗号、方括号、冒号)的项需加引号。

yaml
# Flow sequences use [item1, item2, ...]
numbers: [1, 2, 3]
names: [Alice, Bob, Charlie]

# Empty
empty: []

# Nested flow sequences
matrix: [[1, 2], [3, 4]]

# Flow sequence of mappings
points: [{x: 1, y: 2}, {x: 3, y: 4}]

# Multi-line flow sequence (whitespace is flexible)
long: [
  alpha,
  beta,
  gamma
]

# Quoting inside flow
mixed: ["hello", 'world', 42, true]

# NO trailing comma
bad: [1, 2, 3,]   # parse error

流映射

流映射使用 {},行为类似 JSON 对象。包含特殊字符的键可以加引号。空白很灵活。禁止尾随逗号(与 JSON 不同)。流映射用于简短、密集的数据;块样式对较大结构更易读。

yaml
# Flow mappings use {key: value, ...}
point: {x: 10, y: 20}
config: {host: localhost, port: 8080}

# Empty mapping
empty: {}

# Nested flow mappings
nested: {a: {b: {c: value}}}

# Quoted keys (needed for special chars)
quoted: {"key with space": value, "weird:key": 1}

# Multi-line flow mapping
config: {
  host: localhost,
  port: 8080,
  debug: true
}

# Flow mapping with flow sequence value
mixed: {nums: [1, 2, 3], name: test}

# NO trailing comma
bad: {a: 1, b: 2,}   # parse error

混合流与块

流和块样式可在文档中自由混合。实用法则:对结构元素使用块样式(更好的缩进可读性),对简短内联值(标量列表、小映射)使用流样式。避免深度嵌套的流——它很快变得不可读。

yaml
# Mix flow and block styles freely
config:
  name: myapp
  ports: [80, 443]        # flow sequence in block mapping
  endpoints: {api: /v1, web: /}  # flow mapping in block

# Block inside flow (less common but valid)
flow_with_block: [{name: Alice, roles: [admin, user]}, {name: Bob}]

# Flow at top level
[top, level, flow]

# Block at top level
- block
- sequence

# Pragmatic rule:
# - Block for structure (readability)
# - Flow for short inline values
# - Avoid deeply nested flow — it becomes unreadable

嵌套流

流样式可任意深度嵌套,但每层增加的方括号/花括号变得难以阅读。嵌套 1-2 层后,切换到块样式以提高可读性。深度嵌套流的块等效形式对人类维护者几乎总是更清晰。

yaml
# Flow can nest arbitrarily
data: {users: [{name: Alice, tags: [a, b]}, {name: Bob}]}

# Equivalent block:
# data:
#   users:
#     - name: Alice
#       tags:
#         - a
#         - b
#     - name: Bob

# Multi-line nested flow (rare but valid)
matrix: {
  rows: [
    [1, 2, 3],
    [4, 5, 6]
  ],
  cols: 3
}

# Recommendation: switch to block style after 1-2 levels
# of nesting — flow becomes a "wall of brackets".

何时使用流样式

对简短标量列表、小查找映射、单行记录和 JSON 兼容输出使用流样式。避免对长列表(难以扫描)、深度嵌套结构(方括号墙)或需要内联注释的内容使用流。有疑问时,优先使用块样式以提高可读性。

yaml
# Good uses of flow style:

# 1. Short lists of scalars
ports: [80, 443, 8080]
colors: [red, green, blue]

# 2. Small lookup maps
codes: {200: OK, 404: NotFound, 500: Error}

# 3. Single-line records in a sequence
coordinates:
  - {x: 1, y: 2}
  - {x: 3, y: 4}
  - {x: 5, y: 6}

# 4. JSON-compatible output (flow is JSON-compatible)

# Bad uses of flow style:

# 1. Long lists (use block)
#   ports: [80, 443, 8080, 8443, 9000, 9090, 9091, ...]  # hard to read

# 2. Deeply nested (use block)
#   {a: {b: {c: {d: {e: value}}}}}  # unreadable

# 3. Anything with comments (flow doesn't allow inline comments well)

流样式注意事项

流样式有注意事项:特殊字符(逗号、冒号、方括号)需加引号;无尾随逗号;注释很别扭;无 | 或 > 块标量(在双引号字符串中使用 \n);单行更改更难 diff。块样式对版本控制的配置更友好。

yaml
# Caveat 1: special characters need quotes in flow
flow: [a, b, "c,d", "d:e"]   # commas and colons need quoting
block:
  - a
  - b
  - c,d      # no quotes needed in block style
  - d:e      # no quotes needed in block style

# Caveat 2: no trailing commas
bad: [1, 2, 3,]   # error

# Caveat 3: comments are awkward in flow
config: {
  host: localhost,  # comment allowed but clunky
  port: 8080
}

# Caveat 4: flow style has no multi-line strings
# (no | or > inside flow)
# You can use \n escapes in double quotes:
text: "line1\nline2"

# Caveat 5: flow is harder to diff in version control
# (one-line changes vs whole-block changes)
11

YAML 与 JSON 转换

JSON 是有效的 YAML

YAML 1.2 的设计使 JSON 成为严格子集——任何 JSON 文档都是有效的 YAML。大多数 YAML 解析器乐于读取 JSON。反之不然:YAML 的特性如注释、锚点、多文档流和块标量不是有效的 JSON。

yaml
# JSON is a strict subset of YAML 1.2
# Any JSON file is valid YAML

# This is valid YAML (it's JSON):
{"name": "Alice", "age": 30, "hobbies": ["a", "b"]}

# So is this multi-line JSON:
{
  "name": "Alice",
  "age": 30,
  "hobbies": ["a", "b"]
}

# Parsers usually accept JSON files as YAML
# Python: yaml.safe_load(json_string)  # works
# JS:     YAML.load(json_string)       # works
# Go:     yaml.Unmarshal(json_bytes, &v)  # works (yaml.v3)

# But the reverse is NOT always true:
# YAML with comments, anchors, multi-docs is NOT valid JSON

等效表示

JSON、YAML 块和 YAML 流都可以表示相同的数据。JSON 最可移植(每种语言都有 JSON 解析器)。YAML 块对人类最易读。YAML 流是紧凑的中间选择。根据受众选择:工具(JSON)还是人类(YAML 块)。

yaml
# JSON:
{"name": "Alice", "age": 30, "hobbies": ["a", "b"]}

# YAML block:
name: Alice
age: 30
hobbies:
  - a
  - b

# YAML flow (closest to JSON):
{name: Alice, age: 30, hobbies: [a, b]}

# YAML with quotes (JSON-compatible):
{"name": "Alice", "age": 30, "hobbies": ["a", "b"]}

# All four parse to the same data structure.
# Pick the style that fits your use case:
# - JSON for interop and strict tools
# - YAML block for human-edited config
# - YAML flow for compact inline data

JSON 转 YAML

通过解析 JSON 然后以 default_flow_style=False(块样式)转储为 YAML 来转换。JSON 的强制引号在 YAML 不需要的地方被去掉。注意看起来像数字/布尔值的键——它们在 YAML 中可能需要加引号以保留字符串类型。

yaml
# Convert JSON to YAML using a parser
# Python:
# import json, yaml
# data = json.loads(json_string)
# yaml_str = yaml.safe_dump(data, default_flow_style=False, sort_keys=False)
#
# JS:
# const data = JSON.parse(jsonString)
# const yamlStr = yaml.dump(data)

# Example input JSON:
# {"server": {"host": "localhost", "port": 8080}, "debug": true}

# Output YAML:
# server:
#   host: localhost
#   port: 8080
# debug: true

# Conversion considerations:
# - JSON keys are always quoted -> YAML usually unquotes them
# - JSON arrays -> YAML block sequences (with default_flow_style=False)
# - JSON strings with special chars may need quoting
# - JSON null -> YAML null (or ~)
# - JSON booleans/numbers preserved

YAML 转 JSON

通过解析然后 JSON 序列化来转换。注意事项:注释和标签丢失;锚点展开为普通副本;多文档流需要变成 JSON 数组;YAML 日期可能变成字符串。yq 等工具使命令行操作变得简单。

yaml
# Convert YAML to JSON
# Python:
# import json, yaml
# data = yaml.safe_load(yaml_string)
# json_str = json.dumps(data, indent=2)

# JS:
# const data = yaml.load(yamlString)
# const jsonStr = JSON.stringify(data, null, 2)

# Command-line tools:
# yq -o=json '.config' file.yaml        # mikefarah/yq
# ruby -ryaml -rjson -e 'puts JSON.generate(YAML.load(STDIN.read))' < file.yaml
# python -c 'import json,sys,yaml; print(json.dumps(yaml.safe_load(sys.stdin)))'

# Caveats when converting YAML to JSON:
# - YAML comments are LOST (JSON has no comments)
# - YAML anchors are EXPANDED to plain copies
# - YAML multi-doc streams become a JSON array (or fail)
# - YAML tags (!!) are lost or cause errors
# - YAML dates may become strings (JSON has no date type)

JSON 兼容子集

YAML 的 JSON 兼容子集避免注释、锚点、多文档、标签、块标量以及看起来像其他类型的普通字符串。停留在此子集可保证到 JSON 的干净往返。当 YAML 和 JSON 消费者读取相同数据,或严格类型保留很重要时很有用。

yaml
# To produce YAML that round-trips to JSON cleanly,
# stay within the JSON-compatible subset:

# 1. No comments
# 2. No anchors/aliases
# 3. No multi-doc streams
# 4. No tags
# 5. No block scalars (|, >) — use \n in double-quoted strings
# 6. Quote all string keys (JSON requires this)
# 7. No plain strings that look like other types

# JSON-compatible YAML:
{"name": "Alice", "age": 30, "active": true}
{"items": ["a", "b", "c"]}
{"nested": {"key": "value"}}

# Useful when you need both YAML and JSON consumers
# for the same data, or want strict type preservation.

# Many YAML dumpers have a "json-compatible" mode.

往返注意事项

往返 YAML(解析然后转储)通常会丢失注释、锚点名称、键顺序、引号样式和格式。ruamel.yaml(Python)有一种保留注释和顺序的往返模式。对于人工编辑的配置,绝不盲目用转储输出覆盖——先仔细 diff。

yaml
# Round-trip: YAML -> parse -> dump -> YAML
# Information that may be LOST:
# - Comments (most dumpers drop them)
# - Anchor names (dumpers may re-create or expand)
# - Key order (some dumpers sort keys)
# - Quoting style (dumpers choose their own)
# - Flow vs block style (dumpers usually pick one)
# - Whitespace and indentation amount
# - Multi-document structure (sometimes)
# - Tag prefixes and %TAG directives

# Tools that preserve style:
# - ruamel.yaml (Python): round-trip mode preserves comments/order
# - yaml.v3 (Go): preserves key order
# - prettier with yaml plugin: normalizes formatting

# For config files where humans edit:
# - Use ruamel.yaml round-trip mode
# - Or accept that dumps are "normalized"
# - Never overwrite human-edited files with dumper output
#   without diffing carefully
12

YAML 模式

Failsafe 模式

Failsafe 模式是最保守的——它只识别 str、map 和 seq。所有标量都是字符串,除非显式加标签。适用于不受信任的输入(无隐式强制转换意外)或作为自定义模式的基础。大多数生产加载器使用稍丰富的模式。

yaml
# Failsafe schema — the most conservative
# Recognizes ONLY three types: str, map, seq
# Everything else is a string

# Under Failsafe:
value: 42           # str "42" (NOT int)
flag: true          # str "true" (NOT bool)
empty: null         # str "null" (NOT null)
date: 2024-01-01    # str "2024-01-01" (NOT date)

# Use explicit tags to get other types
int_value: !!int 42
bool_value: !!bool true
null_value: !!null null

# When to use Failsafe:
# - Untrusted input (no implicit type coercion)
# - When you want strings only
# - As a base for custom schemas

# PyYAML: SafeLoader uses a schema close to Failsafe
# but with int/float/bool/null inference.

JSON 模式

JSON 模式完全匹配 JSON 的类型系统:str、int、float、bool、null、map、seq。它比 Core 模式更严格——无八进制/二进制字面量,无下划线分隔符,只有 true/false 作为布尔值,无日期推断。用于 JSON 互操作或严格类型行为。

yaml
# JSON schema — same type system as JSON
# Recognizes: str, int, float, bool, null, map, seq

# Under JSON schema:
int: 42             # int
float: 3.14         # float
str: hello          # str
bool: true          # bool
null_val: null      # null
list: [1, 2, 3]     # seq
map: {a: 1}         # map

# Differences from Core schema:
# - Stricter number parsing (no 0o octal, no _ separators)
# - Booleans are only true/false (no yes/no)
# - No date/timestamp inference

# When to use JSON schema:
# - Interop with JSON tools
# - Strict type behavior
# - When you want JSON's type semantics

Core 模式

Core 模式是 YAML 1.2 的默认模式。它用宽松解析扩展 JSON:十六进制(0x)、八进制(0o)、二进制(0b)、数字中的下划线、.inf/.nan 浮点数。布尔值只有 true/false(不是 yes/no)。日期是字符串(无推断)。大多数现代解析器默认 Core 或 1.1 兼容变体。

yaml
# Core schema — the DEFAULT for YAML 1.2
# Extends JSON schema with more lenient scalar resolution

# Under Core schema:
int: 42             # int
neg: -17            # int
hex: 0xFF           # int (255)
oct: 0o17           # int (15)  [YAML 1.2 syntax]
bin: 0b1010         # int (10)
float: 3.14         # float
exp: 1.0e+3         # float
inf: .inf           # float
nan: .nan           # float
bool: true          # bool (only true/false, not yes/no)
null: null          # null (also ~, empty)
str: hello          # str
date: 2024-01-01    # str (Core does NOT infer dates)

# Differences from YAML 1.1:
# - yes/no/on/off are STRINGS (not booleans)
# - 017 is a string (octal is 0o17 in 1.2)
# - Dates are strings (no implicit inference)

# Most modern parsers default to Core or close to it.

YAML 1.1 与 1.2

YAML 1.1(PyYAML 默认)将 yes/no/on/off 视为布尔值,前导 0 数字视为八进制——著名的 bug 来源(挪威 'no' → false)。YAML 1.2(当前规范)将布尔值限制为 true/false,并使用 0o 表示八进制。始终检查解析器的默认值并给有歧义的字符串加引号。

yaml
# YAML 1.1 (older, used by PyYAML by default)
bool_1_1: yes       # true (yes/no/on/off are booleans)
octal_1_1: 017      # 15 (leading 0 = octal)
sexagesimal: 1:30   # 90 (base-60!) - obscure feature

# YAML 1.2 (current spec)
bool_1_2: yes       # str "yes" (only true/false are bool)
octal_1_2: 0o17     # 15 (explicit 0o prefix)
no_sexagesimal: 1:30 # str "1:30"

# Practical impact:
# - "no" (Norway) becomes False in PyYAML (1.1)
# - "010" becomes 8 in PyYAML, "010" in 1.2
# - "on"/"off" become True/False in 1.1, strings in 1.2

# PyYAML: defaults to 1.1 behavior
# ruamel.yaml: defaults to 1.2 (with 1.1 compatibility options)
# js-yaml: defaults to 1.1 with 1.2 options
# Go yaml.v3: defaults to 1.2-ish

# Always check your parser's default schema!

类型解析差异

类型解析在 YAML 1.1 和 1.2 解析器之间不同:yes/no/on/off、前导零数字和六十进制字面量解析不同。通过给有歧义的字符串加引号、用 %YAML 固定版本、使用显式标签以及针对所有目标解析器测试来缓解。

yaml
# Same YAML, different parsers, different types:
value: yes

# PyYAML (1.1):        True (bool)
# js-yaml (1.1):       true (bool)
# ruamel (1.2):        "yes" (str)
# Go yaml.v3 (1.2):    "yes" (str)
# Strict 1.2:          "yes" (str)

value: 010

# PyYAML (1.1):        8 (int, octal)
# ruamel (1.2):        "010" (str)
# Go yaml.v3 (1.2):    "010" (str)

value: 1.0

# All:                 1.0 (float) - consistent

value: true

# All:                 true (bool) - consistent

# Mitigation strategies:
# 1. Quote strings that look like other types
# 2. Pin the YAML version with %YAML 1.2
# 3. Use explicit tags (!!) for critical values
# 4. Test against all target parsers

选择模式

按用例选择模式:新项目用 1.2 Core(可预测),PyYAML 互操作用 1.1(或给有歧义的字符串加引号),不受信任输入用 Failsafe/JSON,JSON 互操作用 JSON 模式。对于配置文件,1.2 Core 加给有歧义的字符串("no"、"010" 等)加引号是最安全的组合。

yaml
# Recommendations for choosing a schema:

# 1. For NEW projects: use YAML 1.2 Core schema
#    - More predictable (no yes/no surprises)
#    - Modern parsers support it
#    - ruamel.yaml, Go yaml.v3 default to it

# 2. For INTEROP with existing PyYAML code:
#    - Either accept 1.1 semantics
#    - Or use ruamel.yaml with 1.2 + preserve 1.1 features
#    - Quote all ambiguous strings explicitly

# 3. For UNTRUSTED input: use Failsafe or JSON schema
#    - No implicit type coercion
#    - Smaller attack surface
#    - Predictable behavior

# 4. For CONFIG FILES: use 1.2 Core + quote ambiguous strings
#    - "no", "yes", "on", "off", "010", "1.0" should be quoted
#    - Document your schema choice
#    - Lint with yamllint

# 5. For JSON INTEROP: use JSON schema
#    - Same types as JSON
#    - Round-trips cleanly
13

YAML 校验与验证

yamllint

yamllint 是标准 YAML linter(Python)。它捕获语法错误、缩进问题、尾随空格、缺失文档标记等。在 CI 和 pre-commit 钩子中使用它以尽早捕获 YAML 问题。严格模式(-s)将警告视为错误。

yaml
# yamllint is the standard YAML linter
# Install: pip install yamllint

# Lint a file
# yamllint config.yaml

# Lint with strict mode (warnings = errors)
# yamllint -s config.yaml

# Output example:
# config.yaml
#   3:4       error    mapping values are not allowed here
#   5:1       error    syntax error: found character '	' that cannot start any token
#   8:3       warning  missing document start "---"
#   12:5      warning  comment is not indented like content

# Lint multiple files
# yamllint file1.yaml file2.yaml file3.yaml

# Lint a directory (recursive)
# yamllint -d path/to/dir

# Read from stdin
# cat config.yaml | yamllint -

校验配置

用 .yamllint.yml 配置 yamllint。扩展一个配置文件(default 或 strict),然后覆盖单个规则:缩进、行长度、引号样式、注释、尾随空格、文档标记。调整以匹配团队风格。在 CI 中运行以一致执行。

yaml
# .yamllint.yml — configuration file for yamllint
# Place in project root or ~/.config/yamllint/config

# Use a built-in profile
extends: default

# Or use a stricter profile
# extends: strict

# Override specific rules
rules:
  # Require document start marker
  document-start: disable
  # Enforce 2-space indentation
  indentation:
    spaces: 2
    indent-sequences: true
  # Max line length
  line-length:
    max: 120
    allow-non-breakable-words: true
  # Disallow trailing spaces
  trailing-spaces: enable
  # Require consistent quoting
  quoted-strings:
    quote-type: double
    required: only-when-needed
  # Disallow comments without space before #
  comments:
    require-starting-space: true
    min-spaces-from-content: 1

常见语法错误

常见 YAML 错误:用制表符缩进(只允许空格)、冒号后无空格、带冒号的未加引号字符串(被解析为映射)、流样式中的尾随逗号、不一致的缩进和重复键。linter 会自动捕获所有这些。

yaml
# Error: tab characters for indentation
# bad:
# 	key: value   <- tab not allowed
# Fix: use spaces only

# Error: colon without space
# bad:  key:value
# good: key: value

# Error: unquoted special characters
# bad:  url: http://example.com:8080   <- parsed as mapping
# good: url: "http://example.com:8080"

# Error: trailing comma in flow
# bad:  list: [1, 2, 3,]
# good: list: [1, 2, 3]

# Error: inconsistent indentation
# bad:
#   key1: value
#    key2: value   <- one extra space
# good:
#   key1: value
#   key2: value

# Error: duplicate keys (some parsers warn)
# bad:
#   key: first
#   key: second
# good: rename one

制表符与空格错误

制表符被严格禁止用于 YAML 结构缩进——解析错误的首要原因。字符串值(块标量、引号字符串)内的制表符是可以的。配置编辑器对 YAML 文件使用空格(expandtab)。用 grep -P '\t' 检测有问题的行。

yaml
# YAML FORBIDS tabs for indentation.
# This is the #1 cause of YAML parse errors.

# Symptom: parser error like
# "found character '\t' that cannot start any token"
# or "while scanning for the next token, found tab"

# Tabs are allowed INSIDE string values:
script: |
  #!/bin/bash
  if [ -f file ]; then   # this is fine (literal content)
  \techo found           # \t inside block scalar is OK

# But tabs for STRUCTURAL indentation are forbidden:
# bad:
# services:
# \tweb:
# \t\timage: nginx

# Fix: convert tabs to spaces
# Configure your editor to use spaces for YAML files
# VS Code: "editor.insertSpaces": true, "editor.tabSize": 2
# Vim: set expandtab shiftwidth=2

# Detect tabs:
# grep -P '\t' file.yaml   (finds offending lines)

重复键检测

映射中的重复键不被鼓励,是常见的 bug 来源(静默的后值生效覆盖)。yamllint 用 key-duplicates 规则(默认启用)检测它们。常见原因:复制粘贴错误和错误的合并冲突解决。始终在 CI 中 lint 以尽早捕获。

yaml
# Duplicate keys are technically discouraged
# but YAML 1.2 doesn't strictly forbid them.
# Behavior varies by parser (usually last-wins).

config:
  host: localhost
  port: 8080
  host: prod.example.com   # silent override!

# yamllint catches these:
#   4:3  error  duplication of key "host" in mapping

# Enable in .yamllint.yml:
# rules:
#   key-duplicates: enable

# Other tools that detect duplicates:
# - kubeval (for Kubernetes YAML)
# - yq (programmatic checks)
# - Custom schema validation (JSON Schema)

# Common cause: copy-paste errors, merge conflicts
# resolved incorrectly. Always lint in CI to catch.

模式验证

用 JSON Schema 验证 YAML 结构(因为 YAML 是 JSON 超集所以有效)。对于 Kubernetes,使用 kubeval 或 kubectl --dry-run。对于一般配置,使用 yamllint(语法/风格)加 jsonschema(结构)或 Cue(模式语言)。将验证接入 pre-commit 钩子和 CI。

yaml
# Validate YAML against a JSON Schema
# (JSON Schema works on YAML since YAML is a JSON superset)

# Python example with jsonschema + PyYAML:
# import json, yaml, jsonschema
# with open('schema.json') as f: schema = json.load(f)
# with open('config.yaml') as f: doc = yaml.safe_load(f)
# jsonschema.validate(doc, schema)

# Tools for Kubernetes YAML:
# kubeval            - validate against K8s schemas
# kubectl apply --dry-run=client -f file.yaml
# conftest           - policy-based validation (OPA)

# Tools for general YAML:
# yamllint           - syntax + style
# jsonschema-cli     - JSON Schema validation
# cue                - schema + validation language
# dataladen          - config validation

# Pre-commit hook example (.pre-commit-config.yaml):
# repos:
#   - repo: https://github.com/adrienverge/yamllint
#     rev: v1.32.0
#     hooks:
#       - id: yamllint
#         args: [-s]
14

Docker Compose YAML

服务定义

Docker Compose 文件定义多容器应用。顶层键是 version、services、volumes、networks、configs、secrets。每个服务定义 image/build、ports、volumes、environment、depends_on、重启策略。顶层声明的卷是命名的并持久化。

yaml
# docker-compose.yml — multi-container apps
version: "3.9"   # compose file format version

services:
  web:
    image: nginx:1.25
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    restart: unless-stopped
    depends_on:
      - api

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    environment:
      - NODE_ENV=production
      - DB_URL=postgres://db:5432/app
    ports:
      - "8080:8080"
    restart: always

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: always

volumes:
  db_data:

卷与网络

Compose 卷是绑定挂载(主机:容器)、命名卷(在顶层 volumes 中声明)或匿名卷。:ro 后缀使挂载只读。网络隔离服务;internal: true 阻止外部访问。命名卷跨容器重新创建持久化数据。

yaml
services:
  web:
    image: nginx
    volumes:
      # Bind mount: host:container
      - ./html:/usr/share/nginx/html:ro
      # Named volume: name:container
      - shared_data:/data
      # Anonymous volume
      - /tmp/cache
    networks:
      - frontend
      - backend

  db:
    image: postgres
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - backend

volumes:
  db_data:
    driver: local
  shared_data:
    driver: nfs
    driver_opts:
      type: nfs
      device: ":/path/to/share"

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true   # no external access

环境变量

Compose 支持三种环境变量样式:列表(KEY=value)、映射(KEY: value)和 env_file。${VAR} 在解析时从主机 shell 或 .env 文件插值;${VAR:-default} 提供默认值。用 $$ 转义字面 $。项目根目录中的 .env 文件自动加载。

yaml
services:
  api:
    image: myapi
    # Method 1: list of KEY=value
    environment:
      - NODE_ENV=production
      - LOG_LEVEL=info
    # Method 2: map (cleaner)
    environment:
      NODE_ENV: production
      LOG_LEVEL: info
    # Method 3: file
    env_file:
      - .env
      - .env.production
    # Interpolation from host environment
    environment:
      - API_KEY=${API_KEY}     # from shell
      - PORT=${PORT:-8080}      # default 8080

# .env file (auto-loaded by docker compose):
# API_KEY=abc123
# PORT=8080
# DB_URL=postgres://...

# Compose substitutes ${VAR} from host shell or .env
# Use $$ to escape (literal $):
# - TOKEN=price_$$5   # produces price_$5

依赖、健康检查与重启

depends_on 配合 condition: service_healthy 等待依赖的健康检查通过后再启动。健康检查使用 test(CMD 或 CMD-SHELL)、interval、timeout、retries、start_period。重启策略:no、always、unless-stopped(重启后存活但尊重手动停止)、on-failure。

yaml
services:
  api:
    image: myapi
    depends_on:
      db:
        condition: service_healthy   # wait for db healthy
      redis:
        condition: service_started
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  db:
    image: postgres
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

# restart policies:
# no              - never restart (default)
# always          - always restart on stop
# unless-stopped  - restart unless manually stopped
# on-failure      - restart only on non-zero exit

覆盖文件

Compose 在基础之上合并覆盖文件。docker-compose.override.yml 自动应用。映射深度合并(覆盖优先);序列通常被替换(不追加)——注意 ports/volumes。使用 -f 标志为环境特定配置指定显式文件顺序。

yaml
# docker-compose.yml — base
services:
  web:
    image: nginx
    ports:
      - "80:80"
    environment:
      - ENV=base

# docker-compose.override.yml — auto-applied on top
# (loaded automatically by 'docker compose up')
services:
  web:
    ports:
      - "8080:80"      # overrides "80:80"
    environment:
      - DEBUG=true     # added
      - ENV=override   # overrides ENV=base
    volumes:
      - ./dev.conf:/etc/nginx/conf.d/default.conf

# Multiple override files (-f flag):
# docker compose -f compose.yml -f compose.prod.yml -f compose.secrets.yml up

# Merge rules:
# - mappings: deep-merged (overrides win, others kept)
# - sequences: usually REPLACED (not appended) —
#   ports/depends_on/volumes are exceptions in some versions

# Use -f to specify explicit file order

配置文件与 Compose 特性

Profiles 允许用 --profile 标志选择加入服务(例如 debug、test、ci)。其他 v3 特性:init(正确的 PID 1 信号处理)、extends(从另一个服务继承)、configs 和 secrets(作为文件挂载)、deploy(Swarm 放置/副本/资源)。Profiles 非常适合用一个 compose 文件适配多个环境。

yaml
# Profiles — opt-in services
services:
  web:
    image: nginx
    # no profile: always started

  debug:
    image: busybox
    profiles: ["debug"]   # only with --profile debug

  loadtest:
    image: locust
    profiles: ["test", "ci"]

# Enable a profile:
# docker compose --profile debug up
# docker compose --profile test --profile ci up

# Compose file format features (v3.9+):
# - deploy: placement, replicas, resources (Swarm-only in v3)
# - configs: external config files
# - secrets: mounted secret files
# - extends: inherit from another service
# - init: run tini as PID 1

services:
  worker:
    image: worker
    init: true   # proper signal handling
    extends:
      service: base   # inherit fields
    configs:
      - config1
    secrets:
      - api_key

configs:
  config1:
    file: ./config.toml
secrets:
  api_key:
    file: ./api_key.txt
15

Kubernetes YAML

对象结构

每个 Kubernetes 对象都有 apiVersion、kind、metadata 和 spec。apiVersion 是 group/version(例如 apps/v1,核心为 v1)。metadata 包括 name、namespace、labels、annotations。spec 是期望状态——Kubernetes 努力使现实匹配 spec。status 由系统设置。

yaml
# Every Kubernetes object has the same 4 top-level fields
apiVersion: apps/v1        # API group + version
kind: Deployment           # object type
metadata:                  # identity
  name: my-app
  namespace: production
  labels:
    app: my-app
    tier: backend
  annotations:
    deployment.kubernetes.io/revision: "1"
spec:                      # desired state
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:1.0.0
          ports:
            - containerPort: 8080

# apiVersion + kind + metadata + spec is the universal shape
# (some objects use status too, but you don't set it)

标签与选择器

Labels 标识和分组对象;selectors 按标签查询。支持基于相等性(key=value)和基于集合(In、NotIn、Exists、DoesNotExist)的选择器。Annotations 保存非标识元数据(描述、联系信息、工具配置)。Labels 用于选择;annotations 用于附加信息。

yaml
# Labels — identify and group objects
metadata:
  labels:
    app: my-app
    tier: backend
    env: production
    version: "1.0"

# Selectors — query objects by labels
# Equality-based
selector:
  matchLabels:
    app: my-app
    env: production

# Set-based
selector:
  matchExpressions:
    - {key: tier, operator: In, values: [backend, api]}
    - {key: env, operator: NotIn, values: [dev]}
    - {key: version, operator: Exists}

# kubectl commands:
# kubectl get pods -l app=my-app
# kubectl get pods -l 'tier in (backend,api),env!=dev'
# kubectl get pods -l app=my-app -l env=production

# Annotations — non-identifying metadata
metadata:
  annotations:
    description: "My app deployment"
    contact: "[email protected]"

Pod 规格

Pod spec 定义容器、它们的镜像、端口、环境变量(字面量或来自 secrets/configmaps)、资源(requests/limits)、探针(readiness/liveness)、volumes 和 volumeMounts。Pod 是最小可部署单元,但通常通过 Deployment 创建,而非直接创建。

yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
    - name: app
      image: my-app:1.0
      ports:
        - containerPort: 8080
          name: http
      env:
        - name: LOG_LEVEL
          value: info
        - name: SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: secret-key
      resources:
        requests: {cpu: 100m, memory: 128Mi}
        limits: {cpu: 500m, memory: 512Mi}
      readinessProbe:
        httpGet: {path: /health, port: 8080}
        initialDelaySeconds: 5
        periodSeconds: 10
      volumeMounts:
        - name: config
          mountPath: /etc/config
  volumes:
    - name: config
      configMap:
        name: app-config
  restartPolicy: Always

Deployment

Deployment 管理 ReplicaSet 和 Pod。spec.replicas 设置期望数量。strategy.type 可以是 RollingUpdate(默认,由 maxSurge/maxUnavailable 控制)或 Recreate。Pod 模板(spec.template)定义如何创建 Pod。用 kubectl rollout status/undo/history 管理发布。

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:1.0.0
          ports:
            - containerPort: 8080
          resources:
            requests: {cpu: 100m, memory: 128Mi}
            limits: {cpu: 500m, memory: 512Mi}
          readinessProbe:
            httpGet: {path: /ready, port: 8080}
  # Rollback / history
  # kubectl rollout status deployment/my-app
  # kubectl rollout undo deployment/my-app
  # kubectl rollout history deployment/my-app

Service

Service 将 Pod 作为网络服务暴露。type ClusterIP(默认)仅内部;NodePort 在每个节点上暴露;LoadBalancer 配置云 LB;ExternalName 是 DNS CNAME。selector 将流量路由到匹配的 Pod。ports 将服务端口映射到容器 targetPort(数字或命名)。

yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: ClusterIP      # ClusterIP (default), NodePort, LoadBalancer, ExternalName
  selector:
    app: my-app        # routes to pods with these labels
  ports:
    - name: http
      port: 80         # service port
      targetPort: 8080 # container port (or named port)
      protocol: TCP
    - name: https
      port: 443
      targetPort: 8443

# NodePort: exposes on each node's IP at a static port (30000-32767)
# LoadBalancer: provisions a cloud LB
# ExternalName: CNAME to an external service

# Service types:
# ClusterIP    - internal cluster IP (default)
# NodePort     - exposes on node port + ClusterIP
# LoadBalancer - cloud LB + NodePort + ClusterIP
# ExternalName - DNS CNAME (no proxying)

ConfigMap 与 Secret

ConfigMaps 保存非敏感配置(字符串或文件);Secrets 保存敏感数据(在 data 中 base64 编码,或在 stringData 中为纯文本)。两者都通过环境变量(valueFrom)或作为卷挂载使用。Secrets 默认未加密——启用静态加密并限制 RBAC 访问。

yaml
# ConfigMap — non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  log_level: info
  max_connections: "100"
  config.yaml: |
    server:
      host: 0.0.0.0
      port: 8080
---
# Secret — sensitive data (base64-encoded)
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  # base64-encoded values
  api_key: c2VjcmV0LWtleS0xMjM=   # echo -n 'secret-key-123' | base64
  db_password: cGFzc3dvcmQ=
# Or use stringData for plain text (auto-encoded):
# stringData:
#   api_key: secret-key-123

# Consumed by pods:
# env:
#   - name: API_KEY
#     valueFrom:
#       secretKeyRef:
#         name: app-secrets
#         key: api_key
# volumes:
#   - name: config
#     configMap:
#       name: app-config
16

CI/CD 配置(GitHub Actions 与 GitLab CI)

GitHub Actions 工作流

GitHub Actions 工作流位于 .github/workflows/。on: 触发器(push、PR、schedule、workflow_dispatch)。jobs 包含 runs-on + steps。uses: 引用 actions,run: 执行 shell。表达式使用 ${{ }} 语法——在 YAML 模板字面量内必须转义为 \${{ }}。

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test
      - run: npm run build
        env:
          NODE_ENV: production

  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run lint

# ${{ }} is GitHub Actions expression syntax
# MUST be escaped as ${{ }} in YAML template literals
# (here we use literal text, no escaping needed)

GitLab CI 流水线

GitLab CI 位于 .gitlab-ci.yml。stages 定义流水线顺序。jobs 有 stage、image、script 和许多选项(only/except、rules、when、artifacts、environment)。变量使用 $VAR 语法(在 YAML 块标量中无需转义)。when: manual 需要点击才能部署。

yaml
# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  NODE_ENV: production
  IMAGE_TAG: $CI_COMMIT_SHORT_SHA

test:
  stage: test
  image: node:20
  script:
    - npm ci
    - npm test
  artifacts:
    reports:
      junit: test-results.xml
  coverage: '/Lines.*:\s(\d+\.\d+)\%/'

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t myapp:$IMAGE_TAG .
    - docker push myapp:$IMAGE_TAG
  only:
    - main

deploy:production:
  stage: deploy
  script:
    - ./deploy.sh
  environment:
    name: production
  only:
    - main
  when: manual   # require manual trigger

矩阵构建

矩阵构建用不同变量多次运行一个 job。GitHub Actions 使用带 include/exclude 的 strategy.matrix。GitLab 使用 parallel.matrix。fail-fast: false 防止一个失败取消同级。适用于跨平台、多版本测试。${{ }} 语法在 YAML 字面量中必须转义。

yaml
# GitHub Actions matrix
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node: [18, 20, 22]
        exclude:
          - os: windows-latest
            node: 18
        include:
          - os: ubuntu-latest
            node: 20
            experimental: true
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci
      - run: npm test

# GitLab CI matrix (parallel matrix):
# test:
#   image: node:${NODE_VERSION}
#   parallel:
#     matrix:
#       - NODE_VERSION: [18, 20, 22]
#         OS: [linux, macos]

可重用工作流与包含

两个系统都支持重用。GitHub Actions 使用可重用工作流(workflow_call 触发器),用 uses: 调用。GitLab 使用 include(project、local、template、remote)。重用减少了共享 CI 逻辑的重复。显式传递 inputs/secrets 以保持工作流可组合和可审计。

yaml
# GitHub Actions: reusable workflow
# .github/workflows/reusable.yml
on:
  workflow_call:
    inputs:
      node-version:
        required: true
        type: string
    secrets:
      token:
        required: true
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci
        env:
          TOKEN: ${{ secrets.token }}

# Caller:
# jobs:
#   build:
#     uses: ./.github/workflows/reusable.yml
#     with:
#       node-version: '20'
#     secrets:
#       token: ${{ secrets.MY_TOKEN }}

# GitLab CI: include
# include:
#   - project: 'ci/templates'
#     file: '/node.yml'
#   - local: '/.gitlab/extra.yml'
#   - template: Jobs/SAST.gitlab-ci.yml

CI 配置中的锚点

YAML 锚点减少 CI 配置中的重复。GitLab CI 大量使用它们:用 &anchor 定义隐藏 job(.template),然后用 <<: *anchor 合并。GitHub Actions 支持锚点但不鼓励跨 job 使用(优先使用可重用工作流)。锚点使配置 DRY 但更难追踪——明智地使用。

yaml
# YAML anchors reduce duplication in CI configs
# GitLab CI commonly uses anchors

.default_template: &default_template
  image: node:20
  before_script:
    - npm ci
  cache:
    paths:
      - node_modules/

test:
  <<: *default_template
  script:
    - npm test

lint:
  <<: *default_template
  script:
    - npm run lint

build:
  <<: *default_template
  script:
    - npm run build
  artifacts:
    paths:
      - dist/

# GitHub Actions supports anchors too (since 2022):
# jobs:
#   base: &base
#     runs-on: ubuntu-latest
#     steps:
#       - uses: actions/checkout@v4
#   test:
#     uses: ./.github/workflows/reusable.yml
#   # Anchors across jobs are limited — prefer reusable workflows

环境与密钥

GitHub Actions 环境控制部署(需要批准、限制分支)并持有环境范围的密钥。密钥通过 ${{ secrets.NAME }} 引用。GitLab 使用在 UI 中定义的 protected(分支限制)和 masked(日志隐藏)变量。绝不记录密钥——它们可能在错误输出中泄露。

yaml
# GitHub Actions: environments & secrets
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production        # requires approval if configured
      url: https://app.example.com
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: ./deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
          API_TOKEN: ${{ secrets.API_TOKEN }}
          ENV: production

# Secret scopes:
# - Repository secrets: available to all workflows
# - Environment secrets: only when job uses that environment
# - Organization secrets: shared across repos

# GitLab CI: protected/masked variables
# variables:
#   # defined in UI: Settings > CI/CD > Variables
#   # - Protected: only on protected branches/tags
#   # - Masked: hidden in job logs
# deploy:
#   script:
#     - ./deploy.sh
#   environment:
#     name: production
#   only:
#     - main
17

安全注意事项

反序列化风险

YAML 反序列化很危险,因为像 !!python/object/apply 这样的标签可以调用任意函数。PyYAML 的 yaml.load() 在 5.1 之前默认不安全(不受信任输入的代码执行)。对不受信任输入始终使用 yaml.safe_load()——它拒绝对象实例化标签。其他语言有类似危险(SnakeYAML、Psych)。

yaml
# YAML deserialization can be DANGEROUS.
# Some loaders let YAML instantiate arbitrary objects.

# DANGEROUS (PyYAML < 5.1, default was unsafe):
# data = yaml.load(untrusted_input)
# An attacker can craft YAML that runs arbitrary code:
# !!python/object/apply:os.system ["rm -rf /"]

# Other dangerous tags:
# !!python/object/apply:...     # call any Python function
# !!python/object/new:...       # instantiate any class
# !ruby/object:...               # Ruby object instantiation
# !java/object:...               # Java (SnakeYAML)

# Safe loaders reject these tags by default.
# Modern PyYAML (>= 5.1): yaml.load() requires a SafeLoader
# yaml.safe_load() is ALWAYS safe.

# Rule of thumb:
# - NEVER use yaml.load() on untrusted input
# - ALWAYS use yaml.safe_load() (or full_load on trusted files)
# - Or use a schema-validated loader

safe_load 与 load

始终使用 safe_load(Python)、safe_load(Ruby Psych)或默认安全模式(js-yaml、Go yaml.v3)。PyYAML 5.1+ 要求 yaml.load() 显式 Loader。full_load 允许标准标签但不允许任意对象——仅对受信任文件使用。安全加载器拒绝所有对象实例化标签。

yaml
# Python (PyYAML) — the classic safe/unsafe distinction

# SAFE: safe_load rejects all custom tags
import yaml
data = yaml.safe_load(untrusted_yaml)   # ALWAYS use this

# SAFE: load with SafeLoader (explicit)
data = yaml.load(untrusted_yaml, Loader=yaml.SafeLoader)

# FULL (trusted files only): full_load allows standard tags
# but not arbitrary python objects
data = yaml.full_load(trusted_yaml)

# UNSAFE: UnsafeLoader allows !!python/object — DANGEROUS
data = yaml.load(untrusted_yaml, Loader=yaml.UnsafeLoader)  # NEVER on untrusted

# PyYAML 5.1+ requires Loader argument for yaml.load()
# (raises error without it) — safety improvement.

# Other languages:
# JS (js-yaml): yaml.load(str) uses DEFAULT_SAFE_SCHEMA
# Go (yaml.v3): no object instantiation (safe by design)
# Ruby (Psych): Psych.safe_load is the safe variant
# Java (SnakeYAML): SafeConstructor for safe loading

本地标签利用

自定义标签(!include、!env、!file、!exec、!template)即使没有 !!python/object 也可被利用。读取文件的 !include 可通过路径遍历被欺骗读取 /etc/passwd 或 .ssh/id_rsa。审计每个自定义构造器:根据允许列表验证路径,拒绝绝对路径和 ../,沙箱化加载器。

yaml
# Even non-builtin tags can be exploited if your loader
# registers constructors that have side effects.

# Example: a "harmless" custom tag
# yaml.add_constructor('!include', include_file)
#
# If !include reads files from disk, an attacker can
# craft YAML to read sensitive files:
# config: !include /etc/passwd
# or:
# config: !include /etc/shadow
# or:
# config: !include ../../../.ssh/id_rsa

# Mitigation:
# 1. Validate the included path against an allowlist
# 2. Restrict !include to a specific directory
# 3. Refuse absolute paths and ../ traversal
# 4. Run the loader in a sandbox

# Other "innocent" tags that can be exploited:
# !env VAR_NAME        -> leaks env vars
# !exec "command"      -> arbitrary command execution
# !file /path          -> file disclosure
# !template <% ... %>  -> code injection

# Audit every custom constructor for side effects.

十亿笑声与 DoS

十亿笑声攻击使用嵌套别名将一个小文档扩展成千兆字节内存(每层 10 个别名,5 层 = 10^9 个引用)。通过别名深度限制、总大小限制、流式解析器和拒绝过度嵌套来缓解。还要注意深度嵌套(栈溢出)和大型扁平集合。

yaml
# "Billion Laughs" attack — exponential expansion via aliases
a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol","lol"]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c]
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d]

# This 5-line document expands to ~10^9 string references
# consuming gigabytes of memory on parse.

# Mitigations:
# 1. Limit alias depth (some parsers do this)
# 2. Limit total parsed size / memory
# 3. Use a streaming parser for untrusted input
# 4. PyYAML has a limit but not all parsers do
# 5. Reject documents with excessive alias nesting

# Other DoS vectors:
# - Very deep nesting (parser stack overflow)
# - Huge flat sequences/mappings (memory)
# - Pathological number formats (regex backtracking)
# - Quadratic blowup via duplicate keys

# For untrusted input: set hard size + depth limits.

输入验证

分层验证:(1) 大小限制,(2) safe_load 拒绝危险标签,(3) 类型检查(期望 dict 而非 list),(4) 模式验证(JSON Schema、Pydantic、Cue),(5) 领域验证(业务规则如端口范围)。每层捕获不同攻击。绝不信任结构——使用前始终验证。

yaml
# Layered defense for untrusted YAML input:

# 1. Size limit (reject oversized input)
MAX_SIZE = 1024 * 1024  # 1 MB
if len(input_text) > MAX_SIZE:
    raise ValueError("input too large")

# 2. Safe parser (rejects dangerous tags)
import yaml
try:
    data = yaml.safe_load(input_text)
except yaml.YAMLError as e:
    raise ValueError(f"invalid YAML: {e}")

# 3. Type check (expect a mapping, not a list)
if not isinstance(data, dict):
    raise ValueError("expected a mapping at top level")

# 4. Schema validation (JSON Schema, Pydantic, etc.)
from pydantic import BaseModel, ValidationError
class Config(BaseModel):
    host: str
    port: int = 8080
try:
    config = Config(**data)
except ValidationError as e:
    raise ValueError(f"schema validation failed: {e}")

# 5. Domain validation (business rules)
if config.port < 1 or config.port > 65535:
    raise ValueError("port out of range")

库建议

选择默认安全的库:PyYAML(safe_load)、ruamel.yaml、js-yaml(默认安全)、Go yaml.v3、Psych.safe_load、带 SafeConstructor 的 SnakeYAML、serde_yaml。避免没有显式安全加载器的 yaml.load。固定到最近版本以获取安全补丁。审计自定义构造器。阅读库的安全文档。

yaml
# Safe-by-default YAML libraries by language:

# Python:
#   - PyYAML >= 5.1 (yaml.safe_load)
#   - ruamel.yaml (safe by default, round-trip mode)
#   AVOID: yaml.load without explicit SafeLoader

# JavaScript/TypeScript:
#   - js-yaml (yaml.load uses DEFAULT_SAFE_SCHEMA)
#   AVOID: yaml.load with DEFAULT_FULL_SCHEMA on untrusted

# Go:
#   - gopkg.in/yaml.v3 (no object instantiation, safe)
#   AVOID: yaml.v2 has subtle differences, prefer v3

# Ruby:
#   - Psych (Psych.safe_load)
#   AVOID: YAML.load (uses unsafe loader in old Ruby)

# Java:
#   - SnakeYAML with SafeConstructor
#   AVOID: new Yaml() without SafeConstructor on untrusted
#   (SnakeYAML had CVEs from unsafe loading)

# Rust:
#   - serde_yaml (safe by design, no arbitrary types)

# General rule:
# - Read the library's security docs
# - Pin to a recent version (security patches)
# - Use safe_load / SafeConstructor / safe schema
# - Audit custom constructors for side effects
18

最佳实践

一致缩进

一致使用 2 个空格(事实上的标准),绝不使用制表符。配置编辑器对 YAML 插入空格。使用格式化工具(prettier、yamlfixer)和 linter(yamllint)强制一致性。不一致的缩进是最常见的 YAML 错误并会破坏解析。

yaml
# Use 2 spaces for indentation (most common)
# NEVER use tabs (YAML forbids them)

# GOOD
server:
  host: localhost
  port: 8080
  routes:
    - path: /api
      handler: apiHandler
    - path: /web
      handler: webHandler

# BAD (inconsistent)
# server:
#    host: localhost
#   port: 8080      # wrong indent

# Configure your editor:
# VS Code: "[yaml]": {"editor.insertSpaces": true, "editor.tabSize": 2}
# Vim:     autocmd FileType yaml setlocal ts=2 sts=2 sw=2 expandtab
# Emacs:   (add-hook 'yaml-mode-hook
#           (lambda () (setq yaml-indent-offset 2)))

# Use a formatter:
# prettier (with yaml plugin) normalizes indentation
# yamllint checks it
# yamlfixer auto-fixes issues

引号策略

给看起来像数字、布尔值、null 或日期的字符串加引号;包含冒号/逗号/特殊字符的字符串;或以指示符(- ? : * & ! | > ' " % @)开头的字符串。否则优先使用普通(无引号)以提高可读性。对转义序列(\n、\t)使用双引号,对带撇号的字面字符串使用单引号。

yaml
# Quote strings when:
# 1. They look like another type (number, bool, null, date)
version: "1.0"        # string, not float
answer: "yes"         # string, not bool (1.1)
region: "no"          # string "no", not False (1.1)
date: "2024-01-01"    # string, not date
phone: "555-1234"     # string, not number

# 2. They contain special characters
url: "https://example.com:8080"   # colon
list: "a, b, c"                    # comma
expr: "value:with:colons"          # colons

# 3. They start with special indicators (-, ?, :, *, &, !, |, >, ', ", %, @, `)
flag: "-not-a-list"
key: "?not-a-complex-key"

# Don't quote otherwise — plain is more readable
name: Alice            # no quotes needed
path: /usr/local/bin   # no quotes needed

# Prefer double quotes for escapes (\n, \t)
# Prefer single quotes for literal strings with apostrophes
greeting: "Hello\nWorld"
possessive: 'Alice''s car'

避免制表符与其他陷阱

避免常见陷阱:制表符(禁止——使用空格)、未加引号的 yes/no/on/off(1.1 与 1.2 歧义)、带冒号的未加引号字符串、尾随空格、CRLF 行结尾(使用 LF)、BOM(保存为无 BOM 的 UTF-8)以及过长的行(用块标量换行)。linter 会捕获其中大部分。

yaml
# Pitfall 1: tabs for indentation (FORBIDDEN)
# Configure editor to use spaces

# Pitfall 2: yes/no/on/off as strings (1.1 vs 1.2)
# Always quote: "yes", "no", "on", "off"

# Pitfall 3: unquoted strings with colons
# bad:  url: http://x:8080
# good: url: "http://x:8080"

# Pitfall 4: trailing whitespace
# Some parsers are strict; linters always flag it
key: value   # <- no trailing spaces

# Pitfall 5: Windows line endings (CRLF)
# Use LF (\n) only — CRLF can cause subtle issues
# .gitattributes: *.yaml text eol=lf

# Pitfall 6: BOM at start of file
# Save as UTF-8 without BOM

# Pitfall 7: very long lines
# Wrap with block scalars or flow style
description: >
  This is a long description that wraps
  across multiple lines for readability.

# Pitfall 8: inconsistent key ordering
# Group related keys, be consistent

版本固定与模式文档

如果 1.1 与 1.2 差异很重要,用 %YAML 1.2 固定 YAML 版本。文档化你的模式:必填/可选字段、类型、默认值、范围——在注释、JSON Schema 文件或 README 中。在代码中固定模式版本,使配置验证可重现。这减少了上手摩擦和配置错误。

yaml
# Pin the YAML version if it matters
%YAML 1.2
---
key: value

# Document your schema expectations in comments:
# Required fields:
#   - host: string (hostname or IP)
#   - port: integer (1-65535)
# Optional fields:
#   - tls: boolean (default: false)
#   - cert: string (path, required if tls: true)

config:
  host: example.com
  port: 443
  tls: true
  cert: /etc/ssl/cert.pem

# Or use a JSON Schema file (config.schema.json)
# and reference it from your docs / CI:
# "Validated against schema v1.2; see config.schema.json"

# For libraries, document the schema in the README:
# ## Configuration
# The config.yaml file must contain:
# - host (str, required)
# - port (int, required, 1-65535)
# - tls (bool, optional, default: false)

# Pin schema versions in code:
# CONFIG_SCHEMA_VERSION = "1.2"
# validate(data, schema_v1_2)

CI 中校验

将 YAML 校验接入 CI(GitHub Actions、GitLab CI)和 pre-commit 钩子。使用 yamllint 进行语法/风格,prettier 进行格式化。编辑器集成(VS Code YAML 扩展、yaml-language-server)提供内联校验和模式验证。在 CI 中捕获 YAML 错误比在生产中便宜得多。

yaml
# .github/workflows/lint.yml
name: Lint YAML
on: [push, pull_request]
jobs:
  yamllint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install yamllint
        run: pip install yamllint
      - name: Lint
        run: yamllint -s .

# GitLab CI:
# yamllint:
#   image: python:3.12
#   script:
#     - pip install yamllint
#     - yamllint -s .

# Pre-commit hook (.pre-commit-config.yaml):
# repos:
#   - repo: https://github.com/adrienverge/yamllint
#     rev: v1.35.1
#     hooks:
#       - id: yamllint
#         args: [-s, --config-file, .yamllint.yml]
#   - repo: https://github.com/pre-commit/mirrors-prettier
#     rev: v3.1.0
#     hooks:
#       - id: prettier
#         types: [yaml]

# Editor integration:
# VS Code: install "YAML" extension (redhat)
#   + "yamllint" for inline linting
#   + schema validation via yaml-language-server

结构与可读性

用注释标题将相关键分组。保持嵌套浅(最多 3-4 层)——深度嵌套的 YAML 难以阅读。对真正重用的值使用锚点。对多行字符串使用块标量。保持行在 120 字符以内。可读性很重要,因为人类编辑 YAML。

yaml
# Group related keys; use comments to delineate sections
server:
  # Networking
  host: 0.0.0.0
  port: 8080
  tls:
    enabled: true
    cert: /etc/ssl/cert.pem

  # Limits
  timeout: 30
  max_connections: 100
  retries: 3

# Keep nesting shallow (3-4 levels max)
# Deeply nested YAML is hard to read and edit.

# If a value is reused, use an anchor:
defaults: &defaults
  timeout: 30
  retries: 3

prod:
  <<: *defaults
  env: production

staging:
  <<: *defaults
  env: staging

# Use block scalars for multi-line values
script: |
  #!/bin/bash
  set -e
  echo "deploying"

# Keep lines under 120 chars; wrap with block scalars or
# multi-line flow style.
19

高级特性

复杂映射键

? 指示符标记复杂键(用作键的映射或序列)。适用于查找表(例如按区域+实例定价)。注意事项:许多序列化器不保留复杂键,可能在输出时将它们字符串化。大多数配置文件为简单性和工具兼容性坚持使用普通字符串键。

yaml
# Use ? to mark a complex (non-scalar) key
? apple
: red fruit

? orange
: orange fruit

# A mapping as a key
? host: localhost
  port: 8080
: connection_string

# A sequence as a key
? [us-east-1, t2.micro]
: 0.0116
? [us-east-1, t2.small]
: 0.023
? [us-west-2, t2.micro]
: 0.0125

# Useful for lookup tables
pricing:
  ? [production, large]
  : 100
  ? [production, small]
  : 50
  ? [development, large]
  : 20

# In flow style:
{[a, b]: value, {x: 1}: other}

# Caveat: many serializers don't preserve complex keys
# and may convert them to strings on output.

集合 (!!set)

YAML 集合(!!set)是一个映射,其中键是元素,值是 null。某些解析器将其映射为原生 Set 类型。用它声明唯一集合(角色、权限)。注意事项:JSON 没有 Set 类型,所以 JSON 转换产生带 null 值的对象。并非所有序列化器保留 !!set 标签。

yaml
# A set is a mapping with null values
# Use the !!set tag for clarity
permissions: !!set
  ? read
  ? write
  ? execute

# Equivalent to:
permissions:
  read: null
  write: null
  execute: null

# Or in flow style:
flags: !!set { red, green, blue }

# Parsers may map this to a native Set type:
# Python: set() (PyYAML with !!set constructor)
# Ruby: Set (Psych)
# JavaScript: depends on library

# Use case: declare a unique collection
roles: !!set
  ? admin
  ? editor
  ? viewer

# Caveat: JSON has no Set type, so JSON conversion
# usually produces an object with null values.

有序映射 (!!omap)

!!omap(有序映射)是保留插入顺序的单键映射序列。适用于时间线、有序偏好或顺序在语义上重要的情况。现代解析器无论如何保留常规映射顺序(Python dict 3.7+、Ruby Hash),但 Go 映射是随机的——!!omap 在那里使顺序显式。

yaml
# An ordered mapping preserves key order
# Use !!omap tag
timeline: !!omap
  - 2024-01-01: event A
  - 2024-02-01: event B
  - 2024-03-01: event C

# Equivalent representation as a list of single-key mappings:
# timeline:
#   - 2024-01-01: event A
#   - 2024-02-01: event B
#   - 2024-03-01: event C

# Parsers may map this to:
# Python: list of tuples, or OrderedDict
# Ruby: Array of one-key hashes (or OrderedDict)
# Go: ordered map (with custom type)

# Use case: ordered key-value pairs where order matters
# - event timelines
# - ordered preferences
# - step-by-step configurations

# Caveat: regular YAML mappings DO preserve order in
# most modern parsers (Python dict 3.7+, Ruby Hash,
# Go map iteration is random though). !!omap makes the
# intent explicit and works even where maps are unordered.

二进制数据 (!!binary)

!!binary 在 YAML 中将二进制数据编码为 base64。解析器将其解码为原生字节(Python bytes、Ruby ASCII-8BIT 字符串)。适用于直接嵌入小图像或图标。对于大二进制数据,优先通过路径或 URL 引用。多行 base64 使用折叠(>)或字面(|)块标量。

yaml
# Encode binary data as base64 with !!binary tag
image: !!binary "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9
  awAAAABJRU5ErkJggg=="

# Multi-line base64 (folded or literal works)
data: !!binary |
  iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42m
  k+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==

# Parsers decode to bytes:
# Python: bytes object
# Ruby: ASCII-8BIT string
# JavaScript: depends on library (often base64 string)

# Use case: embed small images, icons, or other binary
# directly in YAML config

# For larger binary data, prefer:
# - Reference by path:  image: /path/to/image.png
# - Reference by URL:   image: https://example.com/img.png
# - Store separately and link

# Verify with:
# python -c 'import yaml; d=yaml.safe_load(open("f.yaml")); print(len(d["image"]))'

自定义构造器

注册自定义构造器将标签(!money)映射到原生对象。使用带 SafeLoader 作用域加载器的 add_constructor。构造器接收加载器和节点;使用 construct_scalar/mapping/sequence 读取节点值。始终审计构造器的副作用(文件读取、子进程调用)——它们可通过不受信任输入被利用。

yaml
# PyYAML: register a constructor for a custom tag
import yaml

class Money:
    def __init__(self, currency, amount):
        self.currency = currency
        self.amount = amount
    def __repr__(self):
        return f"Money({self.currency} {self.amount})"

def money_constructor(loader, node):
    value = loader.construct_scalar(node)  # "USD 19.99"
    currency, amount = value.split()
    return Money(currency, float(amount))

# Register the constructor (use SafeLoader scope)
yaml.add_constructor('!money', money_constructor, Loader=yaml.SafeLoader)

# Now this YAML parses to a Money object:
# price: !money "USD 19.99"
config = yaml.safe_load("price: !money \"USD 19.99\"")
print(config['price'])  # Money(USD 19.99)

# Multi-argument constructors use construct_mapping or
# construct_sequence depending on the node kind.

# SECURITY: audit every constructor for side effects.
# A constructor that calls open(), os.system(), or
# subprocess can be exploited via untrusted input.

YAML 1.2 特性与未来

YAML 1.2 使 JSON 成为严格子集,将布尔值限制为 true/false,并采用 0o 表示八进制。合并键 << 从核心规范中移除但仍广泛支持。YAML 1.2.2(2021)澄清了规范;1.3 正在开发中。通过固定版本、给有歧义的字符串加引号以及为复杂配置考虑 Cue/Dhall/Jsonnet 来面向未来。

yaml
# YAML 1.2 (2009, current spec) key changes from 1.1:
# - JSON is a strict subset
# - Booleans: only true/false (not yes/no/on/off)
# - Octal: 0o prefix (not leading 0)
# - Removed sexagesimal numbers
# - Removed merge key << from core spec (still widely supported)

# YAML 1.3 (in development) aims to:
# - Clarify ambiguous parts of 1.2
# - Standardize error reporting
# - Improve interoperability between parsers
# - Possibly restore some 1.1 features as optional

# YAML 1.2.2 (2021 update, spec revision):
# - Clarified type resolution
# - Better-defined core schema
# - Improved examples
# - No breaking changes

# Recommended practices for future-proofing:
# 1. Pin %YAML 1.2 in critical files
# 2. Quote ambiguous strings (yes/no, leading-zero numbers)
# 3. Avoid relying on << merge key for new formats
#    (or document that you depend on it)
# 4. Use JSON-compatible subset when interop matters
# 5. Watch for YAML 1.3 adoption in your parser

# Some tools (Cue, Dhall, Jsonnet) extend/replace YAML
# for complex configuration — consider them for new
# projects with advanced needs.

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。