0%

使用vscode远程调试golang

在学习一些大型golang项目时,例如kubernetes,通过调试器一边跟踪代码一边学习其原理是很好的方法,一般k8s部署在远程环境,在本机进行开发,很自然对远程调试有迫切需求,这也是和调试一般golang程序最主要的诉求区别点,本文重点介绍远程调试这种场景。

vscode远程调试

vscode的成功很重要的一点是进程隔离的插件设计,构建出了丰富的生态环境,而vscode自身保持轻量并提供稳定流畅的编辑体验。

vscode实现远程调试有两种方法:

第一种是网络上搜索出来最多的,通过dlv的远程调试功能,在服务端启动dlv server,客户端vscode连接dlv server

1571736202433

这种方法存在明显缺陷:

  • 只能attach调试

  • 浏览的代码是本地的,远程修改后要同步到本地

第二种是使用vscode的远程开发功能,该功能最早是2019.5月发布,当时只在insider版本包含,后续stable版是从1.35.1开始包含。这个功能也是通过插件实现的,有Remote-SSH、Remote-WSL、Remote-Containers三个插件,适应三种不同的开发环境,名字一目了然

1571736217446

1571736238824

1571736248588

这种方法整个开发环境就在远程主机上,本地机器上只是UI,它能实现的不只是调试,而是整个开发过程,所以这个功能叫远程开发(Remote Development)

  • attach或launch都可以,和本地调试体验一致
  • attach时mode有local和remote两种,前者vscode自动dlv attach,后者手工运行dlv attach,一般在被调试的进程没有权限attach时使用
  • 本地不需要源代码,直接修改远程主机源代码

第二种方法无疑是当前最好的选择。当然无论采用哪种方法,最终调试其实都是通过dlv。

vscode Remote-SSH使用方法

下面详细介绍一下Remote-SSH的使用方法,vscode请使用1.35及以上版本。

  1. 建立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文件中,客户端使用密钥文件登陆。

  2. 安装Remote - SSH插件

  3. 添加Host定义,编辑%USERPROFILE%/.ssh/config文件,添加

    1
    2
    3
    4
    Host k8s_master
    HostName 10.10.10.14
    User vagrant
    IdentityFile D:\k8s\.vagrant\machines\master\virtualbox\private_key

    或者

    1
    2
    3
    4
    5
    Host k8s_master
    HostName 127.0.0.1
    User vagrant
    IdentityFile D:\k8s\.vagrant\machines\master\virtualbox\private_key
    Port 2222

    k8s_master是host的别名,后续ssh客户端可以直接用别名连接,不用再指定User,HostName和IdentityFile等参数,例如:

    1
    ssh k8s_master
  1. vscode连接远程主机,点击左下角,弹出命令中选择Connect to Host,选择上述定义的Host

    1571816298579

  2. 后续打开文件或文件夹就都是远程主机上的,和操作本地文件是一样的

编译源码

在远程主机搭建好完整的golang开发环境,虽然vscode好像运行在Local Host,但实际上整个开发环境都是用的Remote Host上的。

对于大型的golang程序,编译慢是一个头疼的问题,这里针对优化编译速度给出一些建议:

  1. 启用go module,并设置一个速度快的GOPROXY,例如https://goproxy.cn
  2. 先执行go install,将编译结果缓存在全局缓存,后续go build时源码没有变化将直接使用全局缓存中内容
  3. 使用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环境:

1571883694639

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,如果快捷键和系统冲突,需要设置其他快捷键)调出

    1571885343179

vscode内置了一些代码片段可以满足大部分常用场景,也是通过智能提示调出。

1571883950893

Attach to local process

1
2
3
4
5
6
7
{
"name": "Launch file",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${file}"
}

Connect to server

1
2
3
4
5
6
7
8
9
{
"name": "Connect to server",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "${workspaceFolder}",
"port": 2345,
"host": "127.0.0.1"
}

Launch file

1
2
3
4
5
6
7
{
"name": "Launch file",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${file}"
}

Launch package

1
2
3
4
5
6
7
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}"
}

Launch test function

1
2
3
4
5
6
7
8
9
10
11
{
"name": "Launch test function",
"type": "go",
"request": "launch",
"mode": "test",
"program": "${workspaceFolder}",
"args": [
"-test.run",
"MyTestFunction"
]
}

Launch test package

1
2
3
4
5
6
7
{
"name": "Launch test package",
"type": "go",
"request": "launch",
"mode": "test",
"program": "${workspaceFolder}"
}

如上所示,其中用到几个环境变量:

  • 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}
-------------本文结束感谢您的阅读-------------