Docker Compose 到 K8s 遷移必踩的兩個「大坑」:啟動指令與網路暴露
Docker Compose 到 K8s 遷移必踩的兩個「大坑」:啟動指令與網路暴露
將服務從 Docker Compose 遷移到 Kubernetes 時,我們經常會想著「直接把參數翻譯過去就好」。但在實際遷移 AI Agent (如 Hermes) 的過程中,我們踩到了兩個非常經典的架構差異陷阱。
這份筆記記錄了這兩個核心差異,幫助未來的我們(或新手)避開這些令人抓狂的 CrashLoopBackOff 和 Connection refused。
坑一:啟動指令被無情覆蓋 (Entrypoint vs Command)
🚨 症狀
Pod 陷入 CrashLoopBackOff,日誌顯示類似錯誤:
executable file not found in $PATH
🔍 原因分析
這源於 Docker 和 K8s 針對映像檔啟動參數的命名定義不一致,甚至是完全衝突的。
在許多 Docker 映像檔中,開發者會寫一個預設的 Entrypoint 腳本(用來準備環境),然後再把我們傳入的指令(Command)附加在後面執行。
- 在 Docker Compose 裡: 我們寫的
command: gateway run,會乖乖地被附加在Entrypoint腳本之後。 - 在 Kubernetes 裡: 如果我們在 YAML 的 container 區塊寫了
command: ["gateway", "run"],K8s 的command其實等同於 Docker 的entrypoint。 這意味著,K8s 會把映像檔裡原本那個重要的啟動腳本整組刪掉,直接去找一個叫gateway的執行檔,自然就會引發找不到檔案的錯誤。
✅ 解決方案
在 Kubernetes 的 YAML 中:
- 不要用
command來翻譯 Docker 的command。 - 要用
args。
錯誤的 K8s 寫法 (會覆蓋腳本):
1
2
3
4
containers:
- name: my-agent
image: my-agent:latest
command: ["gateway", "run"] # ❌ 這是錯的!
正確的 K8s 寫法 (正確附加參數):
1
2
3
4
containers:
- name: my-agent
image: my-agent:latest
args: ["gateway", "run"] # ✅ 這樣才會保留原有的 Entrypoint 腳本
坑二:明明開了 Port 卻連不上 (ClusterIP vs LoadBalancer)
🚨 症狀
K8s 裡的 Pod 和 Service 都顯示正常運作,但在 Mac 本機終端機執行 ssh -p 2222 hermes@localhost 時卻得到:
Connection refused
🔍 原因分析
這是 Docker 網路模型與 K8s 網路模型的根本差異。
- 在 Docker Compose 裡: 寫
ports: - "2222:22"時,Docker 會自動在您的 Mac 主機 (localhost) 上打洞,把外面的 2222 埠直接牽線到容器的 22 埠。 - 在 Kubernetes 裡: 當我們建立一個 Service 且沒有特別指定
type時,它預設是ClusterIP。ClusterIP是在 K8s 的「虛擬內網」中流通的。這意味著:只有叢集裡的其他 Pod(例如 Cloudflare 網關)能看到它。您站在叢集外面的 Mac (localhost) 去敲門,K8s 根本不會理您。
✅ 解決方案 (特別針對 OrbStack 環境)
要讓 Mac 本機能直接存取 K8s 裡面的服務,我們必須明確改變 Service 的對外暴露類型。
在 OrbStack 這類優化過的本地 K8s 環境中,最佳解是將 Service 類型設為 LoadBalancer。OrbStack 偵測到這個類型後,就會自動幫您在 Mac 的 localhost 綁定對應的通訊埠。
修正後的 Service 定義:
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: hermes-agent
namespace: hermes
spec:
type: LoadBalancer # 👈 關鍵:加上這行,OrbStack 才會在 localhost 開門
selector:
app: hermes-agent
ports:
- port: 2222
targetPort: 22
💡 總結心法
- 翻譯指令時:Docker 的
command= K8s 的args。 - 需要本機直接連線時:K8s 的 Service 不能只用預設,必須加上
type: LoadBalancer(或NodePort)。
This post is licensed under CC BY 4.0 by the author.