iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0

Day 23:用 Jenkins 把合規檢查焊進部署管線

把該做的掃描都焊進去

架構定案是把三種掃描焊進 Jenkins pipeline:SonarQube 掃原始碼品質、OWASP ZAP 掃網站行為層弱點、Trivy 掃 image 裡的套件漏洞,加上前幾天談過的完整性驗證機制。測試機全套都跑,正式機只負責部署,不重複掃。

這中間有一段判斷力上的來回,是這篇最值得記的部分。

Trivy 掃了半天,掃的東西我根本管不到

Trivy 的 CVE 報告跑出來之後,我看了報告內容,直接提出質疑:這份報告掃的是容器裡的 bash、curl 這類系統套件,版本是 base image 決定的,我根本控制不了,掃出漏洞也沒辦法動——這個掃描對我來說沒有意義。

AI 認同這個判斷,而且說得更精確:Trivy 預設掃的是 OS 層套件,版本綁死在 node:24-bullseye 這類 base image 上,除非換 base image 否則改不了;真正對這個專案有意義的,是 package.json 裡自己選用的相依套件,那才是團隊真正能控制、也需要負責的東西。它改了掃描範圍,讓 Trivy 只掃 package.json,不再掃整個容器的 OS 層,另外也考慮過改用 npm audit,最後折衷在 Trivy 只鎖定套件層、同時順手產出軟體物料清單(SBOM)。

這個轉折不是 AI 主動想到的,是人先看出「這份報告在測什麼我管不到的東西」,才把掃描範圍收斂到真正該負責的那一層。掃描能不能跑起來是一回事,掃出來的東西值不值得花心力回應,是另一回事。

一個「蓋掉了」的坑:node_modules 消失了

管線跑起來之後,容器起不來,log 顯示 Cannot find module 'dotenv'——明明 image 裡已經裝好套件了,容器卻找不到。

追下去的原因,是 docker-compose.yml 裡有一個常見的 Docker 技巧:先把整個專案目錄用 bind mount 掛進容器,再對 node_modules 那個路徑額外宣告一個匿名 volume,用意是讓容器優先使用 image 裡裝好的套件,不被 host 端空空如也的目錄蓋過去。這個技巧本身沒有錯,但有個前提:匿名 volume 必須是空的,才會去讀 image 裡的內容。而這個專案的部署流程裡,rsync 同步程式碼時本來就排除了 node_modules,代表 bind mount 之後那個路徑本身就是空的,匿名 volume 跟著也是空的——結果是空目錄把 image 裡辛苦裝好的套件整組蓋掉了。

這個問題後來被順利解掉,但故事沒有在這裡結束。

累積型副作用:解法本身,又生出新問題

專案上線一段時間後,我回頭問了一句:當初為什麼用匿名 volume,而不是具名的?

AI 解釋了原始邏輯——匿名 volume 的用途是「一次性隔離」,不需要跨容器共享、也不需要持久保存,容器重建後 node_modules 反正會從 image 重新來,用匿名 volume 沒有問題。這個邏輯在測試機上完全合理,因為測試機本來就能重新 build image。但正式機的情況不一樣——正式機不能重新建置,用的是 image 裡打包好的版本,這代表 node_modules 這個匿名 volume 在正式機上,其實承擔了「持久保存套件」的責任,跟它原本設計的用途已經不一樣了。

我把真正的觀察講出來:正式環境經過一次次的 docker-compose down && up 重新部署之後,已經生出了一堆孤兒 volume,吃掉主機空間。AI 確認這正是匿名 volume 的常見問題——每次重啟都會產生一個新的匿名 volume,舊的不會自動清掉,時間一長就是一堆看不出用途、又占空間的殘留物。

解法是改成具名 volume,固定一個名字,就不會每次部署都多生一個。但這個解法馬上帶出下一個坑:具名 volume 一旦有資料,Docker 就不會再用 image 的內容去覆蓋它。換句話說,如果新版 image 更新了 npm 套件,具名 volume 裡的內容還是舊的,套件更新形同虛設。

最後的方案是把「刪掉舊的具名 volume」這個動作,直接寫進更新 image 的部署流程裡——每次 load 新 image 之後,先刪掉那個固定名字的 volume,再重新啟動,讓容器重新從新 image 填入內容。中間還有一個小心思:一開始想用 docker volume prune 一次清掉所有沒被使用的孤兒 volume,但這個指令會不分青紅皂白,把同一台主機上其他專案「暫時停著但沒被刪除」的容器所使用的 volume 也一併清掉。最後改成只刪自己專案那個指定名字的 volume,不動到別人的東西。

這一天的分工

這篇的核心跟 Vite 那次遞迴增生的事故是同一種模式:決策鏈上每一步在當下都是對的,卻沒有人在一開始就預見全部後果。匿名 volume 在測試機上是合理的一次性隔離,套用到正式機的長期運作情境裡才顯出問題;改成具名 volume 解決了空間累積,卻又製造了「更新失效」的新坑。AI 在每一步的技術判斷都站得住腳,但它在算的永遠是「這一步對不對」,不是「這樣跑一百次會變成什麼樣子」——這件事只有回頭觀察正式環境實際累積出來的孤兒 volume,才會被看見。

人在這篇裡做的兩件事,都不是技術判斷,而是持續盯著「這個東西實際運作起來是什麼樣子」:一是看穿 Trivy 報告在測一個自己管不到的東西,把掃描範圍收斂回真正該負責的那一層;二是隔了一段時間回頭問「這東西是不是會一直累積」,把一個運作中卻悄悄在吃空間的問題揪出來,逼著整條部署邏輯往前再修一輪。

這條管線最後長什麼樣

講了半天判斷與踩坑,不如直接看這條管線最後跑成什麼樣子。下面是其中一次由程式碼變更自動觸發的完整建置——從取碼、掃描、建置、部署到健康檢查,一條龍跑完,每一關都綠燈:

建置 #22 由 SCM 變更觸發 總時長 6 分 03 秒 全數通過

  ✅ Checkout(5 秒)
  ✅ 準備設定檔(1 秒)
  ✅ SonarQube 原始碼掃描(3 分 07 秒)
  ✅ Build(18 秒)
  ✅ 部署(13 秒)
  ✅ 健康檢查(18 秒)
  ✅ Trivy 套件掃描(35 秒)
  ✅ OWASP ZAP 弱點掃描(1 分 22 秒)
  ✅ SonarQube 品質閘門(0.2 秒)
  ✅ Post Actions(12 毫秒)
        └─ 建置、部署、掃描全部完成

這張成果真正的重點不是「每一格都綠」,而是中間那幾格掃描——SonarQube、Trivy、OWASP ZAP——現在都是自動跑、跑不過就擋下部署,不再靠某個人記得在上線前手動掃一遍。前面那些對掃描範圍、對 volume、對誠實邊界的判斷,最後全被焊進了這條管線,變成每次 commit 都會自動執行一次的紀律。這才是把判斷「沉澱成規範」最具體的一種形式——也正好接上明天要談的主題。

資安合規這一段,到這裡告一段落。七天下來,從判讀弱掃與滲透報告、到組態基準與完整性監控的落地、再到把稽核與掃描焊進部署管線,表面上是七個不相干的技術主題,底下貫穿的其實是同一件事:在這個 AI 懂得比我多的領域裡,知識可以問它,判斷得自己守。

接下來幾天換一個層次。前面這些判斷,如果每次協作都要重新踩一次、重新講一次,那就太貴了。所以明天起談的是:怎麼把這些散落在一次次對話裡的判斷,沉澱成文件與規範,讓每一個新的工作階段都能接上前面的結論、而不用從頭來過。先從那份被我當成「專案憲法」的 CLAUDE.md 開始。


上一篇
Day 22:從一張不合格的稽核表,到自建集中式 log server
系列文
AI 負責答,我負責讓答案可信——公部門工程師的 30 天 Claude 協作紀律 共 23 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言