kubectl debug - 調試 Kubernetes 的最簡方法

調試容器化的 Pod 是每個使用 Kubernetes 開發人員和 DevOps 工程師的日常任務。通常,簡單的kubectl logs、kubectl describe pod就足以找到某些問題的罪魁禍首,但有些問題很難找到。在這些情況下,您可能會嘗試使用kubectl exec,但這可能還不夠,因爲一些容器 (如 Distroless) 甚至不包含您可以 SSH 進入的 shell。如果以上都失敗了,我們還能怎麼辦呢?

這或許是更好的方法


有時你需要拿一個更大的錘子或使用更合適的工具來完成手頭的任務。在 Kubernetes 上調試工作問題時,合適的工具應該是kubectl debug,它是不久前添加的一個新命令 (v1.18),允許您調試正在運行的 pod。它將名爲 EphemeralContainer 的特殊類型的容器注入到有問題的 Pod 中,允許您四處查看並進行故障排除。這對於介紹中描述的情況或其他交互式調試場景下更可取或更有效。因此,kubectl debug看起來是可行的方法。但要使用它,我們需要臨時性的容器ephemeral containers,那麼這些到底是什麼呢?

臨時容器是 Pod 中的一個子資源,類似於普通容器。不過,與常規容器不同的是,臨時容器並非用於構建應用程序,而是用於檢查應用程序。我們不會在 Pod 創建時定義它們,而是使用特殊的 API 將它們注入到運行 Pod 中,以運行故障排除命令和檢查 Pod 的環境。除了這些不同之外,臨時容器還缺少一些基本容器的字段,如 port 或 resource。

但我們爲什麼需要它們呢? 我們不能只用基本的容器嗎? 好吧,你不能把容器添加到 Pod,因爲它們應該是一次性的 (或者換句話說,在任何時候刪除和重新創建),這可能會使故障排除困難,很難重現需要檢查的 bug。這就是爲什麼將臨時容器添加到 API 中——它們允許您將容器添加到現有的容器中,從而更容易檢查正在運行的容器。

考慮到臨時容器是 Pod 規範的一部分,而 Pod 規範是 Kubernetes 的核心,爲什麼你 (可能) 還沒有聽說過它呢? 這些特性大多是未知的原因是因爲臨時容器處於 Alpha 早期階段,這意味着它們在默認情況下沒有啓用。這一階段的資源和特性可能會在 Kubernetes 的未來版本中發生重大變化或被完全刪除。因此,要使用它們,你必須明確地使用 kubelet 中的 Feature Gates 來啓用它們。

配置啓用特性功能

我們已經確定要嘗試kubectl debug,那麼如何啓用臨時容器特性門呢? 這取決於您的集羣設置。例如,如果你正在使用 kubeadm 來創建集羣,那麼你可以使用以下的 ClusterConfiguration 來啓用臨時容器:

apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.20.2
apiServer:
  extraArgs:
    feature-gates: EphemeralContainers=true

在下面的例子中,我們將使用 KinD (Docker 中的 Kubernetes) 集羣作爲簡單集羣用於測試目的,這也允許我們指定我們想要啓用的 Feature Gates。因此,要創建我們需要的集羣:

# File: config.yaml
# Run:  kind create cluster --config ./config.yaml --name kind --image=kindest/node:v1.20.2
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  EphemeralContainers: true
nodes:
- role: control-plane

隨着集羣的運行,我們應該驗證它確實工作了。查看這個配置是否被應用的最簡單的方法是檢查 Pod API,它現在應該在通常的容器旁邊包含 ephemeralContainers 部分:

~ $ kubectl explain pod.spec.ephemeralContainers
KIND:     Pod
VERSION:  v1

RESOURCE: ephemeralContainers <[]Object>

DESCRIPTION:
     List of ephemeral containers run in this pod....
...

這確認了我們已經有了它,因此我們可以開始使用kubectl debug。讓我們從一個簡單的例子開始:

~ $ kubectl run some-app --image=k8s.gcr.io/pause:3.1 --restart=Never
~ $ kubectl debug -it some-app --image=busybox --target=some-app
Defaulting debug container name to debugger-tfqvh.
If you don't see a command prompt, try pressing enter.
/ #

# From other terminal...
~ $ kubectl describe pod some-app
...
Containers:
  some-app:
    Container ID:   containerd://60cc537eee843cb38a1ba295baaa172db8344eea59de4d75311400436d4a5083
    Image:          k8s.gcr.io/pause:3.1
    Image ID:       k8s.gcr.io/pause@sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea
...
Ephemeral Containers:
  debugger-tfqvh:
    Container ID:   containerd://12efbbf2e46bb523ae0546b2369801b51a61e1367dda839ce0e02f0e5c1a49d6
    Image:          busybox
    Image ID:       docker.io/library/busybox@sha256:ce2360d5189a033012fbad1635e037be86f23b65cfd676b436d0931af390a2ac
    Port:           >none<
    Host Port:      >none<
    State:          Running
      Started:      Mon, 15 Mar 2021 20:33:51 +0100
    Ready:          False
    Restart Count:  0
    Environment:    >none<
    Mounts:         >none<

我們首先啓動一個叫做 some-app 的 Pod,這樣我們就可以 debug 一些東西。然後我們對這個 Pod 運行 kubectl 調試,指定 busybox 作爲臨時容器鏡像,並指定一個目標作爲原始容器。此外,我們還包括 - it 參數,以便我們立即附加到容器並獲得一個 shell 會話。

在上面的代碼片段中,您還可以看到,如果我們在運行kubectl debug後描述 Pod,那麼它的描述將包括 Ephemeral Containers 部分,其中的值是我們在前面指定的命令選項。

共享進程空間

kubectl debug是一個非常強大的工具,但有時向 Pod 添加另一個容器可能不足以獲取在 Pod 的其他容器中運行的應用程序的相關信息。例如當進行故障排除的容器不包括必要的調試工具甚至 shell 時。在這種情況下,我們可以使用進程共享注入臨時容器來檢查 Pod 的原始容器。

但進程共享的一個問題是,它不能應用於現有的 Pods,因此我們必須創建一個新的,將 spec.shareProcessNamespace 設置爲 true,並向其注入一個臨時容器。這樣做會非常麻煩,特別是當我們必須調試多個 Pod/Container 或只是重複執行此操作時。幸運的是,kubectl debug可以使用——share-processes 選項爲我們做到這一點:

~ $ kubectl run some-app --image=nginx --restart=Never
~ $ kubectl debug -it some-app --image=busybox --share-processes --copy-to=some-app-debug
Defaulting debug container name to debugger-tkwst.
If you don't see a command prompt, try pressing enter.
/ # ps ax
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    8 root      0:00 nginx: master process nginx -g daemon off;
   38 101       0:00 nginx: worker process
   39 root      0:00 sh
   46 root      0:00 ps ax

~ $ cat /proc/8/root/etc/nginx/conf.d/default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;
...

上面的代碼片段表明,通過進程共享,我們可以看到 Pod 中其他容器中的所有內容,包括它的進程和文件,這對於調試無疑是非常方便的。

您可能已經注意到,除了——shared-processes 之外,我們還包含了——copy-to=new-pod-name,因爲我們需要創建一個新的 pod,它的名稱由這個標誌指定。如果我們從另一個終端列出運行的 pods,我們將看到以下內容:

# From other terminal:
~ $ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
some-app         1/1     Running   0          23h
some-app-debug   2/2     Running   0          20s

這是我們新的調試 Pod 和原來的應用 Pod。與原來的容器相比,它有兩個容器,因爲它還包括臨時容器。

此外,如果你想驗證進程共享是否被允許,那麼你可以運行:

~ $ kubectl get pod some-app-debug -o json  | jq .spec.shareProcessNamespace
true

更有效的利用

現在我們已經啓用了特性門,並且知道了命令是如何工作的,讓我們試着很好地使用它並調試一些應用程序。讓我們想象一下下面的場景——我們有一個不正常的應用程序,我們需要在它的容器中排除與網絡相關的問題。該應用程序沒有我們可以使用的必要的網絡 CLI 工具。爲了解決這個問題,我們可以通過以下方式使用kubectl debug:

~ $ kubectl run distroless-python --image=martinheinz/distroless-python --restart=Never
~ $ kubectl exec -it distroless-python -- /bin/sh
# id
/bin/sh: 1: id: not found
# ls
/bin/sh: 2: ls: not found
# env
/bin/sh: 3: env: not found
#
...

kubectl debug -it distroless-python --image=praqma/network-multitool --target=distroless-python -- sh
Defaulting debug container name to debugger-rvtd4.
If you don't see a command prompt, try pressing enter.
/ # ping localhost
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1)icmp_seq=ttl=64 time=0.025 ms
64 bytes from localhost (::1)icmp_seq=ttl=64 time=0.044 ms
64 bytes from localhost (::1)icmp_seq=ttl=64 time=0.027 ms

在啓動一個 pod 後,我們首先嚐試將 shell 會話放入它的容器中,這可能看起來是有效的,但當我們嘗試運行一些基本命令時,我們可以看到那裏實際上什麼也沒有。因此,我們使用praqma/network-multitool image將臨時容器注入到 pod 中,其中包含curl、ping、telnet等工具,現在我們可以執行所有必要的故障排除。

在上面的例子中,我們在 Pod 中添加另一個容器就足夠了。但有時,您可能需要直接查看有問題的容器,而沒有辦法進入其 shell。在這種情況下,我們可以像這樣利用進程共享:

~ $ kubectl run distroless-python --image=martinheinz/distroless-python --restart=Never
~ $ kubectl debug -it distroless-python --image=busybox --share-processes --copy-to=distroless-python-debug
Defaulting debug container name to debugger-l692h.
If you don't see a command prompt, try pressing enter.
/ # ps ax
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    8 root      0:00 /usr/bin/python3.5 sleep.py  # Original container is just sleeping forever
   14 root      0:00 sh
   20 root      0:00 ps ax
/ # cat /proc/8/root/app/sleep.py
import time
print("sleeping for 1 hour")
time.sleep(3600)

這裏我們再次運行使用 Distroless 映像的容器。知道我們不能在它的 shell 中做任何事情,我們運行kubectl debug with——share-processes——copy-to=…,它會創建一個新的 Pod,其中包含可以訪問所有進程的額外臨時容器。然後列出正在運行的進程,可以看到應用程序容器的進程有 PID 8,我們可以用它來研究它的文件和環境。要做到這一點,我們必須通過/proc/>PID</… 目錄—在本例中是 - /proc/8/root/app/....

另一種常見的情況是,應用程序在容器開始時不斷崩潰,使得調試變得困難,因爲沒有足夠的時間將 shell 會話放入容器並運行一些故障排除命令。在這種情況下,解決方案是創建帶有不同入口點 / 命令的容器,例如一個循環休眠的進程,這將立即阻止應用程序崩潰,並允許我們執行調試:

~ $ kubectl get pods
NAME                READY   STATUS             RESTARTS   AGE
crashing-app        0/1     CrashLoopBackOff   1          8s

~ $ kubectl debug crashing-app -it --copy-to=crashing-app-debug --container=crashing-app -- sh
If you don't see a command prompt, try pressing enter.
# id
uid=0(root) gid=0(root) groups=0(root)
#
...

# From another terminal
~ $ kubectl get pods
NAME                READY   STATUS             RESTARTS   AGE
crashing-app        0/1     CrashLoopBackOff   3          2m7s
crashing-app-debug  1/1     Running            0          16s

附加功能 - 調試集羣節點

本文主要關注調試 Pods 和它們的容器——但是任何集羣管理員都知道——通常需要調試的是節點而不是 Pods。幸運的是,kubectl debug還允許通過創建 Pod 來調試節點,Pod 將運行在指定的節點上,節點的根文件系統掛載在 / root 目錄下。考慮到我們甚至可以使用 chroot 訪問主機二進制文件,這本質上是一個到節點的 SSH 連接:

~ $ kubectl get nodes
NAME                 STATUS   ROLES                  AGE   VERSION
kind-control-plane   Ready    control-plane,master   25h   v1.20.2

~ $ kubectl debug node/kind-control-plane -it --image=ubuntu
Creating debugging pod node-debugger-kind-control-plane-hvljt with container debugger on node kind-control-plane.
If you don't see a command prompt, try pressing enter.
root@kind-control-plane:/# chroot /host

# head kind/kubeadm.conf
apiServer:
  certSANs:
  - localhost
  - 127.0.0.1
  extraArgs:
    feature-gates: EphemeralContainers=true
    runtime-config: ""
apiVersion: kubeadm.k8s.io/v1beta2
clusterName: kind
controlPlaneEndpoint: kind-control-plane:6443

在上面的代碼片段中,我們首先確定了要調試的節點,然後使用 node/… 顯式地運行 kubectl debug。作爲參數來訪問我們集羣的節點。在那之後,當我們連接到 Pod,我們使用 chroot /host 打破 chroot 壁壘,獲得完全訪問主機。最後,爲了驗證我們確實可以看到主機上的所有內容,我們查看 kubeadm.conf 的一部分,在這可以看到特性:EphemeralContainers=true,這是我們在本文開始時配置的。

總結

能夠快速有效地調試應用程序和服務可以爲您節省大量時間,但更重要的是,它可以極大地幫助您解決無法立即解決或者可能會導致您花費大量金錢的問題。這就是爲什麼讓 kubectl 這樣的工具在您的使用和啓用時很重要,即使它們不是 GA 或默認啓用。

無論出於什麼原因 - 啓用臨時容器可能不是一個最優選項,那麼嘗試其他調試方法可能是一個好主意,例如使用調試版本的應用程序鏡像,其中將包括故障排除工具; 或者臨時改變 Pod 的容器的命令指令,以防止它崩潰。

kubectl 調試和臨時容器只是 Kubernetes 許多有用的功能之一——但很少有人知道——所以請密切關注後續文章,這些文章將深入研究 Kubernetes 的其他一些隱藏功能。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/8uN2MySGlgxTed5vwqfFlA