K8s Debug Bridge
將 Kubernetes/OpenShift 服務的即時流量隧道傳送至本地機器進行除錯,僅使用標準的 kubectl/oc exec、Bash 內建的 /dev/tcp 以及橋接 Pod 內的 nc — 無需安裝額外的 CLI 或 Agent。機制如下:啟動一個臨時的「橋接 Pod」,暫時將目標服務的 spec.selector 切換為此 Pod 的標籤,並透過反向隧道橋接 Pod 與本地連接埠。適用於任何 Kubernetes 發行版(Vanilla K8s、OpenShift、EKS/GKE/AKS)— 若可用則使用 oc,否則使用 kubectl(請參閱 scripts/lib-kube-cli.sh)。README.md 涵蓋了設計原理以及與 Telepresence、mirrord 等工具的取捨權衡。
本技能不限定於特定語言或框架:橋接僅負責傳輸流量。VS Code + Air + Delve (Go) 僅作為一個範例整合 — 如果您使用其他語言,請透過相同隧道連接您自己的監控器/除錯器(Node:nodemon+inspector,Python:watchdog+debugpy 等)。
如果使用者尚未設定本地端 — 即沒有重新載入器、未附加除錯器,或希望將此功能調整到他們自己的專案中 — 請在該步驟讀取 docs/local-debug-setup.md 並遵循其指示。請勿憑記憶重現其內容,也請勿在本地應用程式已執行並處於監聽狀態時讀取該文件。
本技能會在您的叢集中建立真實資源並修改真實資源:它會建立一個 Pod,並暫時變更服務的 spec.selector。這不是模擬 — 這是真實的叢集狀態。每個步驟都必須有復原計畫 — 切勿變更選擇器後忘記處理。
先決條件 — 在詢問使用者前驗證
請勿直接向使用者詢問命名空間/服務名稱,然後盲目地將其傳遞給 bridge-up.sh — 拼寫錯誤的名稱會導致腳本執行到一半失敗。請先執行 scripts/check-env.sh [namespace](唯讀,不會變更任何內容):
- 不帶參數執行:顯示登入狀態及可存取的命名空間。如果尚未登入,請在此停止,並告知使用者執行
oc login/kubectl config use-context。 - 帶命名空間執行:驗證該命名空間是否存在,並列出其中的服務(名稱 + 連接埠 + targetPort,包括已橋接的服務)。
將此輸出顯示給使用者,讓他們從清單中選取目標服務 — 不要讓他們猜測。接著:
- 橋接 Pod 映像檔 — 務必詢問,切勿假設。 請勿預設一個映像檔並直接執行:直接向使用者詢問 「橋接 Pod 應該使用哪個映像檔?」 並等待回答。該映像檔必須經過該特定叢集的核可且可從該叢集存取,只有他們才知道該用哪一個 — 許多叢集僅允許從核可的內部註冊庫拉取映像檔,因此猜測的映像檔可能在服務已被重新導向時,因拉取失敗而出錯。
nicolaka/netshoot是一個可以提供的範例,但不要默默將其作為預設值使用。無論使用者選擇什麼,都必須內含nc:Pod 端使用nc而非 socat,因為強化過的映像檔通常會移除socat,但會保留 Nmap 的nc。tunnel.sh會在開啟隧道前檢查nc是否存在,如果映像檔中沒有nc,它會停止並顯示明確的錯誤訊息。 - Pod 清單:橋接 Pod 是從
assets/bridge-pod.yaml渲染而來的。如果叢集需要 imagePullSecrets、securityContext、nodeSelector/tolerations 或不同的資源限制,請編輯該檔案 — 不要繞過腳本處理。請保留debug: "__BRIDGE_NAME__"標籤和debug-bridge容器名稱;其他腳本會透過它們來定址該 Pod。 - 連接埠:使用
check-env.sh輸出中服務的數值targetPort,而非任意空閒連接埠。如果服務使用具名的 targetPort(例如http),請將其解析為對應的容器數值連接埠。一旦選擇器被重新導向,kube-proxy 會將流量轉發到 Pod 的targetPort— 如果您選擇不同的數字,流量將永遠無法到達橋接 Pod。 - 對外依賴:此橋接僅處理入站流量(叢集 → 本地)。如果本地程式碼必須呼叫其他叢集服務(
.svc.cluster.local),請使用外部 Route/Ingress 或本地的 port-forward。 - 密碼與環境:切勿對應用程式 Pod 執行
oc exec ... env、printenv、set或類似命令。這些值可能是多行憑證,即使命令看似只選取了名稱,它們仍會洩漏到終端機/工具輸出中。切勿將 Pod 的環境變數值複製到本地命令、Shell 歷史記錄或.env檔案中。請要求使用預先存在的本地密碼管理工作流程,或讓使用者透過外部方式提供所需的本地環境設定檔。僅需驗證本地應用程式可以啟動;請勿列印其環境變數。
在操作服務之前,本地應用程式必須已啟動。 這是一個先決條件,而不是後續步驟:隧道會將流量傳送到 127.0.0.1:<port>,因此如果在選擇器切換時該處沒有應用程式在監聽,則每個被攔截的請求都會失敗並出現連線拒絕/502 錯誤,同時真實的 Pod 已經退出服務輪詢。請讓使用者啟動他們的應用程式(使用安全佈建的本地環境設定檔 — 絕不能從叢集 Pod 中讀取),並在步驟 2 之前立即確認監聽器:
lsof -nP -iTCP:<port> -sTCP:LISTEN
流程
-
設定橋接:在執行任何操作之前,清楚告知使用者:「我將在
<namespace>命名空間中建立一個名為<bridge-pod>的真實 Pod(映像檔:<image>)— 它會消耗叢集資源,並會在您完成操作後被刪除。」 獲得批准後,執行scripts/bridge-up.sh <namespace> <service> <bridge-pod> <image> [port]— 此腳本會建立橋接 Pod,並將原始選擇器同時儲存到本地狀態以及作為k8s-debug-bridge/owner註釋附加到服務上(無需 python3/jq,純 Bash 即可)。請勿跳過此步驟 — 如果未先儲存原始選擇器,絕不能重新導向,否則將無法復原。如果該服務已被其他人橋接,此腳本會失敗並停止(透過註釋檢查)— 在這種情況下,請向使用者顯示錯誤訊息中的擁有者,不要自行重試。 -
重新導向服務:在將目標服務的選擇器切換為橋接 Pod 的標籤之前,清楚告知使用者並獲得明確核准:「從此刻開始,所有流向
<service>的流量都將導向您的橋接 Pod — 真實的 Pod 將停止接收流量。」 獲得核准後,執行scripts/redirect-service.sh <namespace> <service> --yes—--yes會跳過腳本本身的互動式read提示(因為您已在對話中獲得核准);不加--yes的話,腳本會等待標準輸入,可能導致卡住。 該腳本會將完整的選擇器映射表取代為僅包含debug=<bridge-pod>;它不得將此標籤合併到原始選擇器中,因為橋接 Pod 不帶有應用程式的原始標籤。 -
啟動隧道:
scripts/tunnel.sh <namespace> <service> <bridge-pod> <port>— 此腳本會阻塞,因此應在其自己的終端機或作為背景程序執行。它會自動清理:trap會擷取 EXIT/INT/TERM 訊號,因此 Ctrl+C 或任何其他終止動作都會自動呼叫cleanup.sh。如果您自己在背景啟動它,就沒有 Ctrl+C 可以按 — 使用kill -TERM <pid>來停止它,trap 也會處理此訊號;請在啟動時記下 PID 並告知使用者。僅在程序被無法捕捉的方式終止時(kill -9),才需要手動執行cleanup.sh。 它一次只處理一個連線,並會在每個連線關閉後自動建立新的 Pod 端監聽器,因此在連續的除錯請求之間保持相同的隧道運作。 如果隧道重複記錄WARNING: could not carry a connection,表示127.0.0.1:<port>上沒有任何東西在監聽 — 本地應用程式已關閉、仍在建置中,或是熱重新載入釋放了該連接埠。請先修復此問題,而不是重新啟動隧道。 -
本地除錯:您橋接的連接埠是應用程式提供服務的連接埠(服務的
targetPort),絕非其除錯器監聽的連接埠。如果使用者說「我的除錯器在 2345 連接埠,請橋接那個」,表示他們混淆了這兩者:請糾正他們,詢問應用程式本身在哪個連接埠提供服務,並橋接該連接埠。傳送到除錯器連接埠的流量會以 502 錯誤回應,看起來像是隧道故障。 本地應用程式已啟動並通過驗證(請參閱上面的先決條件);它是接收隧道流量的主體。他們語言的除錯器(例如 Delve、Node inspector)會分別附加到該執行中的應用程式,通常在不同連接埠上 — 例如在 Go 中,Air + Delve 在連接埠 2345,而應用程式本身在隧道化的<port>上監聽。 熱重新載入可能會短暫移除監聽器;在其缺席期間,橋接會回傳連線失敗/502 錯誤。如果本地應用程式未能及時恢復,請停止隧道並清理服務,而不是將所有流量都指向一個有問題的橋接。橋接會原封不動地傳輸 HTTP 路徑:請將本地應用程式的基礎前綴設定為與傳遞給服務的路徑相符;它不會重現 Ingress 的改寫。 流量可能來自叢集內服務的任何呼叫者(例如,儀表板的 Nginx 代理);不需要公開的 Route/Ingress。如果執行暫停時間超過呼叫者的逾時時間,請恢復執行並觸發新的請求。在熱重新載入重新啟動 Delve 後,IDE 可能需要再次附加。 當 Delve 使用--accept-multiclient啟動時,Agent 也可能會附加一個終端機 Delve 用戶端到連接埠2345,與 VS Code 並行,以便檢查當前的原始碼行、堆疊和變數。請向使用者報告觀察結果;在繼續執行或以其他方式變更除錯器狀態之前,務必取得明確核准。 -
完成後:在隧道終端機中按 Ctrl+C,或者如果您在背景啟動了它,則使用
kill -TERM <pid>— 服務選擇器和橋接 Pod 會自動還原,包括恢復完整的原始選擇器映射表。如果 trap 沒有觸發(例如kill -9),則必須手動執行scripts/cleanup.sh <namespace> <service>;請在使用者離開對話前提醒他們。
安全注意事項
此方法不會在叢集中安裝任何永久元件(無 mutating webhook、sidecar 注入器或叢集範圍的 RBAC)— 僅有一個由其名稱/標籤(debug=<bridge-pod-name>)清楚標記的臨時 Pod,以及標準的 exec 權限。即便如此,仍有兩個實際風險,請警告使用者:
- 忘記還原服務選擇器 — 導致正式環境/預備環境的永久中斷。
exec權限可能透過 RBAC 被濫用 — 此技能應僅在自己的命名空間中執行,僅作用於自己的橋接 Pod,且絕不應需要叢集管理員權限。
如需詳細步驟和故障排除,請參閱 README.md。
在結束對話之前,請檢查是否有未清理的橋接。狀態檔案存放在本技能資料夾內,位於 scripts/ 旁邊 — 而不是使用者的專案目錄中。請相對於您正在執行的腳本來解析它,例如:
find "$(dirname <path-to>/scripts)/.state" -name '*.env'
從使用者的專案中執行裸的 find .state 會默默地找不到任何東西,並且即使橋接仍在運作,也會報告乾淨的狀態。如果該處存在 .env 檔案,請告知使用者它所指定的命名空間/服務,並建議執行 scripts/cleanup.sh <namespace> <service>。這只是一個提醒,不是一個決定性的關卡 — 請小心不要忘記。