## 背景[](https://aleiwu.com/post/kubectl-debug-intro/#%E8%83%8C%E6%99%AF)
容器技術的一個最佳實踐是構建盡可能精簡的容器鏡像。但這一實踐卻會給排查問題帶來麻煩:精簡后的容器中普遍缺失常用的排障工具,部分容器里甚至沒有 shell (比如`FROM scratch`)。 在這種狀況下,我們只能通過日志或者到宿主機上通過 docker-cli 或 nsenter 來排查問題,效率很低。Kubernetes 社區也早就意識到了這個問題,在 16 年就有相關的 Issue[Support for troubleshooting distroless containers](https://github.com/kubernetes/kubernetes/issues/27140)并形成了對應的[Proposal](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/troubleshoot-running-pods.md)。 遺憾的是,由于改動的涉及面很廣,相關的實現至今還沒有合并到 Kubernetes 上游代碼中。而在 一個偶然的機會下(PingCAP 一面要求實現一個 kubectl 插件實現類似的功能),我開發了[kubectl-debug](https://github.com/aylei/kubectl-debug):**通過啟動一個安裝了各種排障工具的容器,來幫助診斷目標容器**。
## 工作原理[](https://aleiwu.com/post/kubectl-debug-intro/#%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86)
我們先不著急進入 Quick Start 環節。`kubectl-debug`本身非常簡單,因此只要理解了它的工作原理,你就能完全掌握這個工具,并且還能用它做 debug 之外的事情。
我們知道,容器本質上是帶有 cgroup 資源限制和 namespace 隔離的一組進程。因此,我們只要啟動一個進程,并且讓這個進程加入到目標容器的各種 namespace 中,這個進程就能 “進入容器內部”(注意引號),與容器中的進程”看到”相同的根文件系統、虛擬網卡、進程空間了——這也正是`docker exec`和`kubectl exec`等命令的運行方式。
現在的狀況是,我們不僅要 “進入容器內部”,還希望帶一套工具集進去幫忙排查問題。那么,想要高效管理一套工具集,又要可以跨平臺,最好的辦法就是把工具本身都打包在一個容器鏡像當中。 接下來,我們只需要通過這個”工具鏡像”啟動容器,再指定這個容器加入目標容器的的各種 namespace,自然就實現了 “攜帶一套工具集進入容器內部”。事實上,使用 docker-cli 就可以實現這個操作:
~~~bash
export TARGET_ID=666666666
# 加入目標容器的 network, pid 以及 ipc namespace
docker run -it --network=container:$TARGET_ID --pid=container:$TARGET_ID --ipc=container:$TARGET_ID busybox
~~~
這就是 kubectl-debug 的出發點:**用工具容器來診斷業務容器**。背后的設計思路和 sidecar 等模式是一致的:每個容器只做一件事情。
具體到實現上,一條`kubectl debug <target-pod>`命令背后是這樣的:

步驟分別是:
1. 插件查詢 ApiServer:demo-pod 是否存在,所在節點是什么
2. ApiServer 返回 demo-pod 所在所在節點
3. 插件請求在目標節點上創建`Debug Agent`Pod
4. Kubelet 創建`Debug Agent`Pod
5. 插件發現`Debug Agent`已經 Ready,發起 debug 請求(長連接)
6. `Debug Agent`收到 debug 請求,創建 Debug 容器并加入目標容器的各個 Namespace 中,創建完成后,與 Debug 容器的 tty 建立連接
接下來,客戶端就可以開始通過 5,6 這兩個連接開始 debug 操作。操作結束后,Debug Agent 清理 Debug 容器,插件清理 Debug Agent,一次 Debug 完成。效果如下圖:

## 開始使用[](https://aleiwu.com/post/kubectl-debug-intro/#%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8)
Mac 可以直接使用 brew 安裝:
~~~bash
brew install aylei/tap/kubectl-debug
~~~
所有平臺都可以通過下載 binary 安裝:
~~~bash
export PLUGIN_VERSION=0.1.1
# linux x86_64
curl -Lo kubectl-debug.tar.gz https://github.com/aylei/kubectl-debug/releases/download/v${PLUGIN_VERSION}/kubectl-debug_${PLUGIN_VERSION}_linux_amd64.tar.gz
# macos
curl -Lo kubectl-debug.tar.gz https://github.com/aylei/kubectl-debug/releases/download/v${PLUGIN_VERSION}/kubectl-debug_${PLUGIN_VERSION}_darwin_amd64.tar.gz
tar -zxvf kubectl-debug.tar.gz kubectl-debug
sudo mv kubectl-debug /usr/local/bin/
~~~
Windows 用戶可以在[Release 頁面](https://github.com/aylei/kubectl-debug/releases/tag/v0.1.1)進行下載。
下載完之后就可以開始使用 debug 插件:
~~~bash
kubectl debug target-pod --agentless --port-forward
~~~
> kubectl 從 1.12 版本之后開始支持從 PATH 中自動發現插件。1.12 版本之前的 kubectl 不支持這種插件機制,但也可以通過命令名`kubectl-debug`直接調用。
可以參考項目的[中文 README](https://github.com/aylei/kubectl-debug/blob/master/docs/zh-cn.md)來獲得更多文檔和幫助信息。
## 典型案例[](https://aleiwu.com/post/kubectl-debug-intro/#%E5%85%B8%E5%9E%8B%E6%A1%88%E4%BE%8B)
### 基礎排障[](https://aleiwu.com/post/kubectl-debug-intro/#%E5%9F%BA%E7%A1%80%E6%8E%92%E9%9A%9C)
kubectl debug 默認使用[nicolaka/netshoot](https://github.com/nicolaka/netshoot)作為默認的基礎鏡像,里面內置了相當多的排障工具,包括:
使用**iftop**查看容器網絡流量:
~~~bash
? ~ kubectl debug demo-pod
root @ /
[2] ?? → iftop -i eth0
interface: eth0
IP address is: 10.233.111.78
MAC address is: 86:c3:ae:9d:46:2b
# (圖片略去)
~~~
使用**drill**診斷 DNS 解析:
~~~bash
root @ /
[3] ?? → drill -V 5 demo-service
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 0
;; flags: rd ; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; demo-service. IN A
;; ANSWER SECTION:
;; AUTHORITY SECTION:
;; ADDITIONAL SECTION:
;; Query time: 0 msec
;; WHEN: Sat Jun 1 05:05:39 2019
;; MSG SIZE rcvd: 0
;; ->>HEADER<<- opcode: QUERY, rcode: NXDOMAIN, id: 62711
;; flags: qr rd ra ; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;; demo-service. IN A
;; ANSWER SECTION:
;; AUTHORITY SECTION:
. 30 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019053101 1800 900 604800 86400
;; ADDITIONAL SECTION:
;; Query time: 58 msec
;; SERVER: 10.233.0.10
;; WHEN: Sat Jun 1 05:05:39 2019
;; MSG SIZE rcvd: 121
~~~
使用**tcpdump**抓包:
~~~bash
root @ /
[4] ?? → tcpdump -i eth0 -c 1 -Xvv
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
12:41:49.707470 IP (tos 0x0, ttl 64, id 55201, offset 0, flags [DF], proto TCP (6), length 80)
demo-pod.default.svc.cluster.local.35054 > 10-233-111-117.demo-service.default.svc.cluster.local.8080: Flags [P.], cksum 0xf4d7 (incorrect -> 0x9307), seq 1374029960:1374029988, ack 1354056341, win 1424, options [nop,nop,TS val 2871874271 ecr 2871873473], length 28
0x0000: 4500 0050 d7a1 4000 4006 6e71 0ae9 6f4e E..P..@.@.nq..oN
0x0010: 0ae9 6f75 88ee 094b 51e6 0888 50b5 4295 ..ou...KQ...P.B.
0x0020: 8018 0590 f4d7 0000 0101 080a ab2d 52df .............-R.
0x0030: ab2d 4fc1 0000 1300 0000 0000 0100 0000 .-O.............
0x0040: 000e 0a0a 08a1 86b2 ebe2 ced1 f85c 1001 .............\..
1 packet captured
11 packets received by filter
0 packets dropped by kernel
~~~
訪問目標容器的根文件系統:
容器技術(如 Docker)利用了`/proc`文件系統提供的`/proc/{pid}/root/`目錄實現了為隔離后的容器進程提供單獨的根文件系統(root filesystem)的能力(就是`chroot`一下)。當我們想要訪問 目標容器的根文件系統時,可以直接訪問這個目錄:
~~~bash
root @ /
[5] ?? → tail -f /proc/1/root/log_
Hello, world!
~~~
這里有一個常見的問題是`free``top`等依賴`/proc`文件系統的命令會展示宿主機的信息,這也是容器化過程中開發者需要適應的一點(當然了,各種 runtime 也要去適應,比如臭名昭著的[Java 8u121 以及更早的版本不識別 cgroups 限制](https://blog.softwaremill.com/docker-support-in-new-java-8-finally-fd595df0ca54)問題就屬此列)。
### 診斷 CrashLoopBackoff[](https://aleiwu.com/post/kubectl-debug-intro/#%E8%AF%8A%E6%96%AD-crashloopbackoff)
排查`CrashLoopBackoff`是一個很麻煩的問題,Pod 可能會不斷重啟,`kubectl exec`和`kubectl debug`都沒法穩定進行排查問題,基本上只能寄希望于 Pod 的日志中打印出了有用的信息。 為了讓針對`CrashLoopBackoff`的排查更方便,`kubectl-debug`參考`oc debug`命令,添加了一個`--fork`參數。當指定`--fork`時,插件會復制當前的 Pod Spec,做一些小修改, 再創建一個新 Pod:
* 新 Pod 的所有 Labels 會被刪掉,避免 Service 將流量導到 fork 出的 Pod 上
* 新 Pod 的`ReadinessProbe`和`LivnessProbe`也會被移除,避免 kubelet 殺死 Pod
* 新 Pod 中目標容器(待排障的容器)的啟動命令會被改寫,避免新 Pod 繼續 Crash
接下來,我們就可以在新 Pod 中嘗試復現舊 Pod 中導致 Crash 的問題。為了保證操作的一致性,可以先`chroot`到目標容器的根文件系統中:
~~~bash
? ~ kubectl debug demo-pod --fork
root @ /
[4] ?? → chroot /proc/1/root
root @ /
[#] ?? → ls
bin entrypoint.sh home lib64 mnt root sbin sys tmp var
dev etc lib media proc run srv usr
root @ /
[#] ?? → ./entrypoint.sh
# 觀察執行啟動腳本時的信息并根據信息進一步排障
~~~
## 結尾的碎碎念[](https://aleiwu.com/post/kubectl-debug-intro/#%E7%BB%93%E5%B0%BE%E7%9A%84%E7%A2%8E%E7%A2%8E%E5%BF%B5)
`kubectl-debug`一開始只是 PingCAP 在面試時出的 homework,第一版完成在去年年底。當時整個項目還非常粗糙,不僅文檔缺失,很多功能也都有問題:
* 不支持診斷 CrashLoopBackoff 中的 Pod
* 強制要求預先安裝一個 Debug Agent 的 DaemonSet
* 不支持公有云(節點沒有公網 IP 或公網 IP 因為防火墻原因無法訪問時,就無法 debug)
* 沒有權限限制,安全風險很大
而讓我非常興奮的是,在我無暇打理項目的情況下,隔一兩周就會收到 Pull Request 的通知郵件,一直到今天,大部分影響基礎使用體驗的問題都已經被解決,`kubectl-debug`也發布了 4 個版本(`0.0.1`,`0.0.2`,`0.1.0`,`0.1.1`)。尤其要感謝[@tkanng](https://github.com/tkanng), TA 在第一個 PR 時還表示之前沒有寫過 Go, 而在`0.1.1`版本中已經是這個版本絕大部分 feature 的貢獻者,解決了好幾個持續很久的 issue,感謝!
最后再上一下項目地址:[https://github.com/aylei/kubectl-debug](https://github.com/aylei/kubectl-debug)
參考資料:https://aleiwu.com/post/kubectl-debug-intro/
https://github.com/aylei/kubectl-debug