容器故障?別慌:debug 不行,還有superdebug
本文轉(zhuǎn)載自微信公眾號「云原生百寶箱」
這篇內(nèi)容主要探討了 Kubernetes 的調(diào)試功能,介紹了 kubectl debug 和 kubectl superdebug。它們支持容器掛載并且能夠調(diào)試一些需要排查問題的 Pod。文章指出了在 Kubernetes 中使用 kubectl exec 命令的限制,并介紹了 kubectl debug 的作用,它能創(chuàng)建一個(gè)新的容器來調(diào)試運(yùn)行中的容器,并且能夠在同一個(gè) Pod 內(nèi)共享系統(tǒng)資源。此外,還提到了 ephemeral containers,它們在調(diào)試過程中可以臨時(shí)運(yùn)行在現(xiàn)有的 Pod 中,支持一些排查操作。最后,文章還提及了一些非 Kubernetes 本地調(diào)試容器的方法,包括使用 Docker Engine 或者一些基于 Linux namespaces 的工具。
使用 kubectl exec 執(zhí)行命令
如果你在 Kubernetes 上運(yùn)行軟件,你有時(shí)會想要調(diào)試所部署的應(yīng)用。對于習(xí)慣使用虛擬機(jī)的人來說,一種簡單的調(diào)試方法是連接到正在運(yùn)行的 Pod 并進(jìn)行分享:
kubectl exec -it podname -c containername -- bash
這通常有效并且非常有用。然而,至少有兩個(gè) Kubernetes“最佳實(shí)踐”限制了 exec 在現(xiàn)實(shí)世界中的用處:
? 不以 root 身份運(yùn)行。容器以盡可能少的權(quán)限運(yùn)行,甚至可以使用隨機(jī) UID 運(yùn)行。
? 最小鏡像。鏡像盡可能小,極端情況下將二進(jìn)制文件安裝到distroless 鏡像中。[1]
當(dāng)應(yīng)用這些最佳實(shí)踐時(shí),使用kubectl exec連接到容器要么是不可能的,要么會讓你陷入不適合調(diào)試的貧瘠荒地般的環(huán)境。
調(diào)試容器
調(diào)試正在運(yùn)行的容器的 Kubernetes 原生答案是使用kubectl debug。
debug 命令將一個(gè)新容器附加到正在運(yùn)行的 pod 中。這個(gè)新容器可以以不同的用戶身份從你選擇的任何鏡像運(yùn)行。由于調(diào)試容器與其目標(biāo)容器在同一 Pod 中運(yùn)行(因此在同一節(jié)點(diǎn)上),因此兩個(gè)容器之間的隔離不需要是絕對的。調(diào)試容器可以與同一 Pod 中運(yùn)行的其他容器共享系統(tǒng)資源。
考慮要檢查pod容器postpod中運(yùn)行的 PostgreSQL 數(shù)據(jù)庫的 CPU 使用情況。Pod 不以 root 身份運(yùn)行,并且 Postgres 鏡像沒有類似top或htop安裝的工具——換句話說,該kubectl exec命令沒什么用處。你可以運(yùn)行以下命令:
kubectl debug -it \
--container=debug-container \
--image=alpine \
--target=postcont \
postpod
你將以 root 身份登錄(這是 Alpine 鏡像的默認(rèn)設(shè)置),并且可以輕松安裝你最喜歡的交互式進(jìn)程查看器 htop ( apt add htop)。你與容器postcont共享相同的進(jìn)程命名空間,并且可以查看甚至殺死在那里運(yùn)行的所有進(jìn)程!當(dāng)你退出該進(jìn)程時(shí),臨時(shí)容器也將停止存在。
注意:你可以通過按 CTRL+P 或 CTRL+D 斷開與臨時(shí)容器/bash 會話的連接,而無需退出(終止)它。然后你可以稍后使用 重新連接到它kubectl attach。
注意:kubectl debug提供的功能比此處概述的更多,例如使用修改后的啟動命令復(fù)制 Pod 或啟動可訪問節(jié)點(diǎn)文件系統(tǒng)的“節(jié)點(diǎn)”Pod。
臨時(shí)容器
上面的命令kubectl debug通過創(chuàng)建一個(gè)稱為臨時(shí)容器[2]東西來工作。這些容器應(yīng)該在現(xiàn)有Pod 中臨時(shí)運(yùn)行,以支持故障排除等操作。
“普通”容器和臨時(shí)容器之間的區(qū)別很小。沒有什么能真正阻止臨時(shí)容器長期運(yùn)行。我認(rèn)為,通過查看 Kubernetes 在誕生之初所做的基礎(chǔ)架構(gòu)選擇,可以最好地理解擁有臨時(shí)容器的原因:
? Pod 應(yīng)該是一次性且可更換的,并且支持這一點(diǎn),
? Pod 規(guī)范是不可變的。
當(dāng) Kubernetes 主要用于部署無狀態(tài)工作負(fù)載時(shí)(當(dāng) Pod 本身可以被認(rèn)為是短暫的)時(shí),這非常有意義。在這個(gè) Kubernetes無所不能的新世界中,它可能會受到限制。Pod 規(guī)范保持不變,但 Kubernetes 將臨時(shí)容器建模為Pod 的子資源。與“普通”容器不同,臨時(shí)容器不是 Pod規(guī)范的一部分,即使它們是 pod 的一部分。這種微妙的區(qū)別讓每個(gè)人都高興??!
臨時(shí)容器仍然相對較新;
它們自 Kubernetes v1.25(2022 年 8 月)處于Stable狀態(tài),自 v1.23(2021 年 12 月)是Beta狀態(tài),自 v1.22(2021 年 8 月)是Alpha狀態(tài)。
掛載卷
內(nèi)置命令kubectl debug非常有用。它允許你將臨時(shí)容器添加到正在運(yùn)行的 Pod,可以選擇與正在運(yùn)行的容器共享其進(jìn)程命名空間。但是,如果你希望用于kubectl debug檢查或修改正在運(yùn)行的容器的文件系統(tǒng)的任何部分,那么你就不走運(yùn)了 - 調(diào)試 Pod 的文件系統(tǒng)與你連接到的容器的文件系統(tǒng)是脫節(jié)的。
幸運(yùn)的是,我們可以做得更好。這個(gè)想法很簡單:
? 檢索正在運(yùn)行的目標(biāo)容器的規(guī)格。
? 將臨時(shí)容器修補(bǔ)到 Pod 中。將其配置為與目標(biāo)容器共享相同的進(jìn)程命名空間,并另外包含相同的卷掛載。
沒有用于創(chuàng)建臨時(shí)容器的 kubectl 命令,因此我們需要向 K8s API 發(fā)送 PATCH 請求來創(chuàng)建它。該kubectl proxy命令允許訪問 K8s API。
這個(gè)過程并不完全是用戶友好的,因此將過程包裝到腳本或 kubectl 插件中是有意義的。你可以在此處找到此類腳本的示例實(shí)現(xiàn):
https://github.com/JonMerlevede/kubectl-superdebug
kubectl debug 的擴(kuò)展將目標(biāo)容器的卷規(guī)格附加到臨時(shí)調(diào)試 pod 中……
kubectl superdebug
請注意,此方法和腳本可以輕松擴(kuò)展以從目標(biāo)容器復(fù)制環(huán)境變量規(guī)范。
如果將此腳本另存為kubectl-superdebug并使其在你的路徑上可用,則可以kubectl superdebug從任何地方運(yùn)行它,如下所示:
kubectl superdebug \
--container=debug-container \
--image=alpine \
--target=postcont \
postpod
你可能還想擴(kuò)展此腳本以將目標(biāo)容器的其他方面復(fù)制到調(diào)試容器中,例如對環(huán)境變量的引用。
這完成了調(diào)試運(yùn)行容器的 Kubernetes 原生方法的概述,應(yīng)該可以滿足大多數(shù)人的需求。
非 Kubernetes 原生方法
Kubernetes 不提供以 root 身份連接到正在運(yùn)行的容器(除非主進(jìn)程以 root 身份運(yùn)行)或從另一個(gè)容器訪問容器的根文件系統(tǒng)的方法。這并不意味著這些事情不可能做到。畢竟,Kubernetes 只是一個(gè)位于容器化引擎之上的容器編排器。如果由于某種原因確實(shí)有必要的話,通??梢酝ㄟ^刪除抽象層來做任何你想做的事情。只要確保你必須...
如果你使用 Docker 引擎并且可以直接從節(jié)點(diǎn)或通過節(jié)點(diǎn)上運(yùn)行的特權(quán)容器訪問你的引擎,那么你可以docker exec --user作為你選擇的用戶運(yùn)行和執(zhí)行進(jìn)程。
諸如kubectl ssh和 之類的插件kubectl exec-user實(shí)現(xiàn)了這種方法。不幸的是, containerd[3]和CRI-O[4]等現(xiàn)代引擎不再提供--user標(biāo)志功能——這意味著這些插件無法在現(xiàn)代 Kubernetes 安裝上運(yùn)行。
然而,即使是這些現(xiàn)代引擎通常也只是與 Linux 命名空間交互。你可以通過輸入適當(dāng)?shù)?Linux 命名空間集在任何你想要的“容器”中運(yùn)行命令。kpexec[5]工具實(shí)現(xiàn)了這種方法。它在與目標(biāo)容器相同的節(jié)點(diǎn)上啟動一個(gè)特權(quán) Pod,然后確定要定位的 (Linux) 命名空間,在這些 (Linux) 命名空間中執(zhí)行命令,最后將其輸出流式傳輸?shù)侥愕慕K端。作為一個(gè)額外的好處,它可以在目標(biāo)容器的文件系統(tǒng)之上覆蓋一組可用于調(diào)試的工具。
與 kubectl exec 不同,kpexec 可以運(yùn)行具有不同 uid/gid 甚至不同功能的命令作為容器的主進(jìn)程。它與containerd和cri-o兼容。kpexec 采用某種重量級且脆弱的方法,可能與你的集群的安全配置不兼容。如果 kubectl (super)debug 無法滿足你的需求,則值得考慮。
請注意,kpexec 使用nsenter命令直接執(zhí)行到命名空間中。它與無處不在的容器運(yùn)行時(shí) runc 兼容,但與Kata Containers[6]等運(yùn)行時(shí)不兼容。
在這篇文章中,我們研究了兩種調(diào)試運(yùn)行容器的 Kubernetes 原生方法:kubectl exec和kubectl debug。我們研究了kubectl debug工作原理,并提出了kubectl superdebug一種變體,kubectl debug它啟動一個(gè)與目標(biāo)容器共享相同卷和相同進(jìn)程命名空間的臨時(shí)容器。最后,我們回顧了一些非 Kubernetes 原生的容器調(diào)試方法。
引用鏈接
[1] distroless 鏡像中。: https://github.com/GoogleContainerTools/distroless
[2] 臨時(shí)容器: https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/
[3] containerd: https://containerd.io/
[4] CRI-O: https://cri-o.io/
[5] kpexec: https://github.com/ssup2/kpexec[6] Kata Containers: https://katacontainers.io/