版本控制只是起點。讓每次提交都能自動建置、測試、並安全地送到使用者面前,才是現代交付的核心。本章涵蓋 CI 原則、部署管線各階段、.gitlab-ci.yml 的漸進式組態,以及發布策略。
一、持續整合的核心原則#
持續整合(Continuous Integration, CI)不是一套工具,而是一組紀律:
- 頻繁提交:每位開發者每天至少把變更整合進共用主線一次,讓衝突小而易解。
- 保持綠色建置:主線的建置必須隨時可通過。提交前先在本地確認測試通過。
- 快速回饋:建置與測試要快(理想數分鐘內),開發者才會願意頻繁等待結果。
- 壞了優先修:一旦主線建置變紅,修復它是團隊的最高優先事項,停止往破損的建置上堆疊新變更。
CI 的價值來自「持續」。每週整合一次的團隊得到的不是 CI,只是「偶爾跑一下測試」。整合的頻率決定了回饋的價值。
flowchart LR
A[開發者提交] --> B[CI 自動觸發]
B --> C{建置 + 測試通過?}
C -->|是| D[綠色建置<br/>可繼續開發]
C -->|否| E[紅色建置<br/>全員優先修復]
E --> A
D --> A「建置壞了但先讓它紅著、晚點再修」是 CI 紀律崩壞的開始。紅燈累積會讓團隊對 CI 結果麻木,最終失去快速回饋的能力。
二、部署管線各階段#
部署管線(deployment pipeline)把一次提交從程式碼推進到生產環境的全過程自動化、可視化。越往後的階段越接近真實環境、執行越慢、但信心越高。
flowchart LR
C[Commit<br/>編譯/單元測試] --> B[Build<br/>打包產物]
B --> T[Test<br/>整合/驗收測試]
T --> S[Stage<br/>類正式環境驗證]
S --> P[Prod<br/>正式部署]| 階段 | 做什麼 | 失敗代價 | 執行速度 |
|---|---|---|---|
| Commit | 編譯、靜態檢查、單元測試 | 低 | 秒~分 |
| Build | 打包成可部署產物(artifact) | 低 | 分 |
| Test | 整合測試、自動化驗收測試 | 中 | 分~十分 |
| Stage | 部署到類正式環境,做煙霧/非功能驗證 | 中~高 | 十分 |
| Prod | 部署到正式環境 | 高 | 視策略 |
管線的設計原則是快速失敗:把最便宜、最快的檢查(編譯、單元測試)放最前面,讓多數錯誤在數秒內就被擋下,昂貴的測試只對已通過前段的產物執行。
同一份產物貫穿全程:在 Build 階段產生的 artifact 應一路用到 Prod,而非每個環境重新編譯。重新編譯會引入「在我環境能跑」的不確定性。
三、.gitlab-ci.yml:由簡到繁#
GitLab CI 以倉庫根目錄的 .gitlab-ci.yml 描述管線。以下範例逐步加上常用能力。
最小可行#
stages:
- test
unit-test:
stage: test
script:
- echo "Running tests"
- ./gradlew test加入多階段與建置產物#
stages:
- build
- test
build:
stage: build
script:
- ./gradlew assemble
artifacts:
paths:
- build/libs/
expire_in: 1 hour
unit-test:
stage: test
script:
- ./gradlew test
artifacts:
when: always
reports:
junit: build/test-results/test/*.xml
artifacts讓後續 job 取用前一階段的產物(如build編譯出的 jar)。reports:junit則讓 GitLab 在 MR 介面直接呈現測試結果。
加入條件規則與多環境部署#
stages:
- build
- test
- deploy
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
build:
stage: build
script:
- ./gradlew assemble
artifacts:
paths:
- build/libs/
unit-test:
stage: test
script:
- ./gradlew test
deploy-staging:
stage: deploy
script:
- ./deploy.sh staging
environment:
name: staging
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
deploy-production:
stage: deploy
script:
- ./deploy.sh production
environment:
name: production
rules:
- if: "$CI_COMMIT_TAG" # 只有打 tag 時才部署正式環境
when: manual # 需人工點擊才執行用
rules取代舊的only/except。rules支援更精細的條件組合(分支、tag、變數、檔案變更),是目前官方推薦的寫法。when: manual為高風險的正式部署加上人工閘門。
機密(部署金鑰、token)絕不寫進
.gitlab-ci.yml。改用 GitLab 專案的 CI/CD Variables,並標記為 Protected(僅 protected 分支可用)與 Masked(日誌中遮蔽)。
Runner Executor 對比#
job 實際在 GitLab Runner 上執行,Runner 可用不同 executor:
| Executor | 隔離性 | 環境一致性 | 成本/維運 | 適用場景 |
|---|---|---|---|---|
| Shell | 低 | 差(依賴主機) | 低 | 簡單腳本、自建單機 |
| Docker | 高 | 好(映像固定) | 中 | 多數專案的預設選擇 |
| Kubernetes | 高 | 好 | 高 | 大規模、需彈性擴縮的 job 平台 |
四、發布策略#
部署到正式環境的方式直接決定上線風險。
flowchart TB
subgraph BlueGreen[Blue-Green]
direction LR
LB1[流量] --> Blue[Blue 舊版]
LB1 -.切換.-> Green[Green 新版]
end
subgraph Canary[Canary]
direction LR
LB2[流量] --> Old[舊版 95%]
LB2 --> New[新版 5%]
end
subgraph Rolling[Rolling]
direction LR
I1[實例1 新] --- I2[實例2 新] --- I3[實例3 舊]
end| 策略 | 做法 | 回滾速度 | 資源成本 | 風險暴露 |
|---|---|---|---|---|
| Blue-Green | 兩套環境並存,瞬間切換流量 | 極快 | 高(雙倍環境) | 全量切換 |
| Canary | 先導入少量流量,觀察後逐步放大 | 快 | 中 | 漸進、可早期止損 |
| Rolling | 逐批替換實例,新舊並存一段時間 | 中 | 低 | 漸進 |
Feature Flags#
Feature Flag 讓「部署程式碼」與「啟用功能」解耦:新功能可以先部署到正式環境,但以開關隱藏,按需對特定使用者開啟。
flowchart LR
A[功能程式碼已部署] --> B{Feature Flag 開?}
B -->|關| C[使用者看不到]
B -->|開, 限部分用戶| D[灰度驗證]
D --> E[全量開啟]發布(release)≠ 部署(deploy):
- 部署 是把程式碼放到正式環境的技術動作。
- 發布 是讓使用者實際看到新功能的商業決策。
透過 Feature Flag,團隊可以頻繁、低風險地部署,而把「何時對誰發布」變成獨立、可控、可隨時回收的決策。
Feature Flag 是 Trunk-Based Development 的關鍵支柱:未完成的功能用開關隱藏,便能安全地頻繁整合進主幹,而不必依賴長命分支。記得定期清理過期的 flag,否則會累積成新的技術債。