11

如何对Pod容器进行Remote Debug

 2 years ago
source link: https://www.51cto.com/article/707406.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何对Pod容器进行Remote Debug-51CTO.COM

如何对Pod容器进行Remote Debug
作者:LanceZhang 2022-04-26 05:55:13
先不谈通过看log来debug的效率问题,在 VM 上这样搞尚且可行,可当我们把应用容器化并让K8s管理后,怎么办呢?

576751b7376fea3f081153c6d1f51afa2d5758.png

大家好,我是二哥。

在一个面试场景中,就debug问题,一般会出现下面的对话:

二哥:你平时开发的时候是用什么方法debug ?

应聘者:看日志。

二哥:万一log level没设对或者关键的地方没有加log怎么办呢?

应聘者:那就改代码,加log,重启服务,然后继续看日志。

先不谈通过看log来debug的效率问题,在 VM 上这样搞尚且可行,可当我们把应用容器化并让K8s管理后,怎么办呢?

我们都知道在Pod里是没法方便地通过执行类似 systemctl和 monit等命令来重启应用的,那继续用看日志的方式的话,就剩下一条路了:

  1. 改代码,加log。
  2. commit到git。
  3. CI/CD。
  4. 如果log没有加对,或者想看一下某一个函数调用的返回值,那从步骤1开始重头再来。

um, 看上去挺累的样子。CI/CD和K8s也被折腾得够呛。

二哥稍微有点强迫症,不能忍受这么折磨人的debug方式。另外,相比人肉看Log,通过调试器的方式来debug更优雅、更快捷,也更能激发RD的想象力。最重要的是,通过调试器debug会倒逼RD从代码调用逻辑、和OS交互等多角度思考问题。比如会设断点不难,难的是何时设断点,把断点设在哪里最合适。

“道—法—术—器—势”,是老子《道德经》的精髓思想。本文讲的其实是“术”和“器”,但二哥想说“道”更本质,也更重要,它是核心思想、理念、本质规律。强烈建议好奇心重的同学多思考一下这些“术”背后的实现原理。

二哥通过一个示例给老铁们演示一下,如何从本地机器远程调试Pod里面的应用。应用本身非常简单,是用Node.js写的一段http server。对于其它语言写的应用,你肯定能找到变通方法。

进入debug模式

首先得把http server切换到调试模式。注意这里demo的方法仅适用于Node.js。

kubectl exec nodejs-8448d4cbc6-nbjwd -n lancehbzhang -- /bin/bash -c "kill -USR1 1"

一切顺利的话,你可以从Pod的log里面看到如下所示的信息。这表示debugger侦听在端口9229。

c669f984259dab1e8c2860312de41c95fe05e0.gif38d5f74899d0283a1a35631d152a20227067a8.png

图 1:将容器切换进入debug模式

K8s port-forward

下面的问题是:如何才能把本地debugger发出的调试命令连进来?

方法其实有不少。比如通过一个Load Balancer类型的service。不过这种方法比较费钱,据我所知,腾讯云的Load Balancer价格不菲。

这里二哥介绍一个既免费又通用的方法。用K8s自带的port-forward功能,命令如下所示:

$ kubectl port-forward deploy/nodejs -n lancehbzhang 9229:9229

在一台可以执行kubectl命令的机器上执行这行命令后,如果一切正常,你会看到下面的界面。

d52f02a9912fc545dd4598f64381d8394b0ed7.gif694bde3146d3ebec27368673fadea9ad1dcdf9.png

图 2:使用K8s port-forward

恭喜你,这表示从此以后任何发往这台机器 9229 端口的请求都将会 forward 到 pod nodejs 的 9229 端口,如你所猜,那正是 debugger 正在侦听的端口。

到现在为止,下图中的 ③ 和 ④ 你应该都准备好了。

46b507c319edd655c468303a0e6e36ad9b7428.png

图 3:从本机debugger到远程debuggee全景图

你是不是摩拳擦掌,撸起袖子准备从本地机器连过来了?且慢,有一种场景我们还没解决。

如果执行 kubectl port-forward 的机器和我们的本地机器无法直连怎么办?假如出于安全考虑,上图中 ③ 和 ④ 是可以网络直连的,但 ① 和 ③ 被防火墙隔开了,只留了一个22端口供 ① 通过 ssh 登录到 ③ 。这种情况下,该如何从本机连接到 ④ 上的debugger呢?

这个时候就需要轮到步骤 ② 所示的 SSH Tunnel 登场了。通过这样的方式, 本机VS code只需 attach 到 127.0.0.1:9229,诸如设置断点、单步执行、查看变量等调试命令都被封装起来,塞进 SSH Tunnel 再送至 ③ 上,然后再通过 port-forwarding 转至 ④ 上的debuggee。

注:SSH Tunnel的使用并非本文的重点,大家可以自行谷歌找到使用方法。

好了,准备工作做完了。下面开始二哥的表演。

本地机器打开VS Code,在launch.json里面输入如下所示的配置。其中参数 port表示本机debugger需要连接的端口,localRoot表示本地的代码路径,而remoteRoot则表示 ④ 中应用所在的路径。二哥在build Docker image时,将应用的WORKDIR设置为了/myapp,所以这里也得填成/myapp。其它参数各位自行谷歌。

{
   // Use IntelliSense to learn about possible attributes.
   // Hover to view descriptions of existing attributes.
   // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
   "version": "0.2.0",
   "configurations": [
      {
           "name": "Attach-2-nodejs",
           "port": 9229,
           "request": "attach",
           "skipFiles": ["<node_internals>/**"],
           "type": "pwa-node",
           "localRoot": "${workspaceFolder}",
           "remoteRoot": "/myapp",
           "sourceMaps": true
      }
  ]
}

在第17行设置断点,按下F5开始debugging。

0272a1e98660cd3ca21877d007c313bf60d607.gifd3babca94bec42207bb45454970efa893a4824.png

图 4:本机debugger

还记得前文我们已经打开的 SSH Tunnel 界面吗?这个时候,你会看到它会打印出一些诸如 "Successfully established connection 127.0.0.1:9229 -> 127.0.0.1:9229" 这样的信息。当然,具体信息内容与你使用的工具相关。

a62d42473acd62b45259185216af237775c237.gif910ffa0048988301bde342ed83b42e329957fa.png

图 5:SSH Tunnel正在工作示意图

没有问题的话,网络包应该来到了图3中位置 ③ 。我们来看看这个时候 K8s port-forward 会打印出什么来:

37ea5350043c5698ecd724e4c14aaf4ff80a71.gifd4dd234729c316fccc5768ab489420a32103c9.png

图 6:K8s port-forward正在工作示意图

非常不错,看起来它收到了请求,并且也在勤奋地工作着。那最后我们来看看图3中 ④ 中打印出来的令人激动的信息:"Debugger attached"。

589663653039ecf04d7950d6f9f70b91dfa08f.gif49fa1ae13f7e6f4db10929c648d65b48b14428.png

图 7:debuggee显示已有debugger attach上来了

万事俱备,只差最后一脚了:发个请求,看看能不能命中断点:

66d706e84daf1b8b7ef771ad31c25893a5b494.gif666d5364232339566b784043e8a3a776419c96.png

图 8:发个请求,命中一下断点

回头看看图4吧,多么让人陶醉的界面,在那里你可以查看变量、栈回溯,还可以干很多很多其它骚操作。是的,这个时候才是发挥你想象力的时候。

其它需要做的工作

到目前为止,本文略过一些虽不是重点,但有的时候又可能会影响调试体验的细枝末节,我列举一二。

将Pod的replica设置为 1。不然你就得发了疯地寻找debugger发出的调试命令发到哪里去了呢?

还记得K8s的livenessProbe和readinessProbe吗?如果容器内应用因为被调试而长时间未响应这两个probe,那么Pod有可能会被K8s杀掉。这个时候,或许你费劲千辛万苦才等来的断点命中瞬间化为乌有了。

网上有不少解决方法,比如通过 kubectl patch deploy/nodejs 安装dummy的livenessProbe和readinessProbe。

这个dummy probe不需要真的去probe container是否活着,相反它永远返回 true。比如下面这种方法用 kubectl patch 命令修改了 deployment 的spec。

# 移除 livenessProbe
$ kubectl patch deploy/nodejs -n lancehbzhang --type json -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/livenessProbe"}]'
# 安装 dummy livenessProbe
$ kubectl patch deploy/nodejs -n lancehbzhang -p '{"spec": {"template": {"spec": {"containers": [{"name": "nodejs", "livenessProbe": {"initialDelaySeconds": 5, "periodSeconds": 5, "exec": {"command": ["true"]}}}]}}}}'

首先需要将容器内的应用切换到debug模式。具体如何操作与所使用的语言密切相关。

  • 通过K8s port-forward可以将debugger发出的调试命令转发至被调试应用(debuggee)。
  • 如果运行于你本机的debugger无法和运行着K8s port-forward的那台机器直接通信,那么这个时候就需要把debugger的调试命令丢进SSH Tunnel送至对端。
  • 一切准备就绪后,本机debugger就可以attach到debuggee了。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK