一個版本部署完成後,如果過一陣子回頭問:當時測了什麼、誰核准、部署的是哪一個建置產物,還找得到嗎?整理 CI/CD 時,我除了讓每次交付都能重複執行,也希望這些問題都能從流程留下的紀錄裡找到答案。
Pipeline 自動部署,省下了很多手動操作;但事後查問題時,還需要找回這個版本的測試結果、核准紀錄與建置產物。要留下哪些資料,應該在設計流程時就決定,等到要查時才補,往往已經來不及。
CI 平台的執行紀錄與建置產物多半有保存期限,到期就會自動刪除;如果沒有依追查需要調整,時間一久,當時的結果就找不到了。所以我會先確認各種紀錄留多久,以及建置產物能不能對回需求與程式碼。

圖 Day 26-1:可稽核 CI/CD。
我希望每一關都留下結果:從需求、程式碼,到建置、測試、掃描、核准、部署與部署後的 smoke test,都能一路對應起來,接手的人不用重新猜當時做過什麼。所以圖的最後一格是 Evidence,它不是另一個步驟,而是前面各關留下的紀錄合起來的結果。
不過,這些紀錄要能對上,前提是部署出去的和測試過的是同一個 image。所以我採用 Build once, deploy many:image 只建置一次,之後每個環境都部署同一個 image,不重新建置。因為重新建置時,相依套件或基底 image 可能已經更新,部署的就不一定是測試過的那一份。
部署與驗證紀錄要對應到同一個版本,image 本身就要帶著能對回原始碼的識別資訊。所以每個 image 除了一般的 tag,都另外加一個由 commit SHA 組成的 tag。
在 GitHub Actions 裡,版本識別、核准與 secret 這幾項要求,都落在幾行設定上。以下是目前 workflow 的簡化節錄:
jobs:
build-push:
needs: test # 測試沒過就沒有 image
permissions:
contents: read
packages: write # 只給推 registry 需要的權限
steps:
- uses: docker/build-push-action@v7
with:
push: true
tags: |
ghcr.io/<org>/${{ matrix.service }}:${{ steps.tag.outputs.tag }} # main 為 latest,其他分支 develop-latest
ghcr.io/<org>/${{ matrix.service }}:sha-${{ github.sha }} # 每個 image 都能對回 commit
deploy:
needs: build-push
if: github.ref_name == 'main'
environment: production # GitHub Environment 保護:可設定 required reviewers
steps:
- uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ secrets.DEPLOY_HOST }} # 連線資訊全部走受控 secret
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
cd /opt/app
test -f .env || { echo "ERROR: .env missing on server"; exit 1; } # .env 不經 pipeline 傳輸
export TAG=sha-${{ github.sha }} # 部署剛建好的那一版,不是 latest
docker compose pull && docker compose up -d --remove-orphans
curl -sf --max-time 10 http://localhost:9090/actuator/health || exit 1 # smoke test
這段有三個我刻意留下的痕跡。一是因為 latest 會隨下一次建置移動,事後看不出當時跑的是哪一版,所以 image 的 tag 由 commit SHA 組成,部署時也指定這個 tag。二是 deploy job 綁在 production environment 上,在平台設好 required reviewers 之後,核准者與時間就會由平台記錄。三是 .env 從不經過 pipeline,連線帳密就不會出現在 CI 的 log 裡;伺服器上找不到就直接失敗,而不是悄悄用預設值跑起來。
Secret 放在 GitHub Secrets,不寫進 repository 或 Log。GitHub 會自動遮掉 log 裡出現的 secret,但主要靠比對完全相同的字串;如果 secret 經過 base64 等轉換,或被包在 JSON、YAML 裡,印出來時就可能比對不到而留在 log 裡。所以除錯時不要把設定整份印出來,轉換過的值也要用 ::add-mask:: 另外登錄遮罩。
我希望最後串起來的證據鏈是:PR 透過範本裡的相關單號連到需求,pipeline 留下測試、掃描、核准與部署結果,正式環境再配合 Environment Protection 與最小權限。緊急變更也要保留核准,事後補齊原因與驗證,證據鏈才不會在這裡斷掉一段。
| 取捨 | 這樣選的理由 | 何時要重新評估 |
|---|---|---|
| Build once, deploy many | 正式環境跑的必須是測試過的 image | 無 |
| 檢查關卡先統一,再視需要依風險分級 | 低風險變更不必承擔全部檢查成本 | 變更量變大、統一檢查拖慢交付時 |
| 必要檢查不可略過 | 能略過的檢查,趕時間時很容易被略過 | 無 |
| 緊急變更事後補齊證據 | 允許快速處理,但不放棄可追溯性 | 緊急變更比例過高時,檢討常規流程 |
分級的意思是:低風險的變更可以少跑幾關,但標為必要的那幾關,不論風險高低都要跑完。能穩定自動判斷的,就寫成自動檢查;需要人判斷的,也要寫清楚該看什麼,否則核准很容易變成只是按一下同意。
| 指標 | 計算方式 | 想回答的問題 |
|---|---|---|
| 部署頻率 | 單位時間的部署次數 | 交付是否順暢? |
| Lead Time for Changes | 從 commit 到上線的時間 | 流程是否有瓶頸? |
| Change Failure Rate | 需修復或回退的部署/全部部署 | 檢查關卡是否有效? |
| pipeline 成功率 | 成功執行數/全部執行數 | 流程本身是否穩定? |
| 回退時間 | 從決定回退到完成的時間 | 出問題時能多快恢復? |
如果部署變頻繁,失敗率卻也上升,就要繼續查品質與流程出了什麼變化。這種情況要幾項對照才看得出來,所以我會把這五項放在一起看。前面 Day 03 建好的定義與基準,到了這裡就能拿來對照。
今天想留下的,是一份能重複執行,也能回頭說明的交付流程。下一篇,再談部署平台,看看 Docker Compose 與 Kubernetes 要怎麼依需求選擇。