Goland 对容器中的 Go 程序断点远程调试

1,针对 golang 程序打断点有哪几种情况

  • 临时进程:针对临时运行一次的 Golang 脚本,比如定时统计脚本,定时推送脚本。
  • 常驻进程:针对一直在后台运行的 Golang 程序,比如 HTTP 或者 GRPC 服务。

我们现在假设不管是上面的临时进程还是常驻进程都是运行的容器中。(因为不在容器中的大家可以直接在 Goland 中进行断点 debug)

2,临时进程断点 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

Goland 对容器中的 Go 程序断点远程调试_第1张图片

image.png

Goland 对容器中的 Go 程序断点远程调试_第2张图片

image.png

Goland 对容器中的 Go 程序断点远程调试_第3张图片

image.png

Goland 对容器中的 Go 程序断点远程调试_第4张图片

image.png

4,使用 goland 打断点并触发 debug

Goland 对容器中的 Go 程序断点远程调试_第5张图片

image.png

3,常驻进程断点 debug?

如果你只调试一个常驻进程也可以像临时进程进行一样 debug,不过当你有多个微服务用 docker-compose 部署起来以后,一般都是启动 docker 就启动对应的 golang 程序。这个时候要去杀死这个进程再用dlv进行启动,会发现比较麻烦

这里提供一个方便的方法。就是原来的微服务配置全部不动。然后

  • 通过一个 shell 脚本,获取到容器里面运行微服务的pid。
  • 通过 dlv的 attach 操作直接入侵正在运行的goalng程序 同时暴露 40000 端口。
  • 通过 goland 的 go remote 连接暴露的 40000 端口。

细化操作如下: 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

Goland 对容器中的 Go 程序断点远程调试_第6张图片

image.png

以上就是怎么在不停止golang 的常驻进程(比如 HTTP或者 GRPC 的服务)的情况下,对这个 golang 的常驻进程进行 debug。

4,全自动一键 debug(不想折腾可以跳到第五小节)

上面每次停止 debug 或者 代码更新以后都需要再去手动启动一下dlv,但是程序员是最讨厌手动的。所以得想办法解决这两个手动操作。

4.1 解决代码更新自动重启

安装一个工具包: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 代码,当代码发生变动的时候,会自动重启。

4.2 解决每次都要手动启动dlv

配置一下before launch 来每次自动启动dlv 在go remote 里面增加一个 before launch 命令如下图

Goland 对容器中的 Go 程序断点远程调试_第7张图片

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。

Goland 对容器中的 Go 程序断点远程调试_第8张图片

image.png

4.3 整个流程的一个回顾

完成上面的配置,基本就可以做到一键 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配置。

5,Goland 断点 debug 的基础操作教程

最后提供一个 Goland 断点 debug 页面中简单调试会用到的基础按钮的作用,具体可看下图。红色框起来的的几的按钮以及后面对这几个按钮的解释。

Goland 对容器中的 Go 程序断点远程调试_第9张图片

image.png

  • Show Execution Point (Alt + F10):如果你的光标在其它行或其它页面,点击这个按钮可跳转到当前代码执行的行。
  • Step Over (F8):步过,一行一行地往下走,如果这一行上有方法不会进入方法。
  • Step Into (F7):步入,如果当前行有方法,可以进入方法内部,一般用于进入自定义方法内,不会进入官方类库的方法。
  • Force Step Into (Alt + Shift + F7):强制步入,能进入任何方法,查看底层源码的时候可以用这个进入官方类库的方法。
  • Step Out (Shift + F8):步出,从步入的方法内退出到方法调用处,此时方法已执行完毕,只是还没有完成赋值。
  • Run to Cursor (Alt + F9):运行到光标处,你可以将光标定位到你需要查看的那一行,然后使用这个功能,代码会运行至光标行,而不需要打断点。
  • Evaluate Expression (Alt + F8):计算表达式。

另外贴一个 PHP 的 断点 debug 教程,我记得好像很多是 PHP 转 Golang:

以上,基本就是全部的利用 Goland 对 运行在 docker 里面的 goland 程序进行 debug 的配置和基本操作了,创作不易,欢迎点赞收藏,以及如果有疑问或者改进意见,欢迎在评论区留言。

你可能感兴趣的:(Goland,golang,开发语言,后端)