10 年舊系統如何安全導入 AI 開發:Strangler Fig 遷移方法論

重點摘要

  • 10 年的舊系統能跑就是最有價值的資產,不要試圖先修好再遷移
  • 核心方法論:Strangler Fig 模式 — 新軌道在旁邊長起來,舊系統自然退場
  • AI 第一件事不是寫 code,而是讀懂 10 年的系統邏輯,再動手
  • 四個階段:快照現況 → 建平行新軌 → 一次搬一個服務 → 封存舊系統

你的系統跑了 10 年。它很髒、沒有文件、CI/CD 靠手動、密碼可能在 .env 裡或者在某個工程師的腦袋裡。但它能跑,而且在服務真實的用戶。

現在你想引入 AI 輔助開發,想現代化整個工作流。問題來了:要從哪裡開始? 要先把舊的修乾淨,還是直接用新方法?

錯誤的答案是:「先把舊的修好。」正確的答案是:不要動正在跑的東西,在旁邊建一條新軌道。

為什麼「先修好再用新方法」行不通?

這個直覺很自然,但在實際工程上幾乎都會失敗,原因有三:

  1. 無法停止開發等你修 — 業務不會暫停,新需求還是會進來,你邊修邊開發,舊問題永遠追不完
  2. 「修好」的定義會不斷移動 — 一開始說只要加 .gitignore,結果發現歷史有密碼,要 filter-repo,然後發現測試覆蓋率是零…沒有終點
  3. 你在修一個不完全理解的系統 — 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

階段四:一次搬一個服務

這是遷移的主體。原則是:每次只搬一個服務,驗證通過才搬下一個

服務搬移的優先順序建議:

優先順序 選擇原則 理由
第一批 流量最小的非核心服務 風險最低,可以放心試錯
第二批 獨立性高、依賴少的服務 不會牽一髮動全身
最後 核心業務邏輯(訂單、付款) 等前幾批證明新流程可靠後再動

每個服務的搬移步驟:

  1. AI 在新 repo 重寫該服務的乾淨版本(理解舊 code 後重寫,不是 copy paste)
  2. Jenkins 構建新鏡像,部署到 Staging
  3. 用影子流量驗證新舊行為一致(新舊同時收請求,比對回應)
  4. 確認無誤,切換這個服務的流量到新系統
  5. 觀察 24-48 小時
  6. 舊服務下線

影子流量驗證(用 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 有密碼怎麼辦」

分兩步處理:

  1. 立即輪換所有洩露的密碼 — 因為 GitHub/GitLab 可能已有快取,這一步不能等
  2. 舊 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 完整設計

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *