在学习开源项目时,很多时候光看代码是搞不清楚实际运行时会走哪个逻辑分支,或者无法推测方法参数值。最简单的办法是 debug,熟悉某个功能的执行流程。
我们通常用 delve 去 debug golang 程序,但是很多云原生项目运行在 k8s 环境里,在本地我们可以用 minikube 来模拟 k8s。两者结合产生新问题:如何 debug minikube 里的 golang 容器?
在 mac 上运行 docker,无论是官方的 Docker Desktop,还是Colima,OrbStack。都是创建了一个 Linux 虚拟机,然后在虚拟机里运行 docker daemon。
minikube 是一个容器,里面运行着另一个 docker daemon,minikube 里 k8s 的 apiServer、controller、scheduler、etcd,就是通过这个 docker daemon 来创建的容器。如果用 minikube 创建了多个 node,那么每个 node 也是一个容器。
minikube start --registry-mirror=https://dockerproxy.net
minikube ssh
5. 查看 k8s control plane 容器
运行在 minikube 里的 pod,就是用 minikube 容器里的 docker daemon 创建的容器。
这和 docker 官方的 docker in docker 容器类似,都是在容器内部运行独立的 docker daemon 进程。还有一种简单方式实现容器内部使用 docker,就是将宿主机的 docker daemon socke 挂载到容器里,然后容器里的 docker cli 调用该 socket 进行创建容器、构建镜像等,常用于 CI/CD 流水线。
在 mac 上只能看到 minikube 的容器,看不到minikube 内部的容器,近一步证实这是不同的 docker daemon 进程。
我们知道容器就是通过 namespace 技术实现资源隔离,通过 cgroup 实现资源限制,归根结底就是宿主机上的一个进程。所以上面的 minikube 容器,minikube 里运行的 pod,以及我们自己用 docker run 的容器,都是虚拟机里的进程。而父namespace 是可以看到子 namespace 的进程,那么在虚拟机里,我们是可以看到上诉所有容器对应的进程。
我这里启动了一个容器,并指定 pid namespace 是宿主机(虚拟机)的。可以看到 pod 容器进程,k8s apiServer 进程,以及两个 docker daemon 进程(一个是虚拟机里的,一个是 minikube 容器里的)。
从上诉可知,运行在 minikube 里,pod 内部的进程,在虚拟机里是能看到的。那我们最开始的问题“如何 debug minikube 里的 golang 容器?”,就变成了“如何 debug 虚拟机里的 golang 进程?”。
如何在虚拟机里运行 golang 的 debug 程序 delve 呢?很简单,启动一个包含 delve 程序,同时设置 host pid namespace 的 docker 容器即可。
一般 golang 程序二进制都是进行编译优化的,例如内联优化,这会导致实际运行的代码行数和本地代码对不上。
重新构建二进制
go build -gcflags="all=-N -l" -o bin/manager main.go
准备 Dockerfile
FROM openkruise/kruise-manager:test
COPY bin/manager .
构建镜像
docker build -t openkruise/kruise-manager:test -f Dockerfile .
替换 minikube 里的镜像
minikube loadimage openkruise/kruise-manager:test
执行前需要将这个镜像的容器删掉。
略
首先找到 kruise manager 的 pid。
然后使用 delve 容器进行 attach。
最后使用 goland 的 remote debug 连接 delve 即可。
以上方法同样适用于调试 k8s 控制面,调试 containerd 等 golang 程序。
Post Views: 8