我们现在假设不管是上面的临时进程还是常驻进程都是运行的容器中。(因为不在容器中的大家可以直接在 Goland 中进行断点 debug)
1,首先我们需要在一个 含有 Golang 程序的容器中安装 delve(简称dlv)(debug 工具,类似 PHP 的 xdebug)
第一种安装方式:go install github.com/go-delve/delve/cmd/dlv@latest
第二种安装方式(推荐):针对我们使用的 golang 镜像,进行二次定制,直接将 delve 打包进行,dockerfile 如下
FROM golang:1.20.2 AS builder
WORKDIR /build
# 这里他的 main.go 没有直接在项目主目录,不同于其他 golang 开源项目。
RUN git clone --depth 1 --branch v1.20.2 https://github.com/go-delve/delve.git && \
cd /build/delve/cmd/dlv && \
go build -o /build/delve/bin/dlv
FROM golang:1.20.2
COPY --from=builder /build/delve/bin/dlv /usr/local/bin/dlv
docker buildx build --platform linux/amd64,linux/arm64 -t golang:1.20.2-debug .
备注:使用 FROM golang:1.20.2 AS builder 的语法是 Dockerfile 中的多阶段构建(Multi-stage build)功能,旨在帮助优化镜像的大小和构建过程。
2,在下载好dlv的容器中的程序主目录,运行dlv的命令
dlv debug --headless --listen=:40000 --api-version=2 --accept-multiclient
如下图代表启动成功,并且监听 40000 端口,等待远程的 debug 工具(Goland/vscode等)连接。
image.png
3,配置 goland 连接第二步运行的dlv
image.png
image.png
image.png
image.png
4,使用 goland 打断点并触发 debug
image.png
如果你只调试一个常驻进程也可以像临时进程进行一样 debug,不过当你有多个微服务用 docker-compose 部署起来以后,一般都是启动 docker 就启动对应的 golang 程序。这个时候要去杀死这个进程再用dlv进行启动,会发现比较麻烦。
这里提供一个方便的方法。就是原来的微服务配置全部不动。然后
细化操作如下: 1, 在容器内,编译并运行(或者直接修改 docker-compose 中的 command)一个 golang web 服务 编译并且运行:
go build -gcflags="-N -l" -o demo
./demo
这里先 build 是因为需要添加 -gcflags="-N -l"
这个参数,因为当使用go run命令运行Go代码时,无法直接添加编译器标志(例如-gcflags="-N -l"),而只有加了这个参数才可以避免 golang 在做编译时把一些代码进行忽略,导致goland无法再某一行代码上打断点且会报错:could not find statement at /usr/local/go/src/cmd/ go/main.go:24, please use a line with a statement
2,通过一个shell 脚本(dlv_debug.sh)启动 dlv
#!/bin/bash
# 配置这个程序的名字
#program_name="/data/golang_breakpoint_debug/tmp/main"
program_name="/demo"
# 获取所有程序
program_list=$(ps -ef | grep -v "air" | grep -v "/bin/sh")
# 获取包含 program_name 的程序的PID
go_app_pid=$(echo "$program_list" | awk -v pname="$program_name" '$0 ~ pname {print $2}')
echo "====== get golang program,name:$program_name pid: $go_app_pid ======"
# 运行dlv命令
dlv attach "$go_app_pid" --headless --listen=:40000 --api-version=2 --accept-multiclient
可以在容器内运行上面的 shell 脚本:
image.png
也可以在容器外面执行这个命令:
docker exec -t golang_breakpoint_debug /bin/sh -c "chmod +x /data/golang_breakpoint_debug/dlv_debug.sh && /data/golang_breakpoint_debug/dlv_debug.sh"
3,按照临时程序的debug流程一样配置goland,启动 debug
image.png
以上就是怎么在不停止golang 的常驻进程(比如 HTTP或者 GRPC 的服务)的情况下,对这个 golang 的常驻进程进行 debug。
上面每次停止 debug 或者 代码更新以后都需要再去手动启动一下dlv,但是程序员是最讨厌手动的。所以得想办法解决这两个手动操作。
安装一个工具包:go install http://github.com/cosmtrek/air@latest 将 app 启动命令替换为 air 启动的命令:air --build.cmd 'go build -gcflags="all=-N -l" -o demo' --build.bin="./demo"
备注:这里 air 会监听 golang 代码,当代码发生变动的时候,会自动重启。
配置一下before launch 来每次自动启动dlv 在go remote 里面增加一个 before launch 命令如下图
image.png
dlv_debug_out_start.sh 的 shell 脚本内容如下:
#!/bin/bash
docker exec -t golang_breakpoint_debug /bin/sh -c "chmod +x /data/golang_breakpoint_debug/dlv_debug.sh && /data/golang_breakpoint_debug/dlv_debug.sh" &
sleep 0.5
注意的第一点: 报错:the input device is not a TTY 因为一般docker 命令里面是用的 exec -it ,而如果您在命令中使用了 -t 或 --tty 选项,它会要求终端交互性,并且如果当前的输入设备不是一个 TTY,则会出现该错误。
注意的第二点: shell 脚本中需要 sleep 0.5 ,是因为这个是顺序执行,因为 shell 里面最后加了 & 所以不会阻塞,有可能 shell 运行完了,Goland 启动 debug, 但是shell 那里异步启动还没完成,会导致启动失败。
注意的第三点: 我们配置 go remote 的时候需要配置停止 debug 以后自动停止远程的dlv。
image.png
完成上面的配置,基本就可以做到一键 debug 运行在 docker 中的常驻微服务。最后我们再整体回顾一下,看下这个流程是怎么走通的,假设现在有一个常驻 web 服务大体的流程是
1,docker-compose 启动所有容器,此时容器中有一个 web 服务。
2,这个时候你在 Goalnd里面打好断点,同时点击Goland 中的debug按钮。
3,点击 debug 按钮后,触发 before launch,在宿主机运行 shell 脚本(dlv_debug_out_start.sh)启动 web 服务容器内 shell 脚本(dlv_debug.sh)。
4,web 容器里面的 shell 脚本(dlv_debug.sh)获取正在运行的web服务pid,然后启动dlv劫持这个pid,同时暴露 40000 端口。
5,此时 before launch 运行完毕,Goalnd的debug客户端连接上 web 容器的40000端口,此时可以进行 debug 操作。
6,debug 完毕以后,我们关闭 debug,这个时候自动触发关闭远程dlv
7,我们修改我们需要修改的 Golang 文件,此时触发 air,重启了 web 服务
8,我们再次尝试 debug(循环 3-8)
按照以上的流程,其实我们可以给其他任何语言(PHP/jave)运行在 docker 里面的服务做添加类似的debug配置。
最后提供一个 Goland 断点 debug 页面中简单调试会用到的基础按钮的作用,具体可看下图。红色框起来的的几的按钮以及后面对这几个按钮的解释。
image.png
另外贴一个 PHP 的 断点 debug 教程,我记得好像很多是 PHP 转 Golang:
以上,基本就是全部的利用 Goland 对 运行在 docker 里面的 goland 程序进行 debug 的配置和基本操作了,创作不易,欢迎点赞收藏,以及如果有疑问或者改进意见,欢迎在评论区留言。