← 返回首頁

GitHub Actions:沒人告訴你的那些事

𝕏 (Twitter)🔥
AI 中文摘要Claude 生成

GitHub Actions:沒人告訴你的那些事

我在 GitHub 工作,每天都使用 Actions。我也曾在凌晨兩點除錯 YAML,看著日誌檢視器(log viewer)吃掉我的瀏覽器記憶,並為了找出為什麼條件判斷式(conditional)無法正確執行,而連續推送(push)了一堆 commit。

我不是來告訴你 Actions 是完美的。它並不完美。日誌檢視器曾讓成熟的工程師懷疑自己的職涯選擇。YAML 表達式語法有一種學習曲線,感覺更像是「學習懸崖」。這種「推送-等待-失敗-重複」的除錯循環,可以把一個五分鐘就能修好的問題,變成一整個下午的「人質危機」。

我之所以知道這些,是因為我親身經歷過。而且我看過成千上萬的開發者也經歷過同樣的痛苦。

但我也看到了這一點:大部分的痛苦源於可以避免的模式。並非全部如此,有些是因為平台還在追趕技術發展。但很多問題其實現在就有解決方案,只是人們還沒發現,因為最簡單的路徑就是不斷複製貼上 YAML,然後繼續受苦。

這篇文章是我希望在第一天就有人告訴我的事。

別再使用日誌檢視器了

我是認真的。用於閱讀建置日誌的網頁 UI 是 Actions 最令人沮喪的單一來源,而最快的解決方法就是停止使用它。

gh run view --log-failed

就這樣。失敗的步驟會直接出現在你的終端機中,即時呈現。不需要點擊三個頁面,不需要等待瀏覽器決定今天是否要渲染,也不需要玩「上一頁」的輪盤賭。

如果你需要完整的日誌:

gh run view --log

如果你想即時觀看執行過程:

gh run watch

CLI 的速度更快,可以使用 grep 搜尋,而且當你的測試套件輸出 50,000 行內容時,它也不會崩潰。如果你在 2026 年還在透過網頁 UI 點擊閱讀建置日誌,這就是你該停手的訊號。

YAML 的問題是真實存在的。以下是如何縮減它。

每個 CI 系統最終都會變成「一堆 YAML」。Actions 也不例外。但一個清楚執行單一任務的 40 行工作流程(workflow),與一個包含巢狀條件判斷、矩陣策略(matrix strategies)以及會讓 Shell 程式設計師哭泣的內嵌 Bash 腳本的 400 行怪物,兩者之間是有區別的。

之所以會出現 400 行的怪物,是因為人們不知道——或者沒有使用——那兩個旨在防止這種情況的功能。

Reusable Workflows (可重複使用工作流程)

如果你在多個專案庫(repo)中有相同的 CI 步驟,你可能正在複製貼上工作流程。請停止這樣做。

# .github/workflows/ci.yml
jobs:
  build:
    uses: your-org/.github/.github/workflows/build.yml@main  # @main 對於你控制的內部專案庫來說沒問題 — 但如果你對其他所有東西都進行了 SHA 鎖定,請考慮也鎖定這個
    with:
      node-version: '20'
    secrets: inherit

一個工作流程,維護在一個地方,從各處呼叫。當你更新它時,每個使用它的專案庫都會獲得更新。不會有版本差異。不會再出現「等等,哪個專案庫才有我們部署腳本的最新版本?」這種困惑。

Composite Actions (組合式 Action)

Reusable Workflows 是為了整個管線(pipeline)設計的。Composite Actions 則是為了步驟(steps)——也就是建構模組——而設計的。

# .github/actions/setup-project/action.yml
name: 'Setup Project'
runs:
  using: 'composite'
  steps:
    - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
      with:
        node-version: ${{ inputs.node-version }}
    - run: npm ci
      shell: bash
    - run: npm run build
      shell: bash

現在你的工作流程檔案讀起來就像句子,而不是肥皂劇:

steps:
  - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
  - uses: ./.github/actions/setup-project
    with:
      node-version: '20'
  - run: npm test

YAML 依然存在。但它是 12 行,而不是 120 行。而且當設定變更時,你只需要在一個地方修改即可。

打破「推送-等待-失敗」的循環

Actions 除錯中最令人崩潰的部分是:你對工作流程檔案做了一個字元的修改,推送它,等待四分鐘讓 Runner 啟動,然後發現你少打了一個引號。經過一堆 commit 之後,你的 git 歷史紀錄看起來就像是在求救。

act 是一個由社群維護的工具,它可以在 Docker 容器中於本地執行你的工作流程。環境相同,無需推送。

act -j build

它並非完美的複製品——有些 GitHub 特有的 context 在本地並不存在。但對於「我是否弄壞了 YAML」以及「我的 Bash 腳本是否真的能運作」這類問題,它能將回饋循環從幾分鐘縮短到幾秒鐘。

在執行任何操作之前,若要進行簡單的語法驗證:

gh workflow view ci.yml

如果 YAML 有明顯的問題,它會很快顯示出來。它不是一個完整的 linter,但它能在不經過推送循環的情況下捕捉到基本錯誤。

Marketplace 的信任問題(以及該怎麼做)

每一個 uses: some-stranger/cool-action@v2 都是你沒寫過的程式碼,卻擁有存取你專案庫和 secret 的權限。這是一個真正的安全隱憂,而「鎖定到 SHA」是正確的答案,但沒人遵守。

以下是真正有效的方法:

  1. 鎖定到 SHA 並讓 Dependabot 管理更新:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

在註解中加上版本號,這樣人類就能閱讀。當有新版本發布時,Dependabot 會開啟 PR,你可以在更新前審查差異(diff)。(本文中的 SHA 已於 2026 年 3 月 30 日驗證。)

  1. 盡可能堅持使用 GitHub 維護的 Action。 actions/checkoutactions/setup-nodeactions/cache —— 這些是由建構該平台的同一個團隊所維護的。它們經過審計、測試並持續更新。

  2. 對於其他所有內容,閱讀原始碼。 它是開源的。如果一個 Marketplace 的 Action 只是包在 Dockerfile 裡的 20 行 Shell 腳本,也許直接把這 20 行複製到 run: 步驟中並自己維護會更好。

  3. 在採用之前,使用 OpenSSF Scorecard 來評估 Action 維護者的實踐方式。

在不崩潰的情況下處理條件邏輯

${{ }} 表達式語法是那種「簡單直到它變得不簡單」的東西,一旦變得複雜,就會令人困惑。關於字串插值(string interpolation)、真值(truthiness)和型別轉換的邊緣情況,每個人都至少被坑過一次。

幾條生存法則:

# 在 `if:` 中始終為表達式加上引號
if: ${{ github.event_name == 'push' }}

# 對於來自輸入(inputs)的布林值,使用 fromJSON
if: ${{ fromJSON(inputs.deploy) == true }}

# 多重條件?使用 >- 將換行符號摺疊為空格。
if: >-
  ${{ github.ref == 'refs/heads/main' &&
      github.event_name == 'push' }}

如果你的條件邏輯複雜到需要流程圖,那這就是一個訊號:你需要的是帶有輸入參數的 Reusable Workflow,而不是更多的 if: 語句。

case() 函數是最近新增的功能,值得了解。把它想像成表達式的 switch 語句。它取代了沒人看得懂的巢狀三元運算子。

讓 Copilot 撰寫 YAML

我不會假裝寫 YAML 很有趣。但在 2026 年,你不需要親自寫大部分的內容。

在安裝了 GitHub Copilot 的 VS Code 中,在註解中描述你想要的內容:

# Deploy to production on push to main, run tests first,
# cache node_modules, notify Slack on failure

Copilot 會產生工作流程。你進行審查和調整。它會處理語法、on: 觸發器、runs-on:、步驟順序,讓你專注於管線應該做什麼,而不是去記住 environment 應該放在 jobs: 裡面還是外面。

對於現有的工作流程,Copilot Agent 模式可以將 400 行的 YAML 檔案重構為 Reusable Workflows 和 Composite Actions。告訴它你想要什麼,然後審查 PR。

這並不能修復 Actions 本身。它修復的是你必須直接與它搏鬥的問題。

快取(Caching):你可能沒在用的免費速度

如果你的工作流程在每次執行時都安裝依賴項,且你還沒設定快取,那你就是在浪費免費的時間。

- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
  with:
    node-version: '20'
    cache: 'npm'

那一行 —— cache: 'npm' —— 告訴 setup-node 在執行之間快取你的 node_modules。第一次執行是正常的。之後的每次執行都會跳過完整的 npm ci 下載。對於大型專案,這可以讓每次建置節省兩到四分鐘。

相同的模式也適用於其他生態系統:

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
  with:
    python-version: '3.12'
    cache: 'pip'

# Go (手動快取)
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
  with:
    path: ~/go/pkg/mod
    key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

如果你需要更多控制權,actions/cache 讓你快取任何東西 —— 建置產物(artifacts)、Docker 層、編譯後的二進位檔案。關鍵在於對你的鎖定檔案(lockfile)進行雜湊(hashing),這樣當依賴項真正變更時,快取就會失效。

停止重複執行相同的工作流程

如果你的團隊推送速度很快,你可能見過這種情況:十分鐘內三個 commit,排隊了三個工作流程執行,當前兩個完成時,它們已經過時了。

並行群組(Concurrency groups)可以解決這個問題:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

將此內容加在工作流程的最頂層。相同的分支,相同的工作流程 —— 當新的執行開始時,舊的執行就會被取消。不再浪費 Runner 時間在已經被取代的 commit 上。

這對於開發者快速迭代的 PR 工作流程特別有用。沒有它,你就是在為沒人會看的建置付費。

什麼正在變得更好

我說過我不會美化這一切,所以讓我具體說明什麼已經改進,以及什麼仍需要努力。

現在變得更好:

  • Job summaries,結構化的輸出,而不僅僅是日誌行。

  • 更大的 Runner,付費方案(Team/Enterprise)提供 64 核心機器,ARM Runner 已在公開專案庫中正式發布(GA)。

  • 透過 rulesets 強制執行必要的工作流程,無需複製貼上即可實現組織級別的政策。

  • 不可變的 Action,發布到 GHCR 並帶有來源證明(provenance)的 Action。

  • case() 和最近的表達式改進。

仍需要努力:

  • 日誌檢視器。它已經改進了,但對於大型建置來說還不夠好。請使用 CLI。

  • 除錯體驗。act 有所幫助,但第一方的本地執行將會改變一切。

  • 表達式的學習曲線。文件團隊一直在穩步改進這一點,效果顯著 —— 但仍有成長空間。

我寫這篇文章不是為了捍衛 GitHub 的名聲。我寫它是因為我看過太多團隊在那些已有解決方案的問題中掙扎,而這些解決方案傳遞給人們的速度不夠快。

基礎已經具備。平台是可以運作的。但「可以運作」和「令人愉悅」是兩回事,而它們之間的差距就是你下午時間消失的地方。這些模式縮小了那個差距。雖然不是完全消除,但已經足夠了。


本文來自《Main Branch》,這是一份關於 GitHub 功能和基礎知識的每週電子報。沒有廢話,沒有炒作 —— 只有讓你更擅長交付產品的乾貨。 📖 Leer en español · 閱讀中文版