Kubernetes 上调试 distroless 容器
作者 | Addo Zhang
來源 | 云原生指北
Distroless 鏡像
Distroless 容器,顧名思義使用?Distroless 鏡像[1]作為基礎鏡像運行的容器。
"Distroless" 鏡像只包含了你的應用程序以及其運行時所需要的依賴。不包含你能在標準 Linxu 發行版里的可以找到的包管理器、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 鏡像有什么用?
那些可能是構建鏡像時需要的,但大部分并不是運行時需要的。這也是為什么上篇文章介紹 Buildpacks
其實控制體積并不是 distroless 鏡像的主要作用。將運行時容器中的內容限制為應用程序所需的依賴,此外不應該安裝任何東西。這種方式可能極大的提升容器的安全性,也是 distroless 鏡像的最重要作用。
這里并不會再深入探究 distroless 鏡像,而是如何調試 distroless 容器
沒有了包管理器,鏡像構建完成后就不能再使用類似?apt、yum?的包管理工具;沒有了?shell,容器運行后無法再進入容器。
“就像一個沒有任何門的房間,也無法安裝門。”?Distroless 鏡像在提升容器安全性的同時,也為調試增加了難度。
使用 distroless 鏡像
寫個很簡單的 golang 應用:
package main import ("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 應用的基礎鏡像:
FROM golang:1.12 as build-env WORKDIR /go/src/app COPY . /go/src/app RUN go get -d -v ./... RUN go build -o /go/bin/app FROM gcr.io/distroless/base-debian11 COPY --from=build-env /go/bin/app / CMD ["/app"]使用鏡像創建 deployment
$ kubectl create deploy golang-distroless --image addozhang/golang-distroless-example:latest $ kubectl get po NAME READY STATUS RESTARTS AGE golang-distroless-784bb4875-srmmr 1/1 Running 0 3m2s嘗試進入容器:
$ kubectl exec -it golang-distroless-784bb4875-srmmr -- sh error: Internal error occurred: error executing command in container: failed to exec in 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如何調試 Distroless 容器
1. 使用 distroless debug 鏡像
GoogleContainerTools 為每個 distroless 鏡像都提供了?debug?tag,適合在開發階段進行調試。如何使用?替換容器的 base 鏡像:
FROM golang:1.12 as build-env WORKDIR /go/src/app COPY . /go/src/app RUN go get -d -v ./... RUN go build -o /go/bin/app FROM gcr.io/distroless/base-debian11:debug # use debug tag here COPY --from=build-env /go/bin/app / CMD ["/app"]重新構建鏡像并部署,得益于debug鏡像中提供了 busybox shell 讓我們可以 exec 到容器中。
2. debug 容器與共享進程命名空間
同一個 pod 中可以運行多個容器,通過設置?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?容器,這里為了測試(后面解釋)我們為原容器添加?securityContext.runAsUser: 1000,模擬兩個容器使用不同的 UID 運行:
apiVersion: apps/v1 kind: Deployment metadata:creationTimestamp: nulllabels:app: golang-distrolessname: golang-distroless spec: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 po NAME READY STATUS RESTARTS AGE golang-distroless-85c4896c45-rkjwn 2/2 Running 0 3m12s $ kubectl get po -o json | jq -r '.items[].spec.containers[].name' golang-distroless-example debug然后通過 debug 容器來進入到 pod 中:
$ kubectl exec -it golang-distroless-85c4896c45-rkjwn -c debug -- sh然后在容器中執行:
$ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 14:54 ? 00:00:00 /pause # infra 容器 1000 7 0 0 14:54 ? 00:00:00 /app # 原容器,UID 為 1000 root 19 0 0 14:55 ? 00:00:00 sleep 1d # debug 容器 root 25 0 0 14:55 pts/0 00:00:00 sh root 32 25 0 14:55 pts/0 00:00:00 ps -ef嘗試訪問 進程?7?的進程空間:
$ cat /proc/7/environ $ cat: /proc/7/environ: Permission denied我們需要為?debug?容器加上:
securityContext:capabilities:add:- SYS_PTRACE之后再訪問就正常了:
$ cat /proc/7/environ PATH=/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同樣我們也可以訪問進程的文件系統:
$ cd /proc/7/root $ ls app bin boot dev etc home lib lib64 proc root run sbin sys tmp usr var無需修改容器的基礎鏡像,使用?pod.spec.shareProcessNamespace: true?配合安全配置中增加?SYS_PTRACE?特性,為 debug 容器賦予完整的 shell 訪問來調試應用。但是修改 YAML 和安全配置只適合在測試環境使用,到了生產環境這些都是不允許的。
我們就需要用到?kubectl debug?了。
3. Kubectl debug
針對不同的資源?kubectl debug?可以進行不同操作:
?負載:創建一個正在運行的 Pod 的拷貝,并可以修改部分屬性。比如在拷貝中使用新版本的tag。?負載:為運行中的 Pod 增加一個臨時容器(下面介紹),使用臨時容器中的工具調試,無需重啟 Pod。?節點:在節點上創建一個 Pod 運行在節點的 host 命名空間,可以訪問節點的文件系統。
3.1 臨時容器
從 Kubernetes 1.18 之后開始,可以使用?kubectl?為運行的 pod 添加一個臨時容器。這個命令還處于?alpha?階段,因此需要在“feature gate”[10]中打開。
在使用 k3d 創建 k3s 集群時,打開?EphemeralContainers?feature:
$ k3d cluster create test --k3s-arg "--kube-apiserver-arg=feature-gates=EphemeralContainers=true"@然后創建臨時容器,創建完成后會直接進入容器:
$ kubectl debug golang-distroless-85c4896c45-rkjwn -it --image=ubuntu --image-pull-policy=IfNotPresent #臨時容器 shell $ apt update && apt install -y curl $ curl localhost:8080 Hello world!臨時容器值得注意的是,臨時容器無法與原容器共享進程命名空間:
$ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 02:59 pts/0 00:00:00 bash root 3042 1 0 03:02 pts/0 00:00:00 ps -ef可以通過添加參數?--target=[container]?來將臨時容器掛接到目標容器。這里與?pod.spec.shareProcessNamespace?并不同,進程號為 1 的進程是目標容器的進程,而后者的進程是 infra 容器的進程?/pause:
$ kubectl debug golang-distroless-85c4896c45-rkjwn -it --image=ubuntu --image-pull-policy=IfNotPresent --target=golang-distroless-example注意:目前的版本還不支持刪除臨時容器,參考?issue[11],支持的版本:
3.2 拷貝 Pod 并添加容器
除了添加臨時容器以外,另一種方式就是創建一個 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?調試,并不能為 pod 自動加上?SYS_PTRACE?安全特性,這就意味著如果容器使用的 UID 不一致,就無法訪問進程空間。?截止發文,計劃在?1.23?中支持[12]。
總結
目前上面所有的都不適合在生產環境使用,無法在不修改 Pod 定義的情況下進行調試。
期望 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
往期推薦
什么是RedCup?一文詳解!
谷歌自研Tensor芯片,8核CPU,20核GPU
清華大學:2021元宇宙研究報告
Mendix 發布全球低代碼報告
點分享
點收藏
點點贊
點在看
總結
以上是生活随笔為你收集整理的Kubernetes 上调试 distroless 容器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 倒计时 3 天!1024 程序员节全日程
- 下一篇: Mendix发布全球低代码报告,中国软件