鍵值對、縮排、註解、空值、文件分隔
YAML 最基本的單位是 鍵: 值。冒號後要加一個空格——這條鐵則沒有例外,也是新手第一個踩的坑。
name: YAML 教學 author: OpenCode units: 8
{"name": "YAML 教學",
"author": "OpenCode",
"units": 8}
name:YAML 教學(冒號後沒空格)會被整個當成一個字串,而不是鍵值對。若你看到解析結果「key 消失」或鍵變長,八成是這個。巢狀層級靠縮排表達,這是 YAML 之所以好讀、也之所以難 debug 的原因。規則有三:同層縮排一致、只能用空格、禁用 Tab、慣用兩個空格。
app: database: host: db.example.com cache: ttl: 60
{"app": {
"database": {"host": "db.example.com"},
"cache": {"ttl": 60}}}
用 # 開頭到行尾皆為註解,可佔一整行,也可放在行尾。註解是 YAML 打敗 JSON 的殺手級功能——設定檔需要解釋「為什麼」。
# 這是註解 name: YAML 教學 # 行尾註解也行 # 注意:值開頭是 # 就不是註解,是字串,要加引號(單元 3)
空值可以寫 null、~,或直接留空(冒號後不寫值)。三者等價。
a: null b: ~ c: d: null # 三者等價
{"a": null, "b": null,
"c": null, "d": null}
null。對某些程式(例如 Python 的 None)行為相同,但字典合併、預設值覆寫的邏輯可能不同,是隱性 bug 來源。一個檔案可含多份文件,用 --- 分隔;... 表示文件結束。多數設定檔只用一份,但 frontmatter 就是利用檔首的 --- 區塊(Jekyll / Hugo / Obsidian),單元 7 會看到實例。
--- first: 文件一 --- second: 文件二 ...
多份文件在 Python 用 yaml.safe_load_all、在 yq 用 yq 'eval-all' 讀取。
# 應用程式設定 title: 我的服務 replicas: 3 features: auth: true cache: true tags: [web, api] notes: null
① 註解解釋用途
② 字串值
③ 整數 replicas: 3
④ 巢狀對映 features
⑤ flow 序列 [web, api]
⑥ 空值 null
練習:幫你手上的專案寫一個 config.yml,包含標題、三個以上鍵值、一層巢狀與一行註解,然後用 yaml.safe_load 讀回來檢查。
Ansible 的 inventory 檔大量使用 YAML 基本語法——鍵值對、巢狀對映、多文件分隔、空值。以下是一份真實場景:
--- # Ansible inventory — 生產環境 all: vars: ansible_user: deploy ansible_port: 22 children: webservers: hosts: web1.example.com: web2.example.com: databases: hosts: db1.example.com: ansible_port: 5432 ...
① --- 文件分隔符
② web1.example.com:鍵名含 .,plain string 合法
③ db1.example.com 下方覆寫 ansible_port
④ 留空的值(web1.example.com 後)= null
⑤ ... 文件結束符
為什麼這樣設計而非替代方案:Ansible inventories 故意讓 host 名稱作為「無值的鍵」(等同 null),因為 host 本身就是標識——不需要值。用 --- / ... 包圍是 YAML 慣例,讓解析器明確知道這是一份完整文件。
YAML 對映的鍵有兩種寫法:implicit key(plain string,不需要引號)與 explicit key(加引號)。在 block context 中,implicit key 只能是「單行的純量」——一旦鍵名含有 :、#、{、}、[、]、, 等字元,就必須加引號變成 explicit key。例如 "key: with colon": value 在 YAML 1.2 是合法的。
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
mapping values not allowed here | 冒號後沒空格 | 確認 key: value 冒號後至少一個空格 |
tab characters are not allowed | 混用 Tab 與空格 | 設定編輯器展開 Tab,用 cat -A 檢查 |
while parsing a block mapping | 同層縮排不一致 | 對齊同層級所有鍵的起始位置 |
解析器讀到意外的 --- | 檔案中間有 --- 被當成分隔符 | 中間的 --- 要加引號或移除 |
值是 null 但預期有值 | 冒號後換行沒寫值 | 確認冒號後有內容,或用引號包住空字串 |
.yml 檔中用 --- 分隔三份文件,分別是 dict、list、scalar。用 Python yaml.safe_load_all() 讀取並印出每份文件的 type()。yaml.safe_load 讀取,比較兩者的差異。如果冒號後沒空格的值「看起來」是對的,怎麼發現它是錯的?基本語法(鍵值對、巢狀、多文件、空值)組合起來就是真實的部署專案。以下是一個用 Ansible 佈建三台主機的完整專案:
provisioner/ ├── inventory/ │ └── production.yml # 主機清單(多文件 + 巢狀 + 空值) ├── group_vars/ │ └── all.yml # 全域變數(鍵值對) ├── playbooks/ │ ├── deploy.yml # 主 playbook │ └── healthcheck.yml # 健康檢查 └── ansible.cfg # INI 格式,非 YAML——工具混用是常態
--- all: vars: ansible_user: deploy ansible_port: 22 children: webservers: hosts: web1.example.com: web2.example.com: databases: hosts: db1.example.com: ansible_port: 5432 ...
# 所有主機共用的變數 app_version: "1.4.2" # 加引號防型別誤判 app_replicas: 3 log_level: info features: - metrics - tracing maintenance_mode: false
--- - name: 部署應用 hosts: webservers tasks: - name: 確認變數已注入 debug: msg: "版本 {{ app_version }}" - name: 部署容器 community.docker.docker_container: name: app image: "myapp:{{ app_version }}" restart_policy: unless-stopped
$ ansible-playbook -i inventory/production.yml playbooks/deploy.yml --syntax-check
playbook: playbooks/deploy.yml
$ ansible-inventory -i inventory/production.yml --list | python3 -m json.tool | head -20
{
"_meta": {
"hostvars": {
"db1.example.com": {"ansible_port": 5432}
}
},
"all": {
"children": ["databases", "webservers"]
}
}
① host 名稱當「無值鍵」= null,是 inventory 慣例
② --- / ... 標記文件邊界
③ "1.4.2" 加引號避免被當浮點數
④ playbook 是「序列裡的對映」,每條 task 是對映
⑤ ansible.cfg 是 INI——工具混用格式是常態
在佈建自動化中,縮排錯誤可能不是「報錯」而是「部署到錯誤的機器群」:同層縮排差一格,任務就換了 hosts。品質防線:
--syntax-check:僅解析,不連線,秒級回饋。group_vars。Ansible 提供 ansible-vault encrypt 加密整個檔案;敏感值一律用 ansible-vault 加密並將解密密碼存進 CI secret,而不是進 repo。這樣 YAML 檔案仍可審查,但內容不可讀。幾千台主機的 inventory 解析與 hostvars 合併是有成本的——Ansible 會對每個 host 執行變數合併。實務上拆成 group_vars / host_vars 目錄,讓 Ansible 只載入需要的部分,而不是一支超長單檔。
| 面向 | yaml | markdown | json | github | gitlab |
|---|---|---|---|---|---|
| 基本單位 | 鍵值對 key: value | 段落 / 標題 / 列表 | 物件 / 陣列 | issue / PR / commit | issue / MR / pipeline |
| 結構規則 | 縮排即巢狀(禁 Tab) | 語法極少,結構由標記決定 | 括號與逗號嚴謹 | branch / tag 命名規則 | stage / job 命名規則 |
| 註解 | # 原生支援 | HTML 註解或 frontmatter | 不支援 | PR 描述與 review 留言 | MR 描述與討論串 |
| 多文件/分隔 | --- 分隔多文件 | 一個檔案一份文件 | 不支援(單一文件) | 多 repo 靠子樹/子模組 | 多 repo 靠 subgroup |
| 空值表示 | null / ~ / 留空 | 無此概念 | null | 無此概念 | 無此概念 |
config.yml,並用 safe_load 驗證。cat -A / 編輯器診斷 Tab 混用。ansible-playbook --syntax-check + yamllint 的 CI 門檻。group_vars、哪些該放 host_vars,並說明理由。ansible-vault 加密,並能說明加密流程。