在解决问题之前首先要正确的定义问题

多年以前,我阅读了一本至今都让我受益匪浅的书:《你的灯亮着吗?》。工作中,我总会按照书中的内容来训练自己解决工作中遇到的问题。作为一名测试开发工程师,在定位 BUG 时,我也会用到该书中介绍的问题解决思维模式。

在这本书的第一章「这是谁的问题」中提到:

初出茅庐的问题解决者总是在还没有定义好问题的时候就仓促的给出解决方案。迫于外界环境的压力,经验丰富的问题解决者有时也耐不住性子。在这种情况下,尽管他们能找到很多方法来解决问题,但是不一定对症。

我们一直在孜孜不倦的追求解决问题的方法、策略、妙招,却从来没有仔细想过,问题究竟是什么?

缘起

工作中,经常会自己定位或者帮助同事定位各种各样的 BUG。有时候,BUG 会比较简单,而有时候遇到的 BUG 则相对复杂。在帮助同事定位 BUG 的时候,经常会遇到同事描述的问题和最后实际定位之后的问题并不一致的情况。

在问题定位中,我们要:大胆假设,小心求证。但是,我发现,好多人只做到了大胆假设,在定位 BUG 时,大胆假设了一个问题,然后在定位过程中就把这个大胆假设的问题当成了真正的问题,结果就是在定位 BUG 的道路上越走越迷糊……

每次说到这个的时候,大家普遍反馈:道理是这个道理,理论我们也都懂呀,但是就是做不到呀,怎么办呢?

“鉴于往事,有资于治道”。

为了能够让大家有比较真实的体验,我准备将自己工作中遇到的类似的问题进行梳理。在梳理的过程中,由于各种原因,我无法将原始问题涉及到的内容原样呈现,我需要对原始的问题进行一定的抽象。但是,对问题抽象并不会对问题的原貌和本质进行修改,并力求做到尽量保证问题的原样呈现。

1. 为什么容器不能暴露服务的URI呢?

问题背景

我们的 CI/CD 服务类似 GitHub Actions,当然和 GitHub Actions 的差异非常大(例如整个任务的编排机制、容器的底层机制、容器的托管机制……),但是使用起来是类似的。

每当有对应的事件发生时(例如代码提交、代码合并等),就会触发一些列的自动化任务,例如代码编译、代码扫描、运行单元测试……。在执行这些自动化任务的时候,会首先分配一个容器(类似 GitHub-hosted runners),然后在该容器中执行在平台上配置好的任务。

有一天,我们团队需要一个功能:开放容器中部署的 HTTP 服务,以便其他的容器可以通过 URL 来访问这个 HTTP 服务,因为这样的话就可以避免这个 HTTP 服务的多次部署(部署一次,然后供其他的多个任务来使用),从而提升效率。

我安排了一个同事来解决这个问题。同事告诉我说,从平台的配置上看,是有暴露容器PORT的功能的,但是这个功能目前没有实现,所以需要推动平台方实现这个功能。

问题的变化

不知道大家发现没有,到目前为止,这个案例中的问题已经发生了变化。问题从在其他容器中访问另外容器中的 HTTP 服务     \implies CI/CD 平台方需要提供暴露容器 PORT 的能力

这个问题的转化看起来是多么的自然而又正常。接下来,为了实现我们的目标,我们和平台方组织了一个技术讨论会,专门讨论平台方何时可以实现暴露容器 PORT的功能。

经过和平台方的讨论,因为涉及到端口分配、端口冲突等端口管理问题,暴露容器 PORT这个功能短时间内无法实现。

讨论之后,我们发现,我们需要的功能看起来是无法实现了。不是吗?

回归最原始的问题

在技术讨论的最后,我请教了平台方技术同学我们那个最原始的问题:如果我们想要在其他容器中访问另外容器中的 HTTP 服务,有什么可行的方案吗?

直到这个时候,我们才知道,原来我们容器中使用的 PORT 实际上就是物理机上的 PORT。我们可以直接时候用所分配的容器的 IP 和容器中启动的 HTTP 服务的端口来直接访问这个容器内的服务。

这时,我的同事说,之前用 ifconfig 命令拿到的 IP 和容器中启动的 HTTP 服务的 PORT 测试过,是没办法访问容器内的服务的。

到这里,我想我大概知道问题究竟出在哪里了。ifconfig 获取的是宿主机的网卡 IP 地址,而对于服务器而言,一般会有多个网卡,因此该命令会得到多个 IP 地址。而对于容器的 IP 地址,我们需要使用特定的指令才能够拿到正确的容器 IP。

最后,我们按照平台方的技术同学提供的方法完美的实现了我们所要的功能。

从这个案例中,发现了什么?

  • 错把假设当证据。在定位 BUG,尤其是一些比较复杂的 BUG,往往需要提出各种假设,并通过逻辑推理、寻找数据证据对其进行检验,然后再根据检验的结果提出新的假设,再对这些新假设逐一验证,不断迭代这个过程以逐步靠近 BUG 的真相。但是,提出假设时,可能已经忘了那仅仅是个假设而已,便在这个假设的基础上展开分析,最后得到一个所谓的“结论”。我的同事一开始利用 ifconfig 获取到的 IP 无法访问到容器内部的 HTTP 服务就得到了错误的结论,而这个结论实际上是源自使用了错误命令的一个假设而已。
  • 给专业的人提供解决方案。在一个错误的假设上,我们给平台方提出了一个解决方案。很多时候,这种解决方案可能会实现,但是更多的时候,这种解决方案大概率没法实现。但是,我们忽略了,解决方案不是问题,只是我们以为是个问题而已。这在平台方帮助我们解决真正的问题时会带来很大的干扰。我们不应该给专业的人提供解决方案,而应该给出原始问题描述,向专业人士寻求解决方案

我们经常犯的错误

  • 错把假设当证据
  • 给专业的人提供解决方案

2. Nginx 为什么会自动抢占某个被释放的端口?

问题背景

我个人对 Nginx 比较了解,在工作中也解决了很多业务中遇到的 Nginx 的线上问题。

有一天,一个同事兴致冲冲的来找我,说:

17哥,我发现了一个 Nginx 的特殊功能——Nginx 会自动抢占某个被释放的端口。

我之前阅读 Nginx 源码的时候,未曾发现 Nginx 有这个特殊功能,也未曾记得 Nginx 的官方文档有这个特殊功能描述,并且我不认为类似 Nginx 这类的软件,应该具备这样的能力。所以我对同事的这个有趣的发现提出了质疑。

接下来,同事兴高采烈的给我演示了这个有趣的特性。原始的演示当时没有录制,所以,我在自己的电脑上对当时的演示进行了还原,具体如下图所示。

说明1:整个的演示过程从 启动 Nginx 开始。
说明2:当时演示的时候,并没有使用 watch 命令,仅仅使用了 lsof 命令。为了避免过多的手工操作,我在这里使用了 watch 命令。

这个有趣的演示有什么问题?

看完同事的演示,确实发生了 Nginx自动抢占被释放的9003端口的事情,看着演示,我陷入了深思。然后,我多次执行了 lsof 命令,来观察端口的占用信息。

我发现了一个奇怪的现象,这个现象大家观察演示图中的左上角的 watch 命令的结果也能发现:9003 端口的 Nginx 的 PID(进程号)在不断的发生变化。这说明 Nginx 进程在不断的重启。然后,我查看了 Nginx 的日志,发现了如下的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received
2021/09/20 10:41:34 [notice] 5315#0: exiting
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received
2021/09/20 10:41:34 [notice] 5313#0: gracefully shutting down
2021/09/20 10:41:34 [notice] 5313#0: exiting
2021/09/20 10:41:34 [notice] 4943#0: signal 20 (SIGCHLD) received from 5315
2021/09/20 10:41:34 [notice] 4943#0: cache loader process 5315 exited with code 0
2021/09/20 10:41:34 [notice] 4943#0: cache manager process 5314 exited with code 0
2021/09/20 10:41:34 [notice] 4943#0: signal 20 (SIGCHLD) received
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received from 5321
2021/09/20 10:41:34 [notice] 5313#0: exit
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received from 5313
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received from 5313
2021/09/20 10:41:34 [notice] 4943#0: signal 20 (SIGCHLD) received from 5313
2021/09/20 10:41:34 [notice] 4943#0: worker process 5313 exited with code 0
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received
2021/09/20 10:41:34 [notice] 4943#0: signal 23 (SIGIO) received

直到这个时候我们才发现,原来同事说的有趣的特性是 Nginx 不断的 reload 进程导致的。在 Nginx reload 时,会加载最新的配置,然后按照最新的配置启动新的 worker 进程并处理新的请求。于是,就发生了同事所说的 Nginx 的有趣的特性。

回归问题的本质

于是,经过排查,我们把一个假设转变成了假设背后的问题:Nginx 为什么会自动抢占被释放的端口?     \implies Nginx 为什么会不停的 reload?

因为找不到编译这个 Nginx 的源代码了,所以没办法确定是否是在源码层面进行了修改。

为了查清这个问题:

  • 我首先查看了 crontab 任务,发现机器上并没有配置类似的定时任务。
  • 然后,我查看了 Nginx 加载的所有 lua 插件代码,同样也没有发现有类似的代码。
  • 然后在 Nginx 信号处理的相关代码块进行的 GDB 单步调试,同样没有发现类似代码。

查了 1 天多也没查到为什么这个 Nginx 会不停的 reload。因为还有其他事情,这个问题也就搁置了起来。

后来,过了很长时间,同事告诉我说,在机器的某个目录下,发现了一个每隔 1 秒就 reload 那个 Nginx 的脚本。直到这个时候,整个事情才变得非常清晰。

总结

庭院深深深几许,楼高不见章台路。工作中,在我们解决问题的时候,我们并不是简单的执行这个问题的解决计划,在这个过程中,我们需要不断的对自己的解决计划实施自我监控,不断的监控:自己的执行是否正确,解决方案是否正确,当前的问题是否发生了默默的变化……我们还要时不时的回头看看,从而确认我们没有走错~

案例征集

如果您也有类似的案例,可以给本文提交 ISSUE 评论,也可以在 讨论区 留下您的案例,还可以在下方的评论处留下您的案例,我也会将这些案例增加到文章之中。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2024 wangwei
  • 本站访问人数: | 本站浏览次数:

请我喝杯咖啡吧~