單元 7 · YAML 生態系

GitHub Actions/Docker Compose/Kubernetes/frontmatter

7.1 GitHub Actions · workflow

放在 .github/workflows/*.yml,定義 CI/CD。你現在看的這個教學站就是它部署的。三層結構:on(何時觸發)→ jobs(跑什麼)→ steps(每個 job 的步驟)。

.github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test
注意這行:on: 在 YAML 1.1 裡 on 會被當布林——GitHub Actions 特別容許它作為鍵名。這也再次印證單元 5 的提醒:你自己寫設定檔時,避免用 on 當鍵

7.2 Docker Compose · compose.yaml

定義多容器服務。<< 合併鍵常用來共用設定(單元 6),ports 是「序列裡的對映」的應用。

compose.yaml
services:
  web:
    build: .
    ports:
      - "8080:80"
    environment:
      NODE_ENV: production
  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data: {}

注意 ports / volumes 都是序列;environment 是對映;volumes.db-data: {} 是空對映(留 {} 比留空更明確)。

7.3 Kubernetes · manifest

K8s 資源描述也是 YAML。基本結構:apiVersionkindmetadataspec

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27

這是單元 4 的「序列裡的對映」深度應用:containers 底下是序列,每個元素是對映,image 要跟 name 對齊。

7.4 Frontmatter · Jekyll / Hugo / Obsidian

Markdown 檔案頂端的 --- 區塊,用 YAML 描述文件屬性。這是「文件分隔符」最日常的用途(單元 2)。

某篇筆記的 frontmatter
---
title: YAML 筆記
tags:
  - yaml
  - 教學
date: 2026-08-15
draft: false
---

7.5 其他常見工具

工具用 YAML 做什麼
GitLab CI.gitlab-ci.yml 定義 pipeline
Ansibleplaybook 描述部署任務
Prefect / Airflow資料管線 DAG 定義
HelmK8s 套件模板(含 Go template 語法)
OpenAPI / SwaggerAPI 描述(YAML 或 JSON)

7.6 Worked Example:跨領域驗收

原始碼 · YAML SOURCE
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  template:
    spec:
      containers:
        - name: api
          image: myapi:1.2
          env:
            - name: LOG_LEVEL
              value: info
拆解 · ANATOMY

① 頂層四欄位
replicas 整數
containers = 序列裡的對映
env = 序列裡含兩鍵的對映
👉 用單元 4 的「對齊」眼光檢查:name / image / env 三鍵同層

練習:找一個 GitHub repo 的 .github/workflows/,讀懂它的 on / jobs / steps;再用 compose 的語法為一個服務寫一份含環境變數與 ports 的片段。

看完這單元你應該能說出:
  • 讀懂 GitHub Actions workflow 的 on / jobs / steps 結構。
  • 讀懂 Docker Compose 的 services / volumes 結構。
  • 說出 K8s manifest 的四個頂層欄位(apiVersion/kind/metadata/spec)。
  • 解釋 frontmatter 的 --- 區塊與用途。

延伸閱讀


進階真實情境 Worked Example:完整的 GitHub Actions Matrix CI

Matrix 策略是 GitHub Actions 中最複雜的 YAML 結構之一——序列裡包對映、對映裡包序列、flow 與 block 混用:

原始碼 · matrix-ci.yml
name: Matrix CI
on:
  push: {branches: [main]}
  pull_request: {branches: [main]}

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        node: [18, 20, 22]
        exclude:
          - {os: macos-latest, node: 18}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: {node-version: "${{ matrix.node }}"}
      - run: npm test
拆解 · ANATOMY

on 的值是 flow 對映——push: {branches: [main]}
matrix 含兩個序列(os × node = 6 組合)
exclude 是序列裡的 flow 對映
${{ }} 是 GitHub expression,非 YAML 語法
with 用 flow 對映包單個值——簡潔

為什麼這樣設計而非替代方案:on 用 flow 對映而非 block 是因為每個 trigger 只有一行參數,flow 佔行數更少。exclude 用 flow 對映 {os:..., node:...} 是因為每個排除規則只有兩個欄位——如果改用 block 寫,反而多出 4 行縮排。GitHub Actions 社群的慣例是「簡單用 flow、複雜用 block」。

深入原理擴充

各生態工具的 YAML 方言差異

工具YAML 版本特殊方言注意事項
GitHub Actions1.1(部分)on: 允許當鍵名;${{ }} expression避免自己用 on / off 當鍵
Docker Compose1.1x- 延伸節點;<< 合併鍵x- 前綴的節點被忽略
Kubernetes1.2(推薦)多文件 ---;型別標籤 !!kubectl 預設用 1.2+ 解析器
Ansible1.1!vault 等自訂標籤Ansible 用 PyYAML(1.1)
HelmGo YAML 3(1.2)Go template {{ }} 嵌在 YAML 裡template 語法與 YAML 衝突需注意

大家以為是這樣但其實不是……

誤解:「所有 YAML 工具都用同一版本的 YAML」——事實上不同工具使用不同版本的 YAML 解析器(1.1 或 1.2),且各自有自訂的擴展或限制。GitHub Actions 的 on: 是特例處理,不是通用 YAML 行為。Ansible 仍基於 PyYAML(1.1),所以 yes 仍是布林。一份 YAML 在 A 工具正常不代表在 B 工具也正常。

診斷式疑難排解表

症狀可能原因解決方案
GitHub Actions on 報解析錯誤非 GitHub 的 YAML 解析器把 on 當布林鍵本地測試用 GitHub 的解析器,或加引號
Docker Compose 忽略 x- 節點中的設定這是預期行為——x- 節點只當錨點來源確保 x- 節點的值被 <<:*alias 引用
K8s kubectl applyunknown fieldapiVersion/kind 指錯版本確認 apiVersion 與 K8s 版本相符
Helm template 產生意外輸出Go template {{ }} 與 YAML flow 對映衝突\{{ }} 轉義或 rawYaml helper
Ansible playbook 讀不到變數PyYAML 1.1 把 yes 當布林,變數名被改變數名避免 yes/no/on/off

進階挑戰題

  1. 跨工具 YAML 移植:把同一份 Docker Compose compose.yaml 中的 x- 延伸節點改寫成 Ansible inventory 可用的格式。哪些概念可以直接搬?哪些需要重新設計?
  2. GitHub Actions 語法分析:寫一份含 matrixexcludeinclude 的 GitHub Actions workflow,然後把所有 GitHub expression(${{ }})替換成實際值,觀察純 YAML 的結構。
  3. 版本偵測:在同一份 YAML 中混合使用 YAML 1.1 的特性(yes 布林)與 1.2 的特性(010 字串),然後用不同解析器(PyYAML、ruamel.yaml、Go yaml.v3)解析,比較結果差異。

① 專案級端到端 Worked Example:雙平台 CI/CD 部署專案

一個產品同時用 GitHub Actions(公開 repo CI)與 GitLab CI(私有部署 pipeline)跑兩條管線,共享同一份 Docker Compose——這是「同一份 YAML 面對不同生態方言」的真實場景:

產出檔案樹

dual-ci-app/
├── .github/workflows/
│   └── ci.yml              # GitHub Actions(matrix + 快取)
├── .gitlab-ci.yml          # GitLab CI(stages + include)
├── docker-compose.yml      # 兩邊測試共用
└── Makefile                # 統一指令入口

關鍵檔案內容

.github/workflows/ci.yml
name: CI
on:
  push: {branches: [main]}
  pull_request: {branches: [main]}
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        node: [18, 20]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: {node-version: "${{ matrix.node }}"}
      - run: make test
.gitlab-ci.yml
stages: [test, deploy]

include:
  - project: infra/ci-templates
    file: /node-tests.yml

test:
  stage: test
  image: node:20
  script:
    - npm ci
    - make test
  parallel: 2
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

deploy:
  stage: deploy
  image: docker:27
  script:
    - docker compose -f docker-compose.yml up -d
驗證命令與輸出
$ actionlint .github/workflows/ci.yml && echo "gh OK"
gh OK

$ curl -X POST --header "PRIVATE-TOKEN: $CI_TOKEN" \
    "$GITLAB_URL/api/v4/projects/1/ci/lint" \
    --data-urlencode "content=$(cat .gitlab-ci.yml)"
{"status":"valid","errors":[]}

$ docker compose -f docker-compose.yml config --quiet && echo "compose OK"
compose OK
生態方言對照

① GitHub 用 on / jobs / steps;GitLab 用 stages / script / rules
② GitHub matrix 用 flow 序列;GitLab parallel: 2 更簡單
③ GitLab include 參考共用 template(reusable)
④ 兩邊都引用同一份 docker-compose.yml
⑤ 驗證工具完全不同:actionlint vs GitLab CI Lint API

關鍵洞察:同一個 CI 意圖(測、建、佈署)在兩個平台用不同 YAML 方言描述——語法關鍵字、觸發方式、變數語法全部不同。這就是「YAML 是方言的世界」,一份 YAML 換平台就是翻譯工程。

② 效能/品質/安全深度

品質:方言差異是移植 bug 的最大來源

語法GitHub ActionsGitLab CI
觸發on:(特例鍵名)rules: / only/except(舊式)
變數${{ }} expression$VAR / ${VAR}
共用reusable workflow / compositeinclude + extends
環境env: 層級眾多variables 層級眾多

移植時最容易漏:GitHub 的 env 是「對映」而 GitLab 的 variables 也是對映——但表達式與預設值語法完全不同,直接複製貼上必爆。

效能:CI 解析與快取

大型 monorepo 的 workflow 檔會拖慢每次 CI 的「排程前解析」。GitHub 有 workflow 快取與 paths 過濾;GitLab 有 rules:changes。用這些機制讓無關檔案變更不觸發整條 pipeline——這才是「YAML 效能」在生態系的真正意義。

安全:expression 注入與 secret 洩漏

安全提醒:GitHub 的 ${{ }} 若插值進 run:,可能被 github.event.issue.title 這類不可信輸入注入(CVE 等級的已知風險)。規則:不要把未處理的 event 資料插進 script;secret 只透過 env 注入,不寫進 YAML;CI 日誌永不 echo secret

③ 站際比較對照表

面向yamlmarkdownjsongithubgitlab
平台主力格式設定檔 / manifest文件 / README / wikiAPI 交換 / 設定workflow(YAML)pipeline(YAML)
觸發機制無(資料格式)on: 事件rules / schedule
CI 結構無(被描述)jobs → stepsstages → jobs
變數/secret${VAR}(執行期注入)frontmatter 變數env / secretsvariables / CI/CD variables
驗證工具yamllint / schemamarkdownlintJSON SchemaactionlintGitLab CI Lint API
安全性關注safe_load / Billion LaughsXSS(渲染時)prototype pollutionexpression 注入 / secret 外洩runner 隔離 / secret 外洩

④ 互動式檢核清單