入门
基本结构
YAML 使用缩进(空格,而非制表符)表示嵌套。映射使用 key: value,序列使用 - item。内联语法使用 [] 表示列表,{} 表示映射。注释使用 #。
# 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}注释
注释以 # 开头,前面必须有空白或位于行首。在块标量(| 和 >)内部,# 是字面内容,不是注释。没有多行注释语法——每一行都需要自己的 #。
# 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 严格禁止使用制表符缩进——只允许空格。同一层级的缩进必须一致,但不需要固定数量。序列中的 - 是缩进的一部分。混用制表符和空格会抛出解析器错误。
# 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 文档的开始;... 标记结束。一个文件可以包含由 --- 分隔的多个文档(文档流)。对于单文档配置文件,开头的 --- 是可选的,但建议作为给解析器的提示。
# --- 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 规则。
# 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 对普通标量放宽了这一要求。
# 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.标量(字符串、数字、布尔值、Null)
普通与引号字符串
普通字符串无需引号,除非它们看起来像数字、布尔值、null 或日期。单引号仅通过双写('')来转义 ';没有其他转义。双引号支持完整转义序列(\n、\t、\uXXXX)。加引号可强制字符串类型。
# 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),请加引号。
# 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 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 FalseNull 值
YAML 接受 null、Null、NULL、~ 和空值作为 null。空值(冒号后无内容)是 null,与空字符串 "" 不同。解析器将 YAML null 映射为宿主语言的 null 值(None、null、nil)。
# 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)。
# 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 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映射
简单映射
映射是 key: value 对,键通常是字符串。键可以是任何标量(数字、布尔值,甚至 null)。不鼓励重复键,行为因解析器而异(通常后值生效)。使用 linter 捕获意外的重复键。
# 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 个空格是事实上的标准。更深的嵌套会降低可读性——考虑展平非常深的结构。
# 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映射包含序列
映射和序列可以自由组合。常见模式是映射的序列(记录列表),其中每个 - 开始一个项,对齐的键属于该项。后续键的缩进必须与 - 后第一个键对齐。
# 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 允许使用 ? 键指示符将任何标量或集合作为键。这在配置文件中很少见,但对查找表(例如按区域+实例定价)很有用。复杂键读起来很别扭——大多数配置文件坚持使用普通字符串键。
# 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 不允许尾随逗号。可自由混合流和块样式,但配置文件中优先使用块以提升可读性。
# 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 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序列
块序列
块序列在每个项前使用 -(连字符 + 空格)。项可以是任何 类型:标量、映射或嵌套序列。- 算作缩进的一部分,因此映射项中后续的键与连字符后第一个键对齐。
# 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 层就变得难以阅读——对内部序列切换到流样式。树结构(带子项的映射序列)比纯嵌套序列更易读。
# 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 模式(记录列表)。每个 - 开始一个新项;同一缩进下的后续键属于该项。- 后对齐键以提升可读性。空映射项写为 {}。
# 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 禁止尾随逗号。可自由地将流序列与块映射混合。
# 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 等)进行严格验证。
# 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,因为 - 算作缩进)。两者都有效;选择一种并保持一致。在映射的序列中,一个项的所有键必须与 - 后第一个键对齐。
# 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多行字符串(折叠与字面)
字面块标量 (|)
字面块标量 | 原样保留每个换行符。缩进(| 之后的量)从每行剥离。默认截断(clip)保留一个尾随换行符。用于脚本、代码、诗歌、ASCII 艺术——任何换行符重要的地方。
# | 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折叠块标量 (>)
折叠块标量 > 将单个换行符转换为空格,将行连接成段落。空行成为字面换行符。比块内容缩进更多的行被原样保留(适用于散文中的代码示例)。
# > 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(+)全部保留。指示符紧跟在 | 或 > 之后(例如 |-、|+、>-、>+)。剥离适用于内联使用的字符串;保留很少见但精确匹配源文件。
# 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+ 等。很少需要但在需要时至关重要。
# 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普通多行字符串
普通和引号标量可跨多行,换行符折叠为空格(类似于 > 但是隐式的)。这很脆弱,因为换行取决于上下文和特殊字符。对于任何非平凡的多行文本,优先使用显式 |(字面)或 >(折叠)块标量。
# 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选择折叠还是字面
经验法则:当换行符重要时(脚本、配置文件、代码)使用 |,当你想要可读的多行源文件但连接成一个段落字符串时(描述、文档)使用 >。当字符串内联使用时,使用 |- 剥离尾随换行符。
# 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锚点与别名
定义锚点 (&)
&name 在节点(映射、序列或标量)上定义锚点。锚点不会更改该位置的值——它只是为重用标记节点。别名(*name)按名称引用锚定节点。锚点必须在文档内使用前定义。
# &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 是引用先前锚定节点的别名。别名与原始节点共享标识(在大多数解析器中是同一对象)。锚点/别名是文档作用域的——不能跨 --- 文档边界引用。与 <<(合并键)组合以分层覆盖。
# 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锚点作用域
锚点作用域限于其文档,必须在任何别名引用之前定义(无前向引用)。多个别名可引用同一锚点。别名引用别名有效——它会传递解析。跨文档引用是错误的。
# 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)替换整个节点——你不能部分覆盖它。要覆盖锚定映射的单个键,使用合并键 << 合并锚定映射,然后添加或覆盖键。这是配置组合的典型模式。
# 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 锚定单个项。
# 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 输出)将它们展开为普通副本。将锚点用于真正的重用(模板、默认值),而非微优化。
# 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.合并键 (<<)
基本合并 (<<)
合并键 << 接受一个映射(通常是别名)并将其键值对合并到当前映射中。当前映射中显式设置的键覆盖合并的键。这是 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])合并多个映射。合并从左到右 应用;对于重复键,后面的合并覆盖前面的。映射中的显式键始终优先于任何合并键。也允许多个 << 条目。
# << 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覆盖与优先级
合并优先级:映射中的显式键始终优先。在合并的映射中,序列中后面的覆盖前面同名的键。利用这一点清晰地分层默认值 → 角色特定 → 实例特定的覆盖。
# 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,同时不牺牲每个服务的自定义。
# 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.合并限制
合并键 << 仅适用于映射——序列和标量不能合并。合并是浅层的:当前节点中的嵌套映射完全替换合并的映射(无深度合并)。要进行深度合并,重构数据或在应用代码中处理。
# << 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)。
# 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文档标记与指令
单个文档
单个 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() 或等效方法迭代文档。
# 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 中很少见,但程序输出中常见。
# ... 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 双模式)。
# 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 日志和静态站点生成器(前置元数据 + 内容)也使用流。每个文档独立,可以有自己的指令。
# 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 列的 ---,缩进块或先用 ... 结束文档。同一行带内容的 --- 是文档开始标记,不是分隔符。
# 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流样式
流序列
流序列使用 [],行为类似 JSON 数组。内部空白和换行很灵活。与 JSON 不同,不允许尾随逗号。适用于简短、密集的列表。包含特殊字符(逗号、方括号、冒号)的项需加引号。
# 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 不同)。流映射用于简短、密集的数据;块样式对较大结构更易读。
# 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混合流与块
流和块样式可在文档中自由混合。实用法则:对结构元素使用块样式(更好的缩进可读性),对简短内联值(标量列表、小映射)使用流样式。避免深度嵌套的流——它很快变得不可读。
# 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 层后,切换到块样式以提高可读性。深度嵌套流的块等效形式对人类维护者几乎总是更清晰。
# 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 兼容输出使用流样式。避免对长列表(难以扫描)、深度嵌套结构(方括号墙)或需要内联注释的内容使用流。有疑问时,优先使用块样式以提高可读性。
# 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。块样式对版本控制的配置更友好。
# 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)YAML 与 JSON 转换
JSON 是有效的 YAML
YAML 1.2 的设计使 JSON 成为严格子集——任何 JSON 文档都是有效的 YAML。大多数 YAML 解析器乐于读取 JSON。反之不然:YAML 的特性如注释、锚点、多文档流和块标量不是有效的 JSON。
# 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 块)。
# 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 dataJSON 转 YAML
通过解析 JSON 然后以 default_flow_style=False(块样式)转储为 YAML 来转换。JSON 的强制引号在 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 preservedYAML 转 JSON
通过解析然后 JSON 序列化来转换。注意事项:注释和标签丢失;锚点展开为普通副本;多文档流需要变成 JSON 数组;YAML 日期可能变成字符串。yq 等工具使命令行操作变得简单。
# 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 消费者读取相同数据,或严格类型保留很重要时很有用。
# 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。
# 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 carefullyYAML 模式
Failsafe 模式
Failsafe 模式是最保守的——它只识别 str、map 和 seq。所有标量都是字符串,除非显式加标签。适用于不受信任的输入(无隐式强制转换意外)或作为自定义模式的基础。大多数生产加载器使用稍丰富的模式。
# 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 互操作或严格类型行为。
# 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 semanticsCore 模式
Core 模式是 YAML 1.2 的默认模式。它用宽松解析扩展 JSON:十六进制(0x)、八进制(0o)、二进制(0b)、数字中的下划线、.inf/.nan 浮点数。布尔值只有 true/false(不是 yes/no)。日期是字符串(无推断)。大多数现代解析器默认 Core 或 1.1 兼容变体。
# 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 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 固定版本、使用显式标签以及针对所有目标解析器测试来缓解。
# 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" 等)加引号是最安全的组合。
# 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 cleanlyYAML 校验与验证
yamllint
yamllint 是标准 YAML linter(Python)。它捕获语法错误、缩进问题、尾随空格、缺失文档标记等。在 CI 和 pre-commit 钩子中使用它以尽早捕获 YAML 问题。严格模式(-s)将警告视为错误。
# 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 中运行以一致执行。
# .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 会自动捕获所有这些。
# 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 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 以尽早捕获。
# 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。
# 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]Docker Compose YAML
服务定义
Docker Compose 文件定义多容器应用。顶层键是 version、services、volumes、networks、configs、secrets。每个服务定义 image/build、ports、volumes、environment、depends_on、重启策略。顶层声明的卷是命名的并持久化。
# 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 阻止外部访问。命名卷跨容器重新创建持久化数据。
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 文件自动加载。
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。
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 标志为环境特定配置指定显式文件顺序。
# 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 文件适配多个环境。
# 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.txtKubernetes YAML
对象结构
每个 Kubernetes 对象都有 apiVersion、kind、metadata 和 spec。apiVersion 是 group/version(例如 apps/v1,核心为 v1)。metadata 包括 name、namespace、labels、annotations。spec 是期望状态——Kubernetes 努力使现实匹配 spec。status 由系统设置。
# 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 用于附加信息。
# 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 创建,而非直接创建。
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: AlwaysDeployment
Deployment 管理 ReplicaSet 和 Pod。spec.replicas 设置期望数量。strategy.type 可以是 RollingUpdate(默认,由 maxSurge/maxUnavailable 控制)或 Recreate。Pod 模板(spec.template)定义如何创建 Pod。用 kubectl rollout status/undo/history 管理发布。
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-appService
Service 将 Pod 作为网络服务暴露。type ClusterIP(默认)仅内部;NodePort 在每个节点上暴露;LoadBalancer 配置云 LB;ExternalName 是 DNS CNAME。selector 将流量路由到匹配的 Pod。ports 将服务端口映射到容器 targetPort(数字或命名)。
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 访问。
# 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-configCI/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 模板字面量内必须转义为 \${{ }}。
# .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 需要点击才能部署。
# .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 字面量中必须转义。
# 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 以保持工作流可组合和可审计。
# 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.ymlCI 配置中的锚点
YAML 锚点减少 CI 配置中的重复。GitLab CI 大量使用它们:用 &anchor 定义隐藏 job(.template),然后用 <<: *anchor 合并。GitHub Actions 支持锚点但不鼓励跨 job 使用(优先使用可重用工作流)。锚点使配置 DRY 但更难追踪——明智地使用。
# 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(日志隐藏)变量。绝不记录密钥——它们可能在错误输出中泄露。
# 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安全注意事项
反序列化风险
YAML 反序列化很危险,因为像 !!python/object/apply 这样的标签可以调用任意函数。PyYAML 的 yaml.load() 在 5.1 之前默认不安全(不受信任输入的代码执行)。对不受信任输入始终使用 yaml.safe_load()——它拒绝对象实例化标签。其他语言有类似危险(SnakeYAML、Psych)。
# 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 loadersafe_load 与 load
始终使用 safe_load(Python)、safe_load(Ruby Psych)或默认安全模式(js-yaml、Go yaml.v3)。PyYAML 5.1+ 要求 yaml.load() 显式 Loader。full_load 允许标准标签但不允许任意对象——仅对受信任文件使用。安全加载器拒绝所有对象实例化标签。
# 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。审计每个自定义构造器:根据允许列表验证路径,拒绝绝对路径和 ../,沙箱化加载器。
# 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 个引用)。通过别名深度限制、总大小限制、流式解析器和拒绝过度嵌套来缓解。还要注意深度嵌套(栈溢出)和大型扁平集合。
# "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) 领域验证(业务规则如端口范围)。每层捕获不同攻击。绝不信任结构——使用前始终验证。
# 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。固定到最近版本以获取安全补丁。审计自定义构造器。阅读库的安全文档。
# 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最佳实践
一致缩进
一致使用 2 个空格(事实上的标准),绝不使用制表符。配置编辑器对 YAML 插入空格。使用格式化工具(prettier、yamlfixer)和 linter(yamllint)强制一致性。不一致的缩进是最常见的 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)使用双引号,对带撇号的字面字符串使用单引号。
# 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 会捕获其中大部分。
# 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 中。在代码中固定模式版本,使配置验证可重现。这减少了上手摩擦和配置错误。
# 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 错误比在生产中便宜得多。
# .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。
# 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.高级特性
复杂映射键
? 指示符标记复杂键(用作键的映射或序列)。适用于查找表(例如按区域+实例定价)。注意事项:许多序列化器不保留复杂键,可能在输出时将它们字符串化。大多数配置文件为简单性和工具兼容性坚持使用普通字符串键。
# 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 标签。
# 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 在那里使顺序显式。
# 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 使用折叠(>)或字面(|)块标量。
# 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 读取节点值。始终审计构造器的副作用(文件读取、子进程调用)——它们可通过不受信任输入被利用。
# 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 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.相关 YAML 代码片段
Copy-paste ready code for common tasks.
Scalars
Strings, numbers, booleans, null, and dates.
Sequences and Mappings
Lists and key-value maps.
Anchors and Aliases
Reuse nodes with anchors, aliases, and merge keys.
Multi-Document
Several documents in one stream.
Tags
Explicit type tags for custom resolution.
Flow Style
Inline JSON-like sequences and mappings.
Block Style
Indentation-based nesting.
Schema Types
How YAML 1.1 and 1.2 resolve scalars.
这篇内容对您有帮助吗?