Jenkins 是什麼:CI 不是「有跑測試」,是「頻繁合回主幹」
· 更新於 · tech
📑 目錄
大部分人第一次碰 Jenkins,情境都差不多:公司有一台跑很久的 Jenkins,你要在上面「加一個 job」。點進去、複製隔壁專案的設定、改幾個欄位、按存檔——會動了,收工。這樣用了兩年,你會很熟練,但你對 CI 的理解可能還是零。
因為 CI 從來不是一台伺服器,而是一種團隊怎麼合併程式碼的紀律。這個系列的第一篇,我想先把幾個天天被混用的詞掰乾淨,再回頭看 Jenkins 到底站在哪個位置。
先把三個詞掰開:CI / Continuous Delivery / Continuous Deployment
這三個詞常被縮寫成一句「CI/CD」帶過,但它們是三段不同長度的路——差別在這條路自動化到哪裡為止,以及最後那顆按鈕是人按還是機器按:
我看過太多團隊在會議上爭論「我們要不要做 CD」,但講的其實是兩件不同的事:一邊在說「產物要隨時能上」,另一邊在說「不用人審就自動上」。這兩件事的難度差一個數量級,先講清楚是在講哪一個,討論才會有結論。
CI 的真義:不是「有跑測試」,是「頻繁合回主幹」
回到最前面那一段。CI 的 I 是 Integration(整合),不是 Inspection(檢查)。 它原本要解決的痛,是每個人抱著自己那份改動離主幹越來越遠,最後在合併那天一次爆炸。跑測試只是為了讓「頻繁合回」這件事變安全的手段,不是 CI 本身。
所以要判斷一個團隊有沒有在做 CI,我從來不看它有沒有 Jenkins,只問一句:你們的分支平均活多久? 超過兩三天,那就不是 CI,是「有一台會跑測試的伺服器」——測試跑得再勤,合併地獄一天都不會少。
這也是這個系列主軸的第一課:批次越小,越好退。 Google SRE 講發布工程時說的「高頻率、小步發」(自動化與發布工程),講的是同一件事的另一端——這裡是合併的批次,那裡是上線的批次。一次合回 3 個檔案,出事時 git revert 一下就乾淨了;一次合回 40 個檔案,你連要退哪一段都要開會討論。可回滾不是等到部署那一刻才要想的事,它從你決定分支要活多久的那一刻就決定了。
但「能不能合」,常常不是工程師能決定的
上面那個判準有個地方我要先講清楚,不然它讀起來會很不公平:分支活很久,往往不是工程師拖,是這個功能還不能被看到。 行銷檔期還沒到、合約還沒簽、要跟合作夥伴同時上線——這些都是商業決策,不是誰不想合。
但這正好戳中 CI 最關鍵、也最常被誤解的一件事:合併(merge)不等於發布(release)。 CI 要求的是「程式碼回到主幹」,從來沒有要求「功能對使用者可見」。一個團隊之所以會覺得「還不能合」,通常是因為它把這兩件事綁成同一件——主幹上有的東西,使用者就看得到。 一旦這條線鬆開,「什麼時候合」是工程決定,「什麼時候給誰看」是商業決定,兩邊各自最佳化,誰也不用等誰。
鬆開的手段有一整條階梯,由便宜到貴:
| 手段 | 適用 | 代價 |
|---|---|---|
| 不接線 | 新的類別、新的 API、新的頁面——合進去但不掛上路由/選單,使用者根本走不到 | 幾乎零 |
| Branch by abstraction | 要換掉既有實作:先合介面與新實作,舊的照常跑,最後才切換 | 多一層抽象,切換完要記得拆 |
| Feature flag | 真的要在執行期決定給誰看(灰度、A/B、按客戶開關) | 最貴——見下面 |
我的經驗是,大部分「還不能合」其實只要不接線就解決了。真正需要完整旗標系統的,是那種要在執行期分流的功能;把每個未完成的功能都塞進 flag,是在用最貴的工具解最便宜的問題。
Feature flag 確實會讓程式碼變髒,這點沒有必要粉飾
而且不是一點點:每個 flag 是一個 if,n 個 flag 名目上就有 2ⁿ 種路徑組合;測試矩陣跟著膨脹;最糟的是死掉的 flag 永遠沒人敢刪——那個功能早就全量上線兩年了,但沒人確定關掉那條 else 會不會壞。我看過旗標系統本身變成一個沒人維護的隱藏設定管理系統,那確實比長命分支還糟。
所以這件事該誠實地說成:這是拿一種債換另一種債。 但兩種債的形狀完全不一樣——而這才是我認為換得過的理由:
所以我對 feature flag 的立場是有條件的:要用,就要連清理紀律一起買。 具體是三條——每個 flag 上線時就寫下擁有者與預計移除時間;flag 只做「開關」不做「業務邏輯的分岔點」(不然它會長成第二套程式碼);定期盤點,超過期限的 flag 要嘛清掉、要嘛重新說明為什麼還在。做不到這三條的團隊,我不會建議它為了追求 trunk-based 而大量導入 flag——那真的會換來更糟的東西。
判準是拿來指向系統的,不是指向工程師
最後回到公平性。「分支平均活多久」這個判準,我從來不是拿來評價工程師努不努力——它評價的是這個組織有沒有把「合併」與「發布」解開。 如果答案是分支活三週、而原因是商業檔期,那結論不是「這些工程師沒在做 CI」,是「這個組織還沒有 CI,而那是組織層級的題目」。
這個區分很重要,因為它決定你要去說服誰。跟工程師講「你們要勤合併」沒有用,他們知道;真正要談的是跟產品與商業那一側達成一個共識:程式碼進主幹不代表功能上線,我們用不接線、抽象層或旗標把「看得到」這件事單獨控制,換來的是整合風險大幅下降、上線時間反而更可預測。這個對話,是我認為 tech lead 最該去打的一場——它比任何 CI 工具的導入都值錢。
那 Jenkins 到底是什麼
把上面那條路攤開之後,Jenkins 的定位其實很單純:它是一台「在某件事發生時,在某台機器上,照著一份腳本做事,並且把結果留下來」的伺服器。 就這樣。所有花俏的外掛都是這句話的延伸。
它的內部只有兩種角色——這是後面每一篇都會用到的地基:
Jenkins 的三個特徵,順著看就是它的優點,反過來看就是它的代價:
| 特徵 | 好處 | 代價 |
|---|---|---|
| 自架 | 機器、網路、資料都在自己手上;地端、內網、有合規要求的環境進得去 | 它變成一個你要備份、要升級、要顧權限的正式服務(這個系列後面會專門講怎麼養它) |
| 外掛生態 | 幾乎什麼都能接——舊的、冷門的、自家的系統都有辦法 | 什麼都能接=什麼都要自己維護;外掛版本互卡是 Jenkins 最著名的痛 |
| Jenkinsfile | 整條 pipeline 是程式碼,跟專案一起版控 | 得先學會把流程寫成程式碼,而不是在 UI 上點 |
第三點是這個系列真正的核心,而且它在 Jenkins 上是選配——你完全可以不寫 Jenkinsfile,一輩子在 UI 上點 Freestyle job。這也是為什麼下一篇要專門講:同樣一件事,寫成程式碼跟點在 UI 上,差的到底是什麼。
一個最小的 Jenkinsfile
先看它長什麼樣。這是一份能跑的完整 pipeline,放在 repo 根目錄,檔名就叫 Jenkinsfile:
// Jenkinsfile —— 放在 repo 根目錄,跟程式碼一起 commit、一起 review
pipeline {
agent { label 'linux' } // 這份工作要在帶 linux 標籤的 agent 上跑
stages {
stage('Build') {
steps { sh './gradlew clean assemble' }
}
stage('Test') {
steps { sh './gradlew test' }
}
}
post {
always { junit 'build/test-results/**/*.xml' } // 不管成敗都收測試報告
failure { echo '這次 build 紅了,先修再說' }
}
}
語法後面會慢慢講,這裡只要看懂三件事:agent 決定在哪跑、stages 是做什麼、post 是收尾。
但這篇真正想讓你記住的,不是語法,是這個檔案躺在 repo 根目錄這件事本身。它跟你的程式碼:
- 走同一個 commit——這版程式碼是怎麼 build 的,
git log查得到; - 走同一個 PR、同一次 review——改建置流程要有人看過,不是誰摸進 UI 改一改;
- 走同一次
git revert——流程改壞了,回滾它跟回滾程式碼是同一個動作。
這就是整個系列不打折的紀律:這條路上凡是能寫成程式碼的,就寫成程式碼、進 git。 從這份 Jenkinsfile,到共用函式庫,到最後連 Jenkins 自己的設定(JCasC)都是。
為什麼 2026 年還學 Jenkins
老實說,如果今天開一個全新的專案、程式碼放在 GitHub 上、也沒有奇怪的合規要求,我不會選 Jenkins——GitHub Actions 省掉的維運成本太可觀了。但我還是認為值得學它,有兩個理由——這跟 IaC 那條線的道理一樣:工具會換,但「把知識從人腦搬進版本控制」這個模型不會過期。
第一,它還在那裡。 地端機房、金融與醫療的內網、十幾年的舊系統、需要特殊硬體(iOS 簽章機、燒錄板子)的建置——這些地方 SaaS CI 進不去,而 Jenkins 進得去。接手一個有歷史的團隊,遇到 Jenkins 的機率高得驚人。
第二,它是共同祖先。 Jenkins 的每個概念在別家都有對應:
| Jenkins | GitHub Actions | GitLab CI |
|---|---|---|
| Controller / Agent | (託管的)Runner | Runner / Executor |
| Jenkinsfile | .github/workflows/*.yml | .gitlab-ci.yml |
| Workspace | Workspace | Job 的工作目錄 |
| Credentials | Secrets | CI/CD Variables(masked) |
| Shared Library | Reusable workflow / composite action | include: + template |
把 Jenkins 學透,換工具只是換語法。 反過來只會 GitHub Actions 的人,碰到 agent 排隊、workspace 髒掉、憑證外洩這些問題時,常常不知道自己在面對什麼——因為那些細節被託管環境藏起來了。系列最後一篇我會誠實談什麼時候該搬走,但那是讀完之後的判斷,不是一開始的偏見。
這個系列想回答的問題
貫穿後面十三篇的,其實只有一句話:一次提交,要能被信任地送上線。 而「可信」不是感覺,是三個可以逐項檢查的性質——
- 可重現:同樣的輸入,今天跑、半年後跑、在誰的機器上跑,結果都一樣;
- 可審查:這條路上的每一步,都攤在別人看得到、改得動、能 review 的地方;
- 可回滾:出事時退回去,跟送上去一樣快、一樣有把握。
而「pipeline 是程式碼、要進版控」之所以是全系列的預設立場,是因為它一次服務這三項:進了 git,就同時買到能 review、能追溯、能 git revert。這也是為什麼這篇的重點不是 Jenkins 有幾個外掛,而是那份躺在 repo 根目錄的檔案。
反思
我判斷一個團隊有沒有 CI,不看工具,只看分支活多久
早年我也覺得「CI = 有一台會跑測試的機器」。真正打醒我的是一次接手:團隊有 Jenkins、每次 push 都跑測試、儀表板一片綠,但每個人的 feature branch 都活兩三週。結果就是每個月都有一天叫「合併日」,那天所有人什麼事都做不了,只能解衝突;合併完的第一週線上一定出事,而且沒人查得出是誰的哪一段改的——因為那次合併有四十幾個檔案,是五個人三週的工作黏在一起。
那台 Jenkins 從頭到尾都在正常運作。它每天忠實地告訴大家「你的分支還是綠的」,而那個綠燈完全沒有意義,因為它驗的是一個跟主幹已經差很遠的世界。
所以現在我的第一個問題永遠是:你們的分支平均活多久? 這一題比「你們用什麼 CI 工具」有資訊量太多。工具兩週就能導入,把分支從三週壓到兩天,那是要動到拆任務方式、review 習慣、以及「合併與發布要不要解耦」的事——那才是真正的 CI。
但我也學會問第二題:如果分支活很久,是誰決定的? 如果答案是「功能還不能被看到」,那要修的不是工程師的習慣,而是前面講的那條線——先讓程式碼進得了主幹,再用不接線或旗標決定誰看得到。把這兩題分開問,對話才會落在真正的問題上,而不是變成一場檢討大會。
綠燈的價值,是靠「紅燈時停下來」養出來的
第二個常見的自欺,是 pipeline 紅了大家第一反應是「重跑一次看看」。我很堅持一條線:retry 用在網路,不用在測試。 網路抖動、拉套件逾時,重跑合理;測試紅了就重跑,那是在教全隊「紅燈是雜訊」。
一旦紅燈變成雜訊,這條 pipeline 就死了——它還在跑、還在花錢、還在浪費每個人等待的時間,但沒有人會因為它變紅而停下手邊的事。那時候你擁有的不是 CI,是一個很貴的螢幕保護程式。
我帶團隊的作法很簡單粗暴:main 紅了,全隊停下來先修好,不准往上疊新的 commit。 一開始大家覺得誇張,但實際上一個月只會發生兩三次,而且每次十幾分鐘就解決——因為批次夠小,問題就在剛才那一小段裡。真正貴的從來不是「停下來修」,是讓紅燈一直紅著,然後某天有人在紅著的主幹上又疊了三天的工作。
先問「這條路可不可信」,再問「要不要更自動」
最後一個心得偏管理面。很多團隊想跳過持續交付,直接談持續部署——「我們要做到 push 完就自動上線」。我通常會先潑一盆冷水:你敢把那顆按鈕拿掉,是因為你信任前面幾關;而信任是要有證據的。
我的順序一直是:先讓「隨時可上線」變成事實(產物可重現、關卡擋得住、回滾演練過),再來討論要不要拿掉那顆人工按鈕。倒過來做的團隊,最後不是回頭把按鈕加回去,就是在某次事故之後乾脆把整套自動部署關掉——那比一開始就沒做還糟,因為你同時失去了工具和大家的信心。
下一篇會從最實際的地方開始:同樣一個 build,設定在 UI 上點出來、跟寫成一份進 git 的 Jenkinsfile,差的到底是什麼——以及為什麼那個差別,在事故當下才會顯現。