kubelet 遠程調試方法

  1. kubelet 啓動命令分析

kubelet 是一個 systemd 服務,以使用 Kubeadm 工具安裝的 v1.23.4 k8s 集羣爲例,該服務的配置文件路徑爲/etc/systemd/system/kubelet.service.d/10-kubeadm.conf, 內容如下:

# Note: This dropin only works with kubeadm and kubelet v1.11+
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
EnvironmentFile=-/etc/default/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS

以我的測試環境爲例,執行ps -ef |grep /usr/bin/kubelet, 可見 kubelet 啓動的完整命令如下:

/usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --container-runtime-endpoint=/run/containerd/containerd.sock --pod-infra-container-image=k8s.gcr.io/pause:3.6

如果需要修改 kubelet 命令,可以關閉服務後使用相同參數啓動。或修改 systemd 配置文件後重啓 kubelet 服務。

  1. 編譯 kubelet

根據 k8s makefile 源碼分析 [1],kubelet 編譯命令如下:

https://github.com/kubernetes/kubernetes/blob/v1.22.4/hack/lib/golang.sh#L679

kube::golang::build_some_binaries() {
    ...
    go install "${build_args[@]}" "$@"
    ...
}

其中 GOLDFLAGS, GOGCFLAGS 配置如下:

https://github.com/kubernetes/kubernetes/blob/v1.22.4/hack/lib/golang.sh#L797-L799

kube::golang::build_binaries() {
    ...
    goldflags="${GOLDFLAGS=-s -w} $(kube::version::ldflags)"
    goasmflags="-trimpath=${KUBE_ROOT}"
    gogcflags="${GOGCFLAGS:-} -trimpath=${KUBE_ROOT}"
    ...
}

爲了保留儘可能多的調試信息,我們需要重新設置這兩個編譯參數,所以編譯 kubelet 的命令應爲

git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
git checkout v1.22.4
./build/shell.sh
make generated_files
make -o generated_files kubelet KUBE_BUILD_PLATFORMS=linux/amd64 GOLDFLAGS="" GOGCFLAGS="all=-N -l"

編譯完成後,kubelet 二進制文件位於_output/bin/kubelet

  1. delve 介紹

delve[2] 是一個用於 Go 編程語言的調試器。儘管我們也可以使用 gdb 調試 go 語言程序 [3], 但在調試用標準工具鏈構建的 Go 程序時,Delve 是 GDB 更好的替代品。它比 GDB 更能理解 Go 的運行時、數據結構和表達式。

可以使用如下命令安裝 dlv:

go install github.com/go-delve/delve/cmd/dlv@latest

使用如下命令使用 dlv 進行調試:

dlv exec ./hello -- server --config conf/config.toml

以 kubelet 爲例,使用 dlv 命令行調試的過程如下:

root@st0n3-host:~# dlv exec /usr/bin/kubelet -- --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --container-runtime-endpoint=/run/containerd/containerd.sock --pod-infra-container-image=k8s.gcr.io/pause:3.6
Type 'help' for list of commands.
(dlv) b main.main
Breakpoint 1 set at 0x502e086 for main.main() _output/dockerized./cmd/kubelet/kubelet.go:39
(dlv) c
> main.main() _output/dockerized./cmd/kubelet/kubelet.go:39 (hits goroutine(1):1 total:1) (PC: 0x502e086)
    34:  _ "k8s.io/component-base/metrics/prometheus/restclient"
    35:  _ "k8s.io/component-base/metrics/prometheus/version" // for version metric registration
    36:  "k8s.io/kubernetes/cmd/kubelet/app"
    37: )
    38: 
=>  39: func main() {
    40:  command := app.NewKubeletCommand()
    41: 
    42:  // kubelet uses a config file and does its own special
    43:  // parsing of flags and that config file. It initializes
    44:  // logging after it is done with that. Therefore it does
(dlv)
  1. GoLand 遠程調試 kubelet

我們當然可以使用上文描述的命令行形式進行調試,但 kubernetes 代碼量巨大,使用 IDE 會更方便。

點擊調試按鈕左側的 Edit Configurations 按鈕,配置 dlv 的地址和端口:

使用 IDE 提示的命令啓動 kubelet,或將其配置到 systemd 服務中後重啓服務:

root@st0n3-host:~# cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 
...
ExecStart=/usr/bin/dlv --listen=:10086 --headless=true --api-version=2 --accept-multiclient exec /usr/bin/kubelet -- $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
root@st0n3-host:~# systemctl daemon-reload
root@st0n3-host:~# systemctl restart kubelet.service

此時 kubelet 命令實際還未真正啓動,在 GoLand 中運行剛剛添加的配置,連接上 dlv 後,kubelet 纔會運行。

下好斷點,點擊 debug 按鈕,我們就可以在 IDE 中對 kubelet 進行調試了。

  1. 其他容器軟件調試命令

5.1 runc

編譯

make shell
make EXTRA_FLAGS='-gcflags="all=-N -l"'

調試

mv /usr/bin/runc /usr/bin/runc.bak
cat <<EOF > /usr/bin/runc
#!/bin/bash
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec /usr/bin/runc.debug -- $*
chmod +x /usr/bin/runc

5.2 docker-cli

編譯

修改scripts/build/binary中的編譯命令如下:刪除 LDFLAGS,添加 gcflags

root@st0n3:~/cli# git diff
diff --git a/scripts/build/binary b/scripts/build/binary
index e4c5e12a6b..155528e501 100755
--- a/scripts/build/binary
+++ b/scripts/build/binary
@@ -74,7 +74,7 @@ fi
 echo "Building $GO_LINKMODE $(basename "${TARGET}")"
 
 export GO111MODULE=auto
-
-go build -o "${TARGET}" -tags "${GO_BUILDTAGS}" --ldflags "${LDFLAGS}" ${GO_BUILDMODE} "${SOURCE}"
+go build -o "${TARGET}" -tags "${GO_BUILDTAGS}" -gcflags="all=-N -l" ${GO_BUILDMODE} "${SOURCE}"
 
 ln -sf "$(basename "${TARGET}")" "$(dirname "${TARGET}")/docker"
make -f docker.Makefile shell
make binary

調試

cat <<EOF > docker.debug
#!/bin/bash
dlv --listen=:2344 --headless=true --api-version=2 --accept-multiclient exec ./docker-cli.debug -- $*
chmod +x docker.debug

5.3 dockerd

編譯

修改hack/make/.binary文件中的編譯命令

root@st0n3:~/moby# git diff
diff --git a/hack/make/.binary b/hack/make/.binary
index d56e3f3126..3e23865c81 100644
--- a/hack/make/.binary
+++ b/hack/make/.binary
@@ -81,11 +81,11 @@ hash_files() {
 
        echo "Building: $DEST/$BINARY_FULLNAME"
        echo "GOOS=\"${GOOS}\" GOARCH=\"${GOARCH}\" GOARM=\"${GOARM}\""
-       go build \
+       set -x
+       go build -gcflags "all=-N -l"  \
                -o "$DEST/$BINARY_FULLNAME" \
                "${BUILDFLAGS[@]}" \
                -ldflags "
-               $LDFLAGS
                $LDFLAGS_STATIC_DOCKER
                $DOCKER_LDFLAGS
        " \
make BIND_DIR=. shell
hack/make.sh binary

調試

/root/go/bin/dlv --listen=:2343 --headless=true --api-version=2 --accept-multiclient exec /usr/bin/dockerd.debug -- -D -H unix:///var/run/docker.sock --containerd=/run/containerd/containerd.sock

原文地址:https://ssst0n3.github.io/post/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/%E5%AE%B9%E5%99%A8%E5%AE%89%E5%85%A8/%E5%AE%B9%E5%99%A8%E9%9B%86%E7%BE%A4%E5%AE%89%E5%85%A8/k8s/%E6%BA%90%E7%A0%81%E5%AE%A1%E8%AE%A1/%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91%E5%B9%B6%E7%BC%96%E8%AF%91%E4%BB%A3%E7%A0%81/kubelet-%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95.html

參考資料

[1]

k8s makefile 源碼分析: https://ssst0n3.github.io/post / 網絡安全 / 安全研究 / 容器安全 / 容器集羣安全 / k8s / 源碼審計 / 如何開發並編譯代碼 / k8s-makefile - 源碼分析. html

[2]

delve: https://github.com/go-delve/delve

[3]

儘管我們也可以使用 gdb 調試 go 語言程序: https://go.dev/doc/gdb

k8s 技術圈 專注容器、專注 kubernetes 技術......

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