在容器裡遙控 Chrome 的完整路由
把 Chrome 從 API Pod 拆出來做 KEDA 自動擴展,結果 DevTools、DNS 路由、容量規劃全部炸開。這是那 12 個教訓的完整紀錄。
「理解一個系統最快的方式,就是看它怎麼壞掉。」
截圖服務的流量一直在漲。原本的架構是一個 Pod 裡塞 API server 加 sidecar Chromium。單 Pod,不能水平擴展。流量一爆,那一支 Chrome 就成了瓶頸。
我們動手拆:API 和 Chrome 分開成獨立的 Pod,Chrome 交給 KEDA 自動擴展。聽起來直覺。但拆開的那一刻,Chrome 升級到 version 128,直接忽略 --remote-debugging-address flag。DevTools 鎖死在 localhost,跨 Pod 就是打不通。
從這個斷點開始,一路拆解到網路、協定、容器啟動、負載均衡、安全性,最後做了壓力測試才收工。
這篇是整趟路由的導覽。每一段點出問題和解法,再連到獨立的深入篇做展開。
Pod 就是一支電話
Kubernetes 裡的 Pod 像一支電話。同一支電話裡的 App 用群組聊天(127.0.0.1)溝通,不同電話之間只能打分機號碼(Pod IP)。Chrome version 128 之後只聽群組聊天,外面的 Pod 打不進來。
Chrome 怎麼被遙控
Chrome DevTools Protocol(CDP)需要兩步:先 HTTP 拿 GUID,再 WebSocket 連上去。兩步必須打到同一支 Chrome。這個限制在 Pod 數量增加時會咬你一口。
深入篇:CDP 兩階段連線。
Chrome Version 128:Flag 失效
Chrome version 128 直接忽略 --remote-debugging-address=0.0.0.0,DevTools 鎖死在 127.0.0.1。同一個 Pod 裡沒問題,跨 Pod 就斷線。
第一個修復:socat 接線員
socat 在 Pod 裡當接線員。前台接外線(0.0.0.0:9222),轉給 Chrome 的內線(127.0.0.1:9223)。Chrome 以為 socat 是隔壁同事,因為 socat 就在同一個 Pod 裡。
深入篇:socat 怎麼橋接。
容器啟動的早班流程
entrypoint 腳本是開店檢查清單:啟動 socat,確認它活著,用 exec 把 PID 1 交給 Chrome。Port 設定有五層傳遞鏈,Helm 的 default() 會悄悄吃掉缺失的設定。
GUID Race:擴展時的新問題
KEDA 加到三個 Pod,kube-proxy 每次連線隨機選 Pod。CDP 的兩步被拆到不同 Pod,GUID 對不上。三個 Pod 成功率 33%,六個 Pod 只剩 17%。
深入篇:機率算給你看。
Headless Service:讓 DNS 給你電話簿
Regular Service 像總機隨機轉接。Headless Service 給你所有分機號碼,你自己選一個,兩步都打給它。GUID 一定對。
安全:鎖住沒密碼的 CDP
socat 對全 cluster 敞開,CDP 沒有認證。一條 NetworkPolicy 只放行 render API 連到 Chrome 的 9222 port。
深入篇:一條規則鎖住前門。
能撐多少流量?
max concurrent = per-pod ceiling × Ready pod count。三個 Pod 撐 150 沒問題,硬推 200 就炸。「Ready」不等於「能用」:GPU node 顯示 Ready 後,driver 還要裝好幾分鐘。
為什麼每次都要重新連線
Go 的 HTTP client 預設會重用連線。KEDA 縮容後,重用的連線會指向已消失的 Pod。DisableKeepAlives: true 強制每次 request 都重新查 DNS。三道防線缺一不可:Headless Service、atomic round-robin、fresh TCP。
深入篇:為什麼 keep-alive 會害死擴展。
「每一層抽象都在替你隱藏複雜度。直到它們開始替你隱藏 bug。」
參考來源: