如何在 Kubernetes 上調(diào)試 distroless 容器
本文主要包括如下內(nèi)容:
?介紹 distroless 鏡像、作用以及簡單的使用?如何針對 distroless 容器的進行調(diào)試?臨時容器(v.1.18+)的使用
Distroless 鏡像
Distroless 容器,顧名思義使用?Distroless 鏡像[1]作為基礎(chǔ)鏡像運行的容器。
"Distroless" 鏡像只包含了你的應(yīng)用程序以及其運行時所需要的依賴。不包含你能在標(biāo)準(zhǔn) Linxu 發(fā)行版里的可以找到的包管理器、shells 或者其他程序。
GoogleContainerTools/distroless[2]?針對不同語言提供了 distroless 鏡像:
?gcr.io/distroless/static-debian11[3]?gcr.io/distroless/base-debian11[4]?gcr.io/distroless/java-debian11[5]?gcr.io/distroless/cc-debian11[6]?gcr.io/distroless/nodejs-debian11[7]?gcr.io/distroless/python3-debian11[8]
Distroless 鏡像有什么用?
那些可能是構(gòu)建鏡像時需要的,但大部分并不是運行時需要的。這也是為什么上篇文章介紹 Buildpacks?時說的一個 builder 的 stack 鏡像包含構(gòu)建時基礎(chǔ)鏡像和運行時基礎(chǔ)鏡像,這樣可以做到鏡像的最小化。
其實控制體積并不是 distroless 鏡像的主要作用。將運行時容器中的內(nèi)容限制為應(yīng)用程序所需的依賴,此外不應(yīng)該安裝任何東西。這種方式可能極大的提升容器的安全性,也是 distroless 鏡像的最重要作用。

這里并不會再深入探究 distroless 鏡像,而是如何調(diào)試 distroless 容器
沒有了包管理器,鏡像構(gòu)建完成后就不能再使用類似?apt、yum?的包管理工具;沒有了?shell,容器運行后無法再進入容器。
“就像一個沒有任何門的房間,也無法安裝門。”?Distroless 鏡像在提升容器安全性的同時,也為調(diào)試增加了難度。
使用 distroless 鏡像
寫個很簡單的 golang 應(yīng)用:
package mainimport("fmt""net/http")func defaultHandler(w http.ResponseWriter, r *http.Request){fmt.Fprintf(w,"Hello world!")}func main(){http.HandleFunc("/", defaultHandler)http.ListenAndServe(":8080",nil)}
比如使用?gcr.io/distroless/base-debian11?作為 golang 應(yīng)用的基礎(chǔ)鏡像:
FROM golang:1.12as build-envWORKDIR /go/src/appCOPY ./go/src/appRUN go get-d -v ./...RUN go build -o /go/bin/appFROM gcr.io/distroless/base-debian11COPY --from=build-env /go/bin/app /CMD ["/app"]
使用鏡像創(chuàng)建 deployment
$ kubectl create deploy golang-distroless --image addozhang/golang-distroless-example:latest$ kubectl get poNAME READY STATUS RESTARTS AGEgolang-distroless-784bb4875-srmmr 1/1Running03m2s
嘗試進入容器:
$ kubectl exec-it golang-distroless-784bb4875-srmmr -- sherror:Internal error occurred: error executing command in container: failed to execin container: failed to start exec"b76e800eafa85d39f909f39fcee4a4ba9fc2f37d5f674aa6620690b8e2939203": OCI runtime exec failed:exec failed: container_linux.go:380: starting container process caused:exec:"sh": executable file not found in $PATH: unknown
如何調(diào)試 Distroless 容器
1. 使用 distroless debug 鏡像
GoogleContainerTools 為每個 distroless 鏡像都提供了?debug?tag,適合在開發(fā)階段進行調(diào)試。如何使用?替換容器的 base 鏡像:
FROM golang:1.12as build-envWORKDIR /go/src/appCOPY ./go/src/appRUN go get-d -v ./...RUN go build -o /go/bin/appFROM gcr.io/distroless/base-debian11:debug # use debug tag hereCOPY --from=build-env /go/bin/app /CMD ["/app"]
重新構(gòu)建鏡像并部署,得益于debug鏡像中提供了 busybox shell 讓我們可以 exec 到容器中。
2. debug 容器與共享進程命名空間
同一個 pod 中可以運行多個容器,通過設(shè)置?pod.spec.shareProcessNamespace?為?true,來讓同一個 Pod 中的多容器共享同一個進程命名空間[9]。
Share a single process namespace between all of the containers in a pod. When this is set containers will be able to view and signal processes from other containers in the same pod, and the first process in each container will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both be set. Optional: Default to false.
添加一個使用?ubuntu?鏡像的?debug?容器,這里為了測試(后面解釋)我們?yōu)樵萜魈砑?securityContext.runAsUser: 1000,模擬兩個容器使用不同的 UID 運行:
apiVersion: apps/v1kind:Deploymentmetadata:creationTimestamp:nulllabels:app: golang-distrolessname: golang-distrolessspec:replicas:1selector:matchLabels:app: golang-distrolessstrategy:{}template:metadata:creationTimestamp:nulllabels:app: golang-distrolessspec:shareProcessNamespace:truecontainers:- image: addozhang/golang-distroless-example:latestname: golang-distroless-examplesecurityContext:runAsUser:1000resources:{}- image: ubuntuname: debugargs:['sleep','1d']securityContext:capabilities:add:- SYS_PTRACEresources:{}status:{}
更新 deployment 之后:
$ kubectl get poNAME READY STATUS RESTARTS AGEgolang-distroless-85c4896c45-rkjwn 2/2Running03m12s$ kubectl get po -o json | jq -r '.items[].spec.containers[].name'golang-distroless-exampledebug
然后通過 debug 容器來進入到 pod 中:
$ kubectl exec-it golang-distroless-85c4896c45-rkjwn -c debug -- sh然后在容器中執(zhí)行:
$ ps -efUID PID PPID C STIME TTY TIME CMDroot 10014:54?00:00:00/pause # infra 容器100070014:54?00:00:00/app # 原容器,UID 為 1000root 190014:55?00:00:00 sleep 1d# debug 容器root 250014:55 pts/000:00:00 shroot 3225014:55 pts/000:00:00 ps -ef
嘗試訪問 進程?7?的進程空間:
$ cat /proc/7/environ$ cat:/proc/7/environ:Permission denied
我們需要為?debug?容器加上:
securityContext:capabilities:add:- SYS_PTRACE
之后再訪問就正常了:
$ cat /proc/7/environPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=golang-distroless-58b6c5f455-v9zkvSSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crtKUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443KUBERNETES_PORT_443_TCP_PROTO=tcpKUBERNETES_PORT_443_TCP_PORT=443KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1KUBERNETES_SERVICE_HOST=10.43.0.1KUBERNETES_SERVICE_PORT=443KUBERNETES_SERVICE_PORT_HTTPS=443KUBERNETES_PORT=tcp://10.43.0.1:443HOME=/root
同樣我們也可以訪問進程的文件系統(tǒng):
$ cd /proc/7/root$ lsapp bin boot dev etc home lib lib64 proc root run sbin sys tmp usr var
無需修改容器的基礎(chǔ)鏡像,使用?pod.spec.shareProcessNamespace: true?配合安全配置中增加?SYS_PTRACE?特性,為 debug 容器賦予完整的 shell 訪問來調(diào)試應(yīng)用。但是修改 YAML 和安全配置只適合在測試環(huán)境使用,到了生產(chǎn)環(huán)境這些都是不允許的。
我們就需要用到?kubectl debug?了。
3. Kubectl debug
針對不同的資源?kubectl debug?可以進行不同操作:
?負(fù)載:創(chuàng)建一個正在運行的 Pod 的拷貝,并可以修改部分屬性。比如在拷貝中使用新版本的tag。?負(fù)載:為運行中的 Pod 增加一個臨時容器(下面介紹),使用臨時容器中的工具調(diào)試,無需重啟 Pod。?節(jié)點:在節(jié)點上創(chuàng)建一個 Pod 運行在節(jié)點的 host 命名空間,可以訪問節(jié)點的文件系統(tǒng)。
3.1 臨時容器
從 Kubernetes 1.18 之后開始,可以使用?kubectl?為運行的 pod 添加一個臨時容器。這個命令還處于?alpha?階段,因此需要在“feature gate”[10]中打開。
在使用 k3d 創(chuàng)建 k3s 集群時,打開?EphemeralContainers?feature:
$ k3d cluster create test --k3s-arg "--kube-apiserver-arg=feature-gates=EphemeralContainers=true"@然后創(chuàng)建臨時容器,創(chuàng)建完成后會直接進入容器:
$ kubectl debug golang-distroless-85c4896c45-rkjwn -it --image=ubuntu --image-pull-policy=IfNotPresent#臨時容器 shell$ apt update && apt install -y curl$ curl localhost:8080Hello world!
臨時容器值得注意的是,臨時容器無法與原容器共享進程命名空間:
$ ps -efUID PID PPID C STIME TTY TIME CMDroot 10002:59 pts/000:00:00 bashroot 30421003:02 pts/000:00:00 ps -ef
可以通過添加參數(shù)?--target=[container]?來將臨時容器掛接到目標(biāo)容器。這里與?pod.spec.shareProcessNamespace?并不同,進程號為 1 的進程是目標(biāo)容器的進程,而后者的進程是 infra 容器的進程?/pause:
$ kubectl debug golang-distroless-85c4896c45-rkjwn -it --image=ubuntu --image-pull-policy=IfNotPresent--target=golang-distroless-example注意:目前的版本還不支持刪除臨時容器,參考?issue[11],支持的版本:

3.2 拷貝 Pod 并添加容器
除了添加臨時容器以外,另一種方式就是創(chuàng)建一個 Pod 的拷貝,并添加一個容器。注意這里的是普通容器,不是臨時容器。?注意這里加上了?--share-processes
$ kubectl debug golang-distroless-85c4896c45-rkjwn -it --image=ubuntu --image-pull-policy=IfNotPresent--share-processes --copy-to=golang-distroless-debug注意這里加上了?--share-processes,會自動加上?pod.spec.shareProcessNamespace=true:
$ kubectl get po golang-distroless-debug -o jsonpath='{.spec.shareProcessNamespace}'true
注意:使用?kubectl debug?調(diào)試,并不能為 pod 自動加上?SYS_PTRACE?安全特性,這就意味著如果容器使用的 UID 不一致,就無法訪問進程空間。?截止發(fā)文,計劃在?1.23?中支持[12]。
總結(jié)
目前上面所有的都不適合在生產(chǎn)環(huán)境使用,無法在不修改 Pod 定義的情況下進行調(diào)試。
期望 Kubernetes 1.23 版本之后?debug?功能添加?SYS_PTRACE?的支持。到時候,再嘗試一下。
引用鏈接
[1]?Distroless 鏡像:?https://github.com/GoogleContainerTools/distroless[2]?GoogleContainerTools/distroless:?https://github.com/GoogleContainerTools/distroless[3]?gcr.io/distroless/static-debian11:?https://github.com/GoogleContainerTools/distroless/blob/main/base/README.md[4]?gcr.io/distroless/base-debian11:?https://github.com/GoogleContainerTools/distroless/blob/main/base/README.md[5]?gcr.io/distroless/java-debian11:?https://github.com/GoogleContainerTools/distroless/blob/main/java/README.md[6]?gcr.io/distroless/cc-debian11:?https://github.com/GoogleContainerTools/distroless/blob/main/cc/README.md[7]?gcr.io/distroless/nodejs-debian11:?https://github.com/GoogleContainerTools/distroless/blob/main/nodejs/README.md[8]?gcr.io/distroless/python3-debian11:?https://github.com/GoogleContainerTools/distroless/blob/main/experimental/python3/README.md[9]?多容器共享同一個進程命名空間:?https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/[10]?“feature gate”:?k3d%20cluster%20create%20test%20--k3s-arg%20%22--kube-apiserver-arg=feature-gates=EphemeralContainers=true%22@[11]?issue:?https://github.com/kubernetes/kubernetes/issues/84764#issuecomment-872839644[12]?計劃在?1.23?中支持:?https://github.com/kubernetes/kubernetes/issues/97103#issuecomment-899382147
? ?

喜歡明哥文章的同學(xué)歡迎長按下圖訂閱!
???
