重點摘要
- 10 年的舊系統能跑就是最有價值的資產,不要試圖先修好再遷移
- 核心方法論:Strangler Fig 模式 — 新軌道在旁邊長起來,舊系統自然退場
- AI 第一件事不是寫 code,而是讀懂 10 年的系統邏輯,再動手
- 四個階段:快照現況 → 建平行新軌 → 一次搬一個服務 → 封存舊系統
你的系統跑了 10 年。它很髒、沒有文件、CI/CD 靠手動、密碼可能在 .env 裡或者在某個工程師的腦袋裡。但它能跑,而且在服務真實的用戶。
現在你想引入 AI 輔助開發,想現代化整個工作流。問題來了:要從哪裡開始? 要先把舊的修乾淨,還是直接用新方法?
錯誤的答案是:「先把舊的修好。」正確的答案是:不要動正在跑的東西,在旁邊建一條新軌道。
為什麼「先修好再用新方法」行不通?
這個直覺很自然,但在實際工程上幾乎都會失敗,原因有三:
- 無法停止開發等你修 — 業務不會暫停,新需求還是會進來,你邊修邊開發,舊問題永遠追不完
- 「修好」的定義會不斷移動 — 一開始說只要加 .gitignore,結果發現歷史有密碼,要 filter-repo,然後發現測試覆蓋率是零…沒有終點
- 你在修一個不完全理解的系統 — 10 年的系統有太多隱性知識,修的過程中很容易把「能跑的」改成「不能跑的」
工程界有一個著名的模式專門解決這個問題,叫做 Strangler Fig(絞殺榕)模式。
Strangler Fig 模式:不砍舊樹,讓新藤蔓長過去
絞殺榕是一種熱帶植物。它的種子落在老樹上,慢慢向下長出根,包住舊樹,最後舊樹自然退場,絞殺榕站立在原位。整個過程中,舊樹從未停止「提供支撐」,直到新系統完全就緒。
應用到 DevOps 遷移:
❌ 錯誤思維:
舊系統(停機)→ 修好 → 接新流程 → 恢復服務
✅ 正確思維(Strangler Fig):
舊系統(持續運行,不動)
↓
新軌道在旁邊建立(不影響舊系統)
↓
一次搬一個服務,驗證後切換流量
↓
所有服務搬完,舊系統自然退場
關鍵洞察:能跑的系統是你最有價值的資產,不是問題的來源。遷移的目標是「讓它繼續跑,同時讓新系統在旁邊成長」,不是「讓它停下來修好再說」。
四個階段的完整遷移方法論
階段一:快照現況(不動任何東西)
第一步不是改 code,不是設定 CI/CD,而是把「現在是怎麼跑起來的」完整記錄下來。這份快照是整個遷移過程的地基。
為什麼要快照?因為在 10 年的系統裡,repo 裡的 .env 可能是舊的,文件可能是錯的,只有正在跑的進程才是真相:
# 從正在跑的容器抽出真實的環境變數
docker inspect <container_name> \
--format='{{range .Config.Env}}{{println .}}{{end}}' \
> /tmp/real-env-snapshot.txt
# 或直接讀進程的環境變數
cat /proc/$(pgrep java)/environ | tr '\0' '\n' | grep -E "DB_|API_|SECRET_"
# K8s 環境
kubectl get pods -n production -o name | while read pod; do
echo "=== $pod ==="
kubectl exec $pod -n production -- env 2>/dev/null
done > /tmp/real-k8s-env-snapshot.txt
同時盤點服務清單和依賴關係:
# 有哪些服務在跑
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
# 服務之間怎麼通訊
docker network inspect bridge
# 對外開放哪些 port
ss -tlnp | grep LISTEN
這個階段的產出是一份真實架構圖和一份真實密碼清單(妥善保管,不進任何 repo)。
階段二:AI 讀懂你的系統
這是大多數人忽略的步驟,也是決定 AI 協作能否成功的關鍵。
AI 第一件事不是寫 code。
在 AI 動手之前,它需要讀你 10 年的系統。這個過程大概需要幾天,但會產出你可能從未有過的東西:
| AI 讀完後的產出 | 為什麼重要 |
|---|---|
| 系統架構圖 | 你們可能自己也沒有,新人上手和遷移規劃的基礎 |
| 模組依賴關係 | 知道改哪個地方會影響哪些服務 |
| 高風險區域標記 | 「這段 code 10 個人改過,有 3 個已知 bug 的修復」 |
| 技術債清單(按影響排序) | 知道先解決什麼,不是看到髒的就改 |
| 新人上手文件 | 從 code 反推出來,不需要老工程師口傳 |
這個階段同樣不動任何 code。AI 只是閱讀和理解。等到真正開始寫新功能時,AI 已經知道你的系統慣例是什麼、有哪些地雷不能踩。
階段三:建立平行的新軌道
新建一個乾淨的 repo,在旁邊建立完整的現代化工作流,舊 repo 繼續照舊運作:
舊 GitLab repo(繼續跑,不動)
│
│ 正在服務用戶的系統
│
新 repo(乾淨起點)
│
├─ 正確的 .gitignore(.env 全部排除)
├─ CI/CD pipeline(gitleaks + build + sign)
├─ K8s Secrets(從快照搬進來)
└─ Branch Protection Rules
把真實密碼從快照搬到 K8s Secrets(不是從舊 repo 搬,是從正在跑的進程抽出來):
# 從快照建立 K8s Secrets
kubectl create secret generic app-prod-creds \
--from-literal=DB_PASSWORD="$(grep DB_PASSWORD /tmp/real-env-snapshot.txt | cut -d= -f2)" \
--from-literal=SHOPEE_KEY="$(grep SHOPEE_KEY /tmp/real-env-snapshot.txt | cut -d= -f2)" \
-n production
# 建立完成後,安全刪除快照
shred -u /tmp/real-env-snapshot.txt
階段四:一次搬一個服務
這是遷移的主體。原則是:每次只搬一個服務,驗證通過才搬下一個。
服務搬移的優先順序建議:
| 優先順序 | 選擇原則 | 理由 |
|---|---|---|
| 第一批 | 流量最小的非核心服務 | 風險最低,可以放心試錯 |
| 第二批 | 獨立性高、依賴少的服務 | 不會牽一髮動全身 |
| 最後 | 核心業務邏輯(訂單、付款) | 等前幾批證明新流程可靠後再動 |
每個服務的搬移步驟:
- AI 在新 repo 重寫該服務的乾淨版本(理解舊 code 後重寫,不是 copy paste)
- Jenkins 構建新鏡像,部署到 Staging
- 用影子流量驗證新舊行為一致(新舊同時收請求,比對回應)
- 確認無誤,切換這個服務的流量到新系統
- 觀察 24-48 小時
- 舊服務下線
影子流量驗證(用 nginx 實現,不影響用戶):
# nginx 配置:新舊同時收請求,比對行為
location /api/orders {
mirror /mirror-new; # 複製請求到新系統
proxy_pass http://old-api; # 用戶實際收到舊系統的回應
}
location = /mirror-new {
internal;
proxy_pass http://new-api; # 新系統的回應只寫 log,不回傳用戶
}
AI 在整個過程中的實際角色
理解了遷移方法論後,AI 在這個過程中的職責非常清楚:
AI 能做(且做得很好):
✅ 讀 10 年的舊 code,產出架構文件
✅ 識別技術債,按優先順序排列
✅ 用「符合你們系統慣例」的方式寫新功能
✅ 在 feature branch 開發,開 MR,等你 review
✅ 補測試(補現有 code 的測試覆蓋率)
✅ Debug(分析 log,找根因)
✅ 在新服務和舊系統的接縫處寫 adapter
AI 不能做(也不應該做):
❌ 直接 push 到 main 或 production branch
❌ 決定什麼時候切換流量
❌ 接觸任何密碼或 production secrets
❌ 在沒有充分理解舊系統的情況下動手
一個典型的 AI 協作對話長這樣:
你:「我要加一個批次出貨的功能」
AI:「讓我先讀一下現有的出貨相關 code...
我看到 OrderService.java 的 shipOrder()
和 ShipmentRepository,還有一個舊的
BatchJob 在 legacy/ 目錄。
注意:legacy/BatchJob 有個已知的
transaction 問題(commit a3f2c1b 的修復紀錄),
新功能要避開。
建議的實作方向是...你確認嗎?」
你:「方向對,去做吧」
AI:「好,我開 feature/batch-shipment,
預計改動:
- 新增 BatchShipmentService(核心邏輯)
- 修改 OrderController(加 API endpoint)
- 不動 legacy/ 目錄
完成後開 MR 給你 review」
AI 因為讀過 10 年的系統,不會做出「不符合你們慣例」的 code,也不會踩進已知的地雷。
最常遇到的三個現實問題
問題一:「不知道哪個 .env 是現在真正在用的」
10 年的系統通常有多個 .env 版本,有的是舊的,有的是工程師自己改過的。以正在跑的進程為準,不要相信文件:
# 找到 Java 進程真正使用的環境變數
cat /proc/$(pgrep java)/environ | tr '\0' '\n' | grep -E "DB_|API_|SECRET_"
# 不要用
cat .env # 這可能是 6 個月前的版本
問題二:「團隊還在開發,不能凍結」
不需要凍結。舊 repo 繼續用,新 repo 並行開發。重要的 bugfix 透過 cherry-pick 同步:
舊 repo: feature → dev → main(繼續照舊)
│
└─ cherry-pick 重要修復
│
新 repo: └─ feature → dev → main(新流程)
問題三:「歷史 commit 有密碼怎麼辦」
分兩步處理:
- 立即輪換所有洩露的密碼 — 因為 GitHub/GitLab 可能已有快取,這一步不能等
- 舊 repo 等到遷移完成後再 archive — 不需要現在重寫歷史,新 repo 一開始就是乾淨的
注意:很多人以為 git rm --cached .env 就安全了,但舊 commit 裡的內容仍然可以被 git show <old-commit>:.env 讀出。唯一的技術修復是 git filter-repo,但遷移方法論讓你可以跳過這一步——因為所有新開發都在新的乾淨 repo 上進行。
時間軸規劃
| 時間 | 工作 | 風險 |
|---|---|---|
| 第 1 週 | 快照現況、輪換外部 API Key、AI 開始讀系統 | 零(不動任何 code) |
| 第 2-4 週 | 建新 repo、CI/CD pipeline、K8s Secrets、第一個服務搬過去 | 低(影子流量驗證) |
| 第 1-3 個月 | 逐服務遷移,每個驗證 24-48 小時後才繼續 | 中(每次只影響一個服務) |
| 遷移完成後 | 舊 repo archive、完整安全強化(RBAC、Audit Log、鏡像簽名) | 低(新系統已穩定) |
決策樹:你現在該從哪裡開始
你的系統現在能跑嗎?
│
├─ 能跑 → 用 Strangler Fig 模式(本文的方法)
│ │
│ ├─ 步驟 1:快照現況(本週就做)
│ ├─ 步驟 2:AI 讀系統(同步進行)
│ ├─ 步驟 3:建新軌道(第 2-4 週)
│ └─ 步驟 4:逐服務遷移(之後)
│
└─ 不能跑 → 先讓它跑起來,再回到這裡
這套方法論的本質
Strangler Fig 模式應用在 AI 輔助開發遷移上,核心洞察只有一個:
「10 年的技術債是過去的決策的結果,你無法在不破壞現有價值的情況下一次消除它。但你可以選擇:從今天開始,所有新的工作都用正確的方式做。」
舊系統是你團隊的集體記憶,AI 有能力閱讀並理解這些記憶,然後用現代化的方式繼續往前走。不需要推倒重建,也不需要凍結開發去修舊債——只需要一條平行的新軌道,和耐心地一次移動一個服務。
想了解新軌道的 CI/CD 具體設計,可以參考上一篇文章:AI 輔助開發 CI/CD 工作流:Jenkins、K8s、ISO 27001 完整設計。
發佈留言