一句話結論:單機或小叢集只想要一個「看得到所有 workload」的介面,Headlamp 夠用而且很輕。Rancher 和 Portainer 都不是不好,是對這個場景太重,或剛好踩到記憶體的坑。
當初怎麼做
單機的 Kubernetes 叢集,總是想要一個網頁介面:不用每次都敲 kubectl get pods -A,看 log、看資源用量也比較直覺。
第一個選的是 Rancher。理由很單純:名氣大、功能完整,感覺「正式的叢集就該有」。
哪裡不行
Rancher:用不到的功能太多,壞了又很難修
Rancher 預設會帶一整組週邊元件:多叢集管理、Fleet、Turtles、內嵌的 Cluster API controller。
某一天 Rancher 就卡在 CrashLoopBackOff,連續好幾天。原因是兩套內嵌的 CAPI controller 的 CRD,storedVersions 版本衝突。官方文件有記載這類問題,正式的解法是跑它自己的清理工具,而那是破壞性的。
回頭看,這個判斷就很明顯:一台機器、一個叢集,從頭到尾沒用過下游叢集、Fleet、Turtles。為了一個「看叢集」的需求,裝了一整個多叢集管理平台,出問題時要追的東西比要看的東西還多。
所以沒有去追這個衝突,直接整個換掉。
Portainer:輕量,但看 Helm 頁面會吃光記憶體
換成 Portainer CE,用 Helm 安裝,單機叢集夠用。
直到點進 Kubernetes 的 Helm 頁面。這個頁面會讀 Helm repository 的 index,記憶體瞬間飆高。
原本 limit 是 512Mi,pod 連續被 OOMKilled 三次,前端出現「Unable to retrieve helm repositories」和一堆連線被重置的錯誤。
調到 1Gi 之後還是撐不住,反覆 OOM。與其繼續加記憶體,不如承認它的行為不適合這台機器。
換成什麼
換成 Headlamp。它是 Kubernetes SIG 的專案,只做 Kubernetes,介面簡單,資源限制設 300m CPU、256Mi 記憶體就夠。
用 Helm 安裝,重點設定是 ingress 和一個 clusterRoleBinding 綁 cluster-admin,這樣可視範圍就是整個叢集。
遷移怎麼進行
遷移本身很單純,因為這類管理介面幾乎沒有自己的狀態要搬:
- 新增 Headlamp 的部署設定,移除 Portainer 的。
- 確認舊的 namespace 和 PVC 真的被清掉,沒清就手動刪。
- 把文件、儀表板裡寫死的舊 namespace 名稱全部換掉。
- 產生登入 token。
接下來才是真正花時間的部分,登入。
踩到的坑
Headlamp 的登入頁需要貼上一個 ServiceAccount 的 bearer token。
坑一:token 的有效期限
最先想到的是用 kubectl create token 產生 token。預設的有效期限只有 1 小時,所以文件上寫的是加 --duration=8760h(一年)。
後來發現隔天還是要重新登入,就修改文件,說「API Server 通常會限制手動產生 token 的存活時間,--duration 無法突破上限」,改用一個永久的 Secret:
apiVersion: v1 kind: Secret metadata: name: headlamp-permanent-token namespace: portal annotations: kubernetes.io/service-account.name: headlamp type: kubernetes.io/service-account-token 建立之後,Kubernetes 的 controller 會自動把 token 填進這個 Secret,而且不會過期。取出來用:
kubectl -n portal get secret headlamp-permanent-token -o jsonpath='{.data.token}' | base64 -d Secret 本身不含明文憑證,可以放進 Git。
這個做法是對的,但當時的診斷是錯的。為了驗證,這次在一個全新的 k3s(v1.31.5)叢集重做一遍:
| 方式 | token 的有效期限 |
|---|---|
kubectl create token(預設) | 1 小時 |
kubectl create token --duration=8760h | 8760 小時(一年),沒被截短 |
kubernetes.io/service-account-token 的 Secret | 沒有 exp,不會過期 |
也就是說,在預設的 k3s 上,--duration=8760h 其實會生效。隔天又被要求重新登入,真正的原因是下一個坑。當時那個叢集有沒有額外的 token 期限上限,已經無法確認,所以不能說前面的說法一定是錯的,但至少「--duration 無法突破上限」不是普遍成立的結論。
永久 Secret 仍然比較省事:不用記得重新產生,也能放進 Git 管理。要記得這個 token 綁的是 cluster-admin,等於整個叢集的鑰匙,不要貼在聊天或截圖裡。
坑二:token 沒過期,卻還是要重新登入
Headlamp 前端有自己的 session TTL,預設只有 24 小時,跟 token 有效期限無關。到期之後被踢回登入頁,要重貼同一組還有效的 token。預設值 24 小時是照上游文件與 相關 issue(新增 session TTL 設定)的說法,這次沒有另外量測。
chart 有 config.sessionTTL 可以調,文件說上限是一年,於是設成 31536000 秒。
結果 Headlamp 直接 CrashLoopBackOff:
invalid value "3.1536e+07" for flag -session-ttl: parse error 原因出在 chart 的 template:它把 sessionTTL 直接插進 args,沒有轉成整數。Helm 讀 values 檔裡的數字時是 float64,Go 預設的浮點數輸出格式,數值一到 7 位數就改用科學記號。
不用部署,本機 helm template 就能重現。測 chart 0.43.0,傳進去的值和渲染結果如下:
config.sessionTTL | 渲染出的參數 |
|---|---|
999999 | -session-ttl=999999 |
1000000 | -session-ttl=1e+06 |
31536000 | -session-ttl=3.1536e+07 |
分界就在 7 位數。再用寫這篇時最新的 0.46.0 測一次,31536000 仍然渲染成 3.1536e+07,問題還在。
所以只能取 6 位數以內的值。設成 999999,約 11.5 天,是不踩到科學記號的最長值。
這類「數字在 YAML 裡是整數,進了 template 變成浮點數」的坑,不只在 Headlamp,任何 chart 把數字裸插進 command line 都可能遇到。覺得數字很大的時候,先 helm template 看一眼渲染結果再部署。
坑三:Application 永遠 OutOfSync
如果用 GitOps 工具管理,Headlamp 部署後可能一直顯示 OutOfSync,雖然服務完全正常。
chart 固定在 Deployment 的 pod spec 帶入 hostUsers: true。這個欄位依賴 user namespace 的 feature gate,k3s v1.31.5 預設沒開,API server 收到會直接丟掉這個欄位,結果 live 的物件永遠沒有它,對不上期望的內容。
在乾淨的 k3s v1.31.5 上實測:chart 渲染出 hostUsers: true,套用之後讀回 Deployment,這個欄位是空的。
解法是在 Application 加 ignoreDifferences 忽略這個欄位。
換了之後的代價
Portainer 還能順便管主機上的 Docker,Headlamp 只管 Kubernetes。這個功能隨著替換一起捨棄,需要看 Docker 時回到命令列。
另外,Headlamp 比較陽春。沒有 Rancher 的多叢集、也沒有 Portainer 那種一站式的應用範本。對單機叢集來說,這些剛好都用不到。
如果日後真的要管多個叢集,或需要比較完整的權限管理,Rancher 這類工具還是會回來,只是這一次不會為了一個「看 pod」的需求先裝它。
延伸閱讀
- Headlamp 專案
- Kubernetes 文件:ServiceAccount 的長期 API token
