社区编辑申请
注册/登录
如何对Pod容器进行Remote Debug
云计算 云原生
先不谈通过看log来debug的效率问题,在 VM 上这样搞尚且可行,可当我们把应用容器化并让K8s管理后,怎么办呢?

大家好,我是二哥。

在一个面试场景中,就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。

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

K8s port-forward

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

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

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

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

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

图 2:使用K8s port-forward

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

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

图 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。

图 4:本机debugger

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

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

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

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

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

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

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

图 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了。
责任编辑:姜华 来源: 二哥聊云原生
相关推荐

2022-05-26 07:33:48

Pod容器debug

2022-06-01 09:38:36

KubernetesPod容器

2022-06-06 14:35:59

KubevirtKubernetes虚拟机

2022-05-30 09:32:07

Spring容器

2022-06-03 09:41:03

DockerKubernetes容器

2022-06-28 12:35:21

DockerPython

2022-05-10 11:12:09

容器容器安全

2022-05-31 09:42:05

容器安全云原生安全

2022-06-14 11:01:48

SpringBootTomcatUndertow

2022-06-10 07:45:09

CentOS国产操作系统

2022-06-06 09:02:47

Overlay2BindISO

2022-06-15 10:30:07

数据中心5G蜂窝网络

2022-06-07 14:38:40

云原生架构云计算

2022-05-17 11:27:17

容器数据存储

2022-03-23 15:39:39

容器安全云平台

2022-06-22 05:53:49

城域网广域网VXLAN

2022-05-26 15:02:35

Docker容器云原生

2022-03-10 08:24:17

Docker容器SaaS

2022-05-23 10:45:34

DAYU200鸿蒙

2022-06-02 07:13:12

Python3.11编程语言

同话题下的热门内容

首个全链路国产操作系统要诞生了,OpenCloudOS首度披露技术路线监控Kubernetes的最佳实践、工具和方法探讨一下云原生带来的收益和陷阱平台即代码的未来是Kubernetes扩展过去五年,PolarDB云原生数据库是如何进行性能优化的?做大做强云计算市场须立足实际2022 年会是您采用多云的一年吗?Kubernetes 资源拓扑感知调度优化

编辑推荐

Service Mesh真的是云原生应用的绝配吗云原生桌面:虚拟桌面的解构与重新定义解密云原生---看企业云的未来云原生技术及其未来发展趋势展望如何评估云原生NFV中的容器化VNF部署
我收藏的内容
点赞
收藏

51CTO技术栈公众号