cover

事故發生時最怕三件事:不知道現在跑的是哪個版本、不知道是誰什麼時候改的、不知道回滾要回到哪裡。Git、CI、Release、Rollback 這四件事串在一起,就是為了讓你永遠不會遇到這三種恐慌。

先講結論

main 永遠可部署、版本號是合約(不是裝飾)、Release 必須可重現、Rollback 必須在事故之前就準備好。四條規則,缺一不可。

分支策略:不要過度設計

小團隊用 trunk-based:main + feature branch + tag 觸發 release。大團隊用簡化版 Git Flow:main(穩定)+ develop(整合)+ feature/*。

不管哪種,核心原則就一個:main 永遠可部署。任何未完成的功能都不能直接進 main。如果你的 main 時不時會壞,那不是分支策略的問題,是你沒有 CI gate。

版本號:SemVer 是給人看的合約

v1.6.0 不是流水號,它告訴所有人:major 是破壞性變更、minor 是新功能、patch 是修 bug。

tag 打錯或忘了打?後果就是你不知道 prod 跑的到底是哪個版本。所以 pipeline 要設規則:只有 tag 才允許 deploy

git checkout main
git pull --rebase
git merge --no-ff develop
git tag -a v1.6.0 -m "Release v1.6.0"
git push origin main
git push origin v1.6.0

Release Notes:寫給「半夜被叫起來的人」看的

好的 Release Notes 要回答:這版改了什麼、有沒有破壞性變更、出事要怎麼回退。

## v1.6.0
 
### New Features
- [EC-245] Checkout 支援折扣碼
 
### Bug Fixes
- [EC-239] 修正訂單金額四捨五入錯誤
 
### Breaking Changes
- API /v1/orders 回傳結構調整
 
### Rollback Plan
- Image 回滾至 v1.5.2
- Migration 為 additive,可安全回退

看到了嗎?最後一定有 Rollback Plan。沒有 rollback plan 的 release,就像沒有降落傘的跳傘——你很有信心,但出事的時候你會後悔。

CI Gate:沒過就不能部署

CI 是 Release 的強制閘門。Lint、測試、安全掃描、build——任何一個失敗都不能部署。

stages:
  - lint
  - test
  - build
  - deploy
 
lint:
  stage: lint
  script:
    - npm ci
    - npm run lint
 
build:
  stage: build
  script:
    - docker build -t registry.example.com/app/api:$CI_COMMIT_TAG .
    - docker push registry.example.com/app/api:$CI_COMMIT_TAG
  rules:
    - if: $CI_COMMIT_TAG

Rollback:兩條路線

路線 A:Image 回退(最快)——直接把 image tag 換回上一版,不重 build。適合程式 bug。

路線 B:版本回退(完整)——checkout 舊 tag、重新 build、重新 deploy。適合需要同步 config 或 migration 的情況。

# 找上一個穩定 tag
git tag --sort=-creatordate | head -n 5
 
# 改 docker-compose 的 image tag 回 v1.5.2
# image: registry.example.com/app/api:v1.5.2
docker compose up -d
 
# 驗證
curl -sf https://api.example.com/health

最容易踩的坑

Hotfix 沒有回合 develop:修了 prod 的 bug,但忘了 merge 回 develop。下次 release 又把 bug 帶回來。Hotfix PR 的 checklist 必須包含 merge back。

DB migration 不可回退:drop column、rename table 放在 release 裡,rollback 的時候 DB 回不去。原則是 migration 只做 additive,破壞性操作留到下一個 major。

Tag 被覆蓋:有人 force push 了一個已存在的 tag,prod 的版本跟 registry 裡的不一致。解法:禁止覆蓋 tag,prod 只允許 v* tag。


Release 不是「把 code 推上去」。Release 是「我有信心推上去,而且出事我知道怎麼退回來」。