回到部落格
2026年4月6日5 分鐘閱讀

系統設計10:CI/CD Pipeline System(以 GitHub Actions 為例)

從控制層與資料層的解耦、Pull 模型必然性到 MicroVM 隔離,拆解現代 CI/CD 系統的底層調度架構。

《從 Web 到 AI-Native:一個新鮮人的系統設計五維度推演筆記》 系列學習筆記系統設計

前言

當我們 push 程式碼到 GitHub,幾秒鐘後 GitHub Actions 就會自動啟動並開始編譯、跑測試、甚至部署到雲端。你是否想過,要支撐全球千萬名開發者同時觸發工作流,並且安全地執行各種不可信腳本,背後的系統架構長什麼樣子?

設計一套 CI/CD 系統的複雜度,遠高於一般非同步任務排程器(Job Scheduler)。本篇文章透過五維度分析法,拆解這套系統的控制層(Control Plane)與資料層(Data Plane)設計哲學。

這篇你可以帶走什麼

  • 為什麼 CI/CD 系統必須嚴格劃分控制平面與資料平面
  • Runner 獲取任務為何只能用 Pull 模型,而非 Push
  • 面對 DAG 任務相依性,資料庫該如何設計與分片
  • 如何安全、低延遲地執行不可信程式碼,且為何需要「短暫生命週期(Ephemeral)」
  • 海量日誌與建置產物(Artifacts)的傳輸策略
  • 機密憑證(Secrets)的安全傳遞與遮罩防護

維度一、可直接觀察的事實:系統邊界與核心特徵

CI/CD 系統的生命週期非常明確,主要分為兩個階段:

  • 觸發階段:開發者提交程式碼,平台收到 Webhook 事件。
  • 執行階段:系統解析 YAML,根據相依性分配執行節點,並即時回傳 Log。

這類系統具備幾個鮮明流量特徵:

  • 流量龐大且突發:每天可能有數十億次事件觸發。
  • 多租戶特性(Multi-tenancy):系統必須區分免費與付費租戶,提供不同優先級與資源配額(Quota)。
  • 嚴苛的安全隔離要求:你等於在自己的基礎設施上執行未知且可能惡意的腳本。

重點:CI/CD 的本質不是「幫你跑腳本」,而是「在不可信多租戶環境中穩定且可控地跑腳本」。

維度二、條件檢查:架構解耦與安全執行的最佳解

控制層與資料層的絕對解耦

CI/CD 的黃金法則是把系統切成兩層:

  • 控制層(Control Plane):接收 Webhook、解析 YAML、維護 DAG 狀態、決定任務派發目標。
  • 資料層(Data Plane / Runner):只負責拿到腳本後執行,並回傳狀態與日誌。

執行環境最佳解:MicroVM 與短暫生命週期(Ephemeral)

在資料層執行不可信程式碼時,常見抉擇是容器與虛擬機:

  • 容器啟動快,但共用宿主機核心,隔離較弱。
  • 傳統 VM 隔離強,但啟動慢,會拖累 CI/CD 體驗。
  • 最佳平衡(MicroVM):如 Firecracker 類型的微虛擬機,在安全與啟動延遲間取得完美平衡,適合高頻啟停。
  • 短暫生命週期(Ephemeral):這是官方 Runner 的核心防護機制。每次 Job 執行完畢,這台 VM 就會被直接銷毀(Destroy),下一個 Job 會開一台全新的 VM。這保證了絕對的「狀態隔離」,避免上一個任務的殘留檔案或惡意木馬毒害下一個使用者。

重點:CI/CD 執行節點要同時滿足高隔離與低延遲,MicroVM 搭配 Ephemeral(用完即丟)是現代標準做法。

維度三、反證檢查:跳脫直覺的通訊與資料庫盲點

盲點一:控制層應該直接 Push 任務給 Runner?

直覺上,既然控制層掌握了全局的排程狀態,直接將任務「推(Push)」給閒置的 Runner 似乎是最快且架構最乾淨的作法。然而,現實情況是許多企業為了資安考量,會將 Self-hosted Runner(自建執行節點)部署在自家內網的 NAT 或嚴格的防火牆後方,這導致外部的控制層根本無法主動發起連線(Inbound Traffic)穿透進去。因此,實務上的正解是採用「拉(Pull)模型」搭配長輪詢(Long-polling)。由 Runner 主動向 Runner Gateway 請求任務以順利穿透防火牆;同時,為了防止 Runner 領走任務後因記憶體耗盡(OOM)或斷線而導致任務石沉大海,系統會強制要求 Runner 在執行期間定期發送心跳訊號(Heartbeat)來維持任務租約(Lease),一旦租約逾時,控制層便能安全地收回任務並重新派發。

盲點二:資料庫分片用 workflow_id 最平均?

當面臨海量資料需要進行資料庫分片時,工程師的直覺往往是使用 Hash(workflow_id),因為其基數(Cardinality)極大,能最大程度地將寫入流量均勻打散到各個節點,避免寫入熱點(Hotspot)產生。但反證來看,資料庫的 Schema 設計必須優先服務於真實的「讀取模式(Query Pattern)」。在 CI/CD 系統中,使用者最頻繁的操作其實是「列出我們組織(租戶)底下所有的 Workflow 狀態」;如果用 workflow_id 分片,這個極為普通的查詢就會演變成災難性的「跨分片查詢(Cross-shard Query)」,不僅消耗大量運算資源,還會嚴重拖垮查詢延遲。因此,架構的正解是以 tenant_id(租戶 ID)作為 Sharding Key,寧可承擔一點大客戶帶來的資料傾斜風險,也要優先保證這類高頻的核心業務查詢能精準命中單一分片,以維持系統的高效讀取。

維度四、不確定性分析:資料傳輸與機密管理策略

挑戰一:海量日誌與產物的傳輸策略

觀察事實:Runner 在執行過程中,會噴出綿延不絕的日誌(Logs),結束時甚至會產出好幾 GB 的建置產物(Artifacts,如 Docker Image)。

核心假設:如果我們讓所有日誌和產物都「原路」傳回控制層,控制層的網路頻寬與記憶體絕對會瞬間被打爆。

核心對策:控制訊號與巨量資料「徹底分流」,必須將傳輸路徑拆分為兩條:

  • 輕量訊號(走控制層):Runner 只透過 Gateway 回傳「任務成功 / 失敗」的狀態,並利用 WebSocket 推送少量的「即時日誌切片」給前端 UI,維持使用者的觀看體驗。
  • 巨量資料(旁路傳輸,直連儲存層):控制層在派發任務時,會附上一組預先簽名網址(Pre-signed URL)。Runner 執行完畢後,拿著這個 URL 將好幾 GB 的產物與完整日誌「繞過控制層」,直接上傳到 Object Storage(如 AWS S3)。下游的 Job 也能直接從 S3 下載所需的產物,讓控制層做到真正的零負擔。

挑戰二:機密憑證(Secrets)的傳遞風險

觀察事實:CI/CD 腳本不可避免地需要讀取資料庫密碼或 AWS 部署憑證。

直覺盲點:把 Secrets 寫死在資料庫的 Job 定義裡,然後透過 API 隨任務一起明碼傳給 Runner。

核心對策:Secrets 在資料庫中必須是非對稱加密的。控制層派發任務時,只傳遞加密後的字串;Runner 拿到任務後,在執行環境的記憶體(RAM)中動態解密,並注入到環境變數(Environment Variables)裡,絕對不能將 Secrets 寫入實體硬碟。此外,Runner 的日誌回傳模組必須內建 Filter,將意外印出的密碼字串自動替換成 *** 遮罩,防止機密外洩。

維度五、最終結論與行動建議

實務上的系統架構藍圖:

  • API Gateway & Event Bus:接收 Webhook 後寫入事件流(如 Kafka),支援削峰填谷與租戶優先級策略。
  • Orchestrator:讀取 DAG 依賴,判斷可執行節點,推送任務到 Runner Gateway。
  • Runner Gateway:維護 Long-polling 連線與 Lease 狀態。
  • Data Plane:以 Ephemeral MicroVM 隔離執行腳本,並把 artifacts 旁路上傳至 Object Storage。

破關路線圖(信心度 95%)

  • 先完成控制平面與資料平面的解耦,確立 Runner Pull 模式。
  • 梳理資料庫 Schema(Workflow、Job、Dependency Edge),並確立 tenant_id 分片策略。
  • 導入 Pre-signed URL,讓大檔傳輸旁路控制層。
  • 確保執行環境的用完即丟(Ephemeral)特性與 Secrets 動態解密機制。

這是現代 CI/CD 系統在擴展性、安全性與體驗之間的主流平衡點。

脆弱點提示

若日誌量大到前端 WebSocket 無法承受(例如每秒噴出數萬行 Log),現有輕量推播機制會拖垮前端渲染與閘道。

此時需要引入獨立日誌管線(例如 Elasticsearch + Logstash 類型),先做採樣、聚合或分層索引,再回推給前端。


一句話總結

設計 CI/CD 系統,本質上是在經營一個巨大分散式狀態機:誰能把控制層的任務編排與資料層的安全執行解耦得夠乾淨,誰就能持續提供流暢穩定的開發者體驗。