在学习一些大型golang项目时,例如kubernetes,通过调试器一边跟踪代码一边学习其原理是很好的方法,一般k8s部署在远程环境,在本机进行开发,很自然对远程调试有迫切需求,这也是和调试一般golang程序最主要的诉求区别点,本文重点介绍远程调试这种场景。
vscode远程调试
vscode的成功很重要的一点是进程隔离的插件设计,构建出了丰富的生态环境,而vscode自身保持轻量并提供稳定流畅的编辑体验。
vscode实现远程调试有两种方法:
第一种是网络上搜索出来最多的,通过dlv的远程调试功能,在服务端启动dlv server,客户端vscode连接dlv server
这种方法存在明显缺陷:
-
只能attach调试
-
浏览的代码是本地的,远程修改后要同步到本地
第二种是使用vscode的远程开发功能,该功能最早是2019.5月发布,当时只在insider版本包含,后续stable版是从1.35.1开始包含。这个功能也是通过插件实现的,有Remote-SSH、Remote-WSL、Remote-Containers三个插件,适应三种不同的开发环境,名字一目了然
这种方法整个开发环境就在远程主机上,本地机器上只是UI,它能实现的不只是调试,而是整个开发过程,所以这个功能叫远程开发(Remote Development)
- attach或launch都可以,和本地调试体验一致
- attach时mode有local和remote两种,前者vscode自动dlv attach,后者手工运行dlv attach,一般在被调试的进程没有权限attach时使用
- 本地不需要源代码,直接修改远程主机源代码
第二种方法无疑是当前最好的选择。当然无论采用哪种方法,最终调试其实都是通过dlv。
vscode Remote-SSH使用方法
下面详细介绍一下Remote-SSH的使用方法,vscode请使用1.35及以上版本。
-
建立Local Host到Remote Host的SSH通道
如果使用vagrant,它已经为我们配置好,运行vagrant ssh-config会显示ssh的端口、密钥文件等信息。
$ vagrant ssh-config
Host master
HostName 127.0.0.1
User vagrant
Port 2222
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile D:/k8s/.vagrant/machines/master/virtualbox/private_key
IdentitiesOnly yes
LogLevel FATAL验证ssh登陆:
1
ssh -i D:/k8s/.vagrant/machines/master/virtualbox/private_key -p 2222 vagrant@localhost
如果可以连通虚拟机ip,也可以直接:
1
ssh -i D:/k8s/.vagrant/machines/master/virtualbox/private_key vagrant@ip
如果自己配置密钥登陆,需要使用ssh-keygen生成公钥和密钥,将公钥内容写入服务端的authorized_keys文件中,客户端使用密钥文件登陆。
-
安装Remote - SSH插件
-
添加Host定义,编辑%USERPROFILE%/.ssh/config文件,添加
1
2
3
4Host k8s_master
HostName 10.10.10.14
User vagrant
IdentityFile D:\k8s\.vagrant\machines\master\virtualbox\private_key或者
1
2
3
4
5Host k8s_master
HostName 127.0.0.1
User vagrant
IdentityFile D:\k8s\.vagrant\machines\master\virtualbox\private_key
Port 2222k8s_master是host的别名,后续ssh客户端可以直接用别名连接,不用再指定User,HostName和IdentityFile等参数,例如:
1
ssh k8s_master
-
vscode连接远程主机,点击左下角,弹出命令中选择Connect to Host,选择上述定义的Host
-
后续打开文件或文件夹就都是远程主机上的,和操作本地文件是一样的
编译源码
在远程主机搭建好完整的golang开发环境,虽然vscode好像运行在Local Host,但实际上整个开发环境都是用的Remote Host上的。
对于大型的golang程序,编译慢是一个头疼的问题,这里针对优化编译速度给出一些建议:
- 启用go module,并设置一个速度快的GOPROXY,例如https://goproxy.cn
- 先执行go install,将编译结果缓存在全局缓存,后续go build时源码没有变化将直接使用全局缓存中内容
- 使用go build -p指定编译命令并行数
要调试golang程序,还需要加上“-N -l"编译参数,上述go install/go build都要加上。
-N disable optimizations
-l disable inlining
调试
vscode进行调试主要是设置launch.json文件,切换到debug视图,点击Add Configuration…,如果当前打开过go文件,会自动生成launch.json,否则需要选择go环境:
launch.json的configurations字段是一个数组,可以添加多个配置,通过request和mode的组合用于不同的调试场景,包括以下组合:
request | mode | program | remotePath | processId | args |
---|---|---|---|---|---|
launch | debug、test(也可用auto) | 源文件或包绝对路径 | / | / | 命令行参数列表 |
launch | exec(也可用auto) | 编译好的二进制程序绝对路径 | / | / | 同上 |
launch | remote(废除,使用attach) | / | / | / | / |
attach | local | / | / | 进程ID | / |
attach | remote | / | 被调试源码文件绝对路径 | / | / |
特别说明一下
-
当mode为auto时自动根据program判断是哪种情况
-
attach+remote这种情况,remotePath非常重要,例如设置在workspaceFolder/path/to/debuged_file的断点,dlv服务端是打在remotePath/path/to/debuged_file上的,一旦设置不对就会导致断点断不到,并会导致dlv服务出问题(见最后说明)
-
不是所有字段在每种场景都需要,/表示这种情况不需要
-
上述表格只列出了主要的一些字段,每种场景支持哪些字段,可以通过智能提示(默认快捷键Ctrl+Space,如果快捷键和系统冲突,需要设置其他快捷键)调出
vscode内置了一些代码片段可以满足大部分常用场景,也是通过智能提示调出。
Attach to local process
1 | { |
Connect to server
1 | { |
Launch file
1 | { |
Launch package
1 | { |
Launch test function
1 | { |
Launch test package
1 | { |
如上所示,其中用到几个环境变量:
-
file:当前文件绝对路径
-
workspaceFolder:工作区目录绝对路径
-
fileDirname:当前文件所在目录绝对路径
dlv存在的问题
[vagrant@localhost ~]$ dlv version
Delve Debugger
Version: 1.3.0
Build: $Id: 2f59bfc686d60989dcef9de40b480d0a34aa2fa5 $
dlv在使用中存在一些不是特别方便的地方:
- attach+local时,vscode退出调试后被调试进程也退出,无法实现deattach的效果
- attach+remote时,vscode退出调试后dlv服务端也退出
- attach+remote时,如果remotePath设置不对,dlv服务端就无法正常退出也无法再正常使用(可能是一个Bug),只能kill -9杀掉,如果dlv不杀掉,被调试进程也无法退出,这时如果强行kill掉被调试进程,它就会变成僵尸进程,如果恰好被调试进程是systemd启动的服务,那么要消除僵尸进程就只有重启机器了
- vscode server是运行在ssh所使用的用户下的,如果被调试的进程当前用户没有权限是无法使用attach+local模式的,这时只有用attach+remote模式,手工启动dlv attach,remotePath设为${workspaceFolder}