GitHub Actions 進階:Path Filter、Concurrency、Cache 與 GitOps 自動更新
這篇記錄把 GitHub Actions pipeline 從基礎補到可用: trigger 設計、path filter、concurrency、cache, 到 CI 自動更新 deployment.yaml 完成 GitOps 閉環。最後對比 Jenkins 和 CircleCI。
Trigger 設計
三種 trigger 各自的用途:
|
|
| Trigger | 觸發時機 | 用途 |
|---|---|---|
push |
merge 到 main | 主線 CI, 每次合併後跑 |
pull_request |
PR 開啟或更新 | review 前驗證, 保護 main |
workflow_dispatch |
手動從 GitHub UI | hotfix 補跑、手動指定環境 deploy |
workflow_dispatch 加了 inputs 之後, GitHub UI 會出現下拉選單, 讓你在觸發時選擇環境。inputs 支援 string、boolean、number、choice 四種類型。
Path Filter:只在相關檔案變動時觸發
push README 不應該觸發 CI。paths filter 讓 workflow 只在有意義的檔案變動時才跑:
|
|
** 匹配任意深度的子目錄, * 只匹配單層。**.go 能匹配 handlers/health.go、middleware/auth.go, 而 *.go 只匹配根目錄的 .go 檔。
workflow_dispatch 不需要 paths, 手動觸發永遠跑。
if 條件:控制 Job 和 Step 執行
PR 的目的是 review, 不是上線。deploy job 不應該在 PR 觸發:
|
|
github.event_name 是 GitHub Actions 內建的 context, 值對應觸發的 trigger 名稱。pull_request 被排除在外, test 和 build 還是會跑, 確認 PR 可以合。
if 可以放在兩個層級:
| 層級 | 效果 | 範例 |
|---|---|---|
| job 層級 | 整個 job 跳過或執行 | deploy 只在 push 跑 |
| step 層級 | 單一 step 跳過或執行 | prod 環境才跑某個 step |
step 層級常見的內建函式:
|
|
Concurrency:防止同時跑多個 Deploy
sequenceDiagram
participant C1 as Commit A
participant C2 as Commit B
participant D as Deploy Job
C1->>D: trigger deploy (3 min)
C2->>D: trigger deploy (30s later)
Note over D: cancel-in-progress: true
cancel A, run B
sequenceDiagram
participant C1 as Commit A
participant C2 as Commit B
participant D as Deploy Job
C1->>D: trigger deploy (3 min)
C2->>D: trigger deploy (30s later)
Note over D: cancel-in-progress: true
cancel A, run B
sequenceDiagram
participant C1 as Commit A
participant C2 as Commit B
participant D as Deploy Job
C1->>D: trigger deploy (3 min)
C2->>D: trigger deploy (30s later)
Note over D: cancel-in-progress: true
cancel A, run BsequenceDiagram
participant C1 as Commit A
participant C2 as Commit B
participant D as Deploy Job
C1->>D: trigger deploy (3 min)
C2->>D: trigger deploy (30s later)
Note over D: cancel-in-progress: true
cancel A, run B
concurrency 讓同一組的 job 不能同時跑:
|
|
group 是識別 key, github.ref 是 branch 名稱, 讓 main branch 的 deploy 共用同一個 lock。
cancel-in-progress: true |
cancel-in-progress: false |
|
|---|---|---|
| 行為 | 新的進來, 取消舊的 | 新的排隊等 |
| 適合 | deploy(要最新版本) | database migration(不能中斷) |
concurrency 只放在 deploy job, 不放 test 和 build——每個 commit 都需要完整的測試記錄。
Go Module Cache:加速 CI
每次 runner 是全新環境, 所有 dependencies 重新下載。go.sum 沒變, 重新下載完全是浪費。
actions/setup-go v4 之後內建 cache:
|
|
cache: true 自動以 runner.os + go.sum hash 為 key。go.sum 沒變就命中 cache 跳過下載。
key 設計原則:把決定 cache 內容的檔案 hash 進 key。
| 情境 | Key 包含什麼 |
|---|---|
| Go | runner.os + hashFiles('**/go.sum') |
| Node.js | runner.os + hashFiles('**/package-lock.json') |
| Python | runner.os + hashFiles('**/requirements.txt') |
ENV_TAG:環境感知的 Image Tag
flowchart LR
A{trigger?} -->|workflow_dispatch| B[inputs.environment]
A -->|push to main| C[prod]
A -->|other branch| D[dev]
B --> E[ENV_TAG]
C --> E
D --> E
flowchart LR
A{trigger?} -->|workflow_dispatch| B[inputs.environment]
A -->|push to main| C[prod]
A -->|other branch| D[dev]
B --> E[ENV_TAG]
C --> E
D --> E
flowchart LR
A{trigger?} -->|workflow_dispatch| B[inputs.environment]
A -->|push to main| C[prod]
A -->|other branch| D[dev]
B --> E[ENV_TAG]
C --> E
D --> Eflowchart LR
A{trigger?} -->|workflow_dispatch| B[inputs.environment]
A -->|push to main| C[prod]
A -->|other branch| D[dev]
B --> E[ENV_TAG]
C --> E
D --> E
|
|
SHORT_SHA:跨 Step 共用變數
每個 run: block 是獨立的 shell, 變數不能直接跨 step 傳遞。GITHUB_ENV 是 GitHub Actions 的特殊檔案, 寫進去的變數在後續所有 step 都能讀到:
|
|
${GITHUB_SHA::7} 是 bash 字串截斷語法, 取前 7 字元。Docker push 和 deployment.yaml 更新都用同一個 SHORT_SHA, 保證 tag 一致。
GitOps 自動更新 deployment.yaml
CI build 完之後, 自動更新 k8s/base/deployment.yaml 的 image tag, push 到 gitops branch, ArgoCD 偵測到變更後 sync:
|
|
sed 用 | 而不是 / 作為分隔符, 是因為 image URL 裡有 /, 避免語法混淆。push 到獨立的 gitops branch 而不是直接 push main, 是因為 main 有 branch protection rule。contents: write permission 讓 runner 有寫入 repo 的權限。
Image Tagging:git SHA vs Semver
| Git SHA | Semver (v1.2.3) |
|
|---|---|---|
| 唯一性 | 天生唯一 | 需要人工維護 |
| 可追蹤性 | 直接對應 commit | 需要額外記錄 tag 對應的 commit |
| 語意 | 無 | 有, v1.3.0 vs v1.2.1 一眼看出 |
| 適合 | 內部服務、CI/CD 自動化 | 對外 API、library |
pipeline 用 {env}-{sha} 格式(例如 prod-7639a24), 一眼看出環境和 commit。
Jenkins vs GitHub Actions
|
|
| GitHub Actions | Jenkins | |
|---|---|---|
| 設定檔 | .github/workflows/*.yml |
Jenkinsfile |
| 語法 | YAML | Groovy DSL |
| Job 依賴 | needs: [test] |
stage 預設循序執行 |
| Branch 限制 | if: github.event_name == 'push' |
when { branch 'main' } |
| Cleanup | step-level if: always() |
post { always {} } |
| 架設 | 雲端, 不用管 infra | 需要自己維護 server |
| 整合 | GitHub 深度整合 | plugin 生態豐富 |
| 適合 | 雲端原生、開源專案 | 企業、on-premise、複雜 pipeline |
CircleCI 對比
|
|
| 概念 | GitHub Actions | CircleCI |
|---|---|---|
| Job 依賴 | needs: [test] |
requires: [test] |
| Branch 限制 | if: condition |
filters: branches: only: |
| 可重用套件 | Marketplace actions | Orbs |
| 跨 repo 共用 credentials | 每個 repo 各設 secrets | Contexts(org 層級共用) |
| 手動觸發 + 參數 | workflow_dispatch + UI |
API call + pipeline.parameters |
CircleCI 的核心優勢是 Contexts 跨 repo 共用——多個 repo 指向同一個 Context, credentials 集中管理, 改一次全部生效。這個優勢在中大型企業多 repo 環境才有明顯差異。