调试重载的Golang应用程序或我们如何查找Kubernetes中不存在的问题

在现代的Kubernetes云世界中,一种或另一种方式必须面对您或您的同事未犯的软件错误,但您必须解决它们。本文可能有助于Golang和Kubernetes领域的新手了解一些调试自己和外部软件的方法。

图片

我的名字叫Viktor Yagofarov,我正在DomKlik开发Kubernetes云,今天我想谈谈如何使用生产k8s(Kubernetes)集群的关键组件之一解决问题。

在我们的战斗群中(在撰写本文时):

  • 推出了1890个Pod和577个服务(实际微服务的数量也在该数字范围内)
  • Ingress控制器提供大约6k RPS,Ingress直接将大约相同数量的RPS发送到hostPort


问题


几个月前,我们的Pod开始遇到解析DNS名称的问题。事实是DNS主要在UDP上工作,并且在Linux内核中conntrack和UDP 存在一些问题。访问k8s服务的服务地址时,DNAT只会加剧conntrack争用的问题。值得补充的是,发生问题时,在我们的集群中,大约有40k RPS朝向DNS服务器CoreDNS。

图片

决定使用社区专门在群集的每个工作节点上创建的NodeLocal DNS(nodelocaldns)本地缓存DNS服务器,该服务器仍处于beta状态,旨在解决所有问题。简而言之:连接到群集DNS时摆脱UDP,删除NAT,添加一个额外的缓存层。

在实现nodelocaldns的第一个迭代中,我们使用版本1.15.4(不要与cube的版本混淆),该版本与«kubernetes-installer» Kubespray一起提供 -我们正在谈论的是我们来自南桥的公司Fork fork。

引入后几乎立即出现了问题:内存流动,并且根据内存限制(OOM-Kill)重新启动了炉床。重新启动此主机时,主机上的所有流量都丢失了,因为在所有Pod中,/ etc / resolv.conf都精确指向nodelocaldns的IP地址。

这种情况肯定并不适合所有人,我们的OPS团队采取了许多措施来消除这种情况。

由于我本人是Golang的新手,所以我非常感兴趣,要一路走来,并熟悉使用这种出色的编程语言调试应用程序。

我们正在寻找解决方案


所以走吧!1.15.7

版已下载到开发集群中,该集群已经被认为是beta版本,而不是1.15.4版本的alpha版本,但是该少女在DNS(40k RPS)中没有此类流量。这是可悲的。 在此过程中,我们取消了Kubespray的nodelocaldns的约束,并编写了一个特殊的Helm图表以更方便地进行推广。同时,他们为Kubespray编写了一部剧本,使您可以更改kubelet设置,而不必在一小时内消化整个群集状态。此外,这可以逐点完成(首先在少量节点上检查)。 接下来,我们将nodelocaldns 1.15.7的版本推出到prod。 las,情况再次发生。记忆正在流动。 官方的nodelocaldns存储库具有标记为1.15的版本。8





,但是由于某些原因,我无法让docker使用该版本,并以为我尚未收集到正式的Docker映像,因此不应使用此版本。这是重要的一点,我们将回到这一点。

调试:第一阶段


很长一段时间以来,我原则上都不知道如何组装我的版本的nodelocaldns,因为萝卜中Makefile崩溃了,而docker镜像中出现了无法理解的错误,而且我真的不明白如何用govendor巧妙地构建Go项目,该项目立即以奇怪的方式被整理到目录中用于几个不同的DNS服务器选项。事实是,当普通的现成的依赖版本已经出现时,我便开始学习Go

Pavel Selivanov在这个问题上为我提供了很多帮助。保罗贾姆,对此深表感谢。我设法组装了我的版本。

接下来,我们拧紧pprof分析器,在处女架上测试该组件,并将其推出产品。

聊天团队的一位同事对整理配置文件非常有帮助,这样您就可以通过CLI URL方便地使用pprof实用程序,并使用浏览器中的交互式菜单来研究内存和进程线程,对此我也非常感谢。

乍一看,基于事件探查器的输出,该过程运行良好-大多数内存分配在堆栈上,并且看起来好像Go-routine经常使用它

但是到了某个时候,很明显,与“健康”线程相比,nodelocaldns的“坏”线程有太多线程处于活动状态。而且线程并没有消失在任何地方,而是继续挂在内存中。这时,帕维尔·塞里瓦诺夫(Pavel Selivanov)的“线程在流动”的预感得到了证实。

图片

调试:阶段2


为什么发生这种情况(线程在流动)变得很有趣,并且对nodelocaldns进程的研究的下一个阶段已经开始。

静态分析器代码staticcheck表明,仅在中创建线程的阶段存在一些问题,该线程用在nodelocaldns中(它称为luluda CoreDNS,后者代表nodelocaldns'om)。据我了解,在某些地方不是传递指向该结构指针,而是传递的副本

决定使用gcore实用程序 “不良”过程进行核心转储,然后查看其中的内容。

用类似gdb的dlv工具卡在coredump中我意识到了它的强大功能,但是我意识到我会以这种方式寻找原因很长时间。因此,我将coredump加载到Goland IDE中并分析了进程内存的状态。

调试:阶段3


研究程序的结构并查看创建它们的代码非常有趣。在大约10分钟内,很明显,许多go例程为TCP连接创建了某种结构,将其标记为false,并且从不删除它们(还记得40k RPS吗?)。

图片

图片

在屏幕快照中,您可以看到代码中有问题的部分以及关闭UDP会话时未清除的结构。

同样,从coredump中,这些结构中的IP地址也知道了这么多RPS的罪魁祸首(感谢您帮助我们在集群中找到瓶颈:)。

决断


在解决这个问题的过程中,我在Kubernetes社区的同事的帮助下发现,nodelocaldns 1.15.8的官方Docker镜像仍然存在(而且我实际上弯手,以某种方式做错了docker pull,或者WIFI顽皮拉动力矩)。

在此版本中,他使用的库版本极大地“不高兴”:具体来说,“罪魁祸首” “约稿”了大约20个版本!

此外,新版本已经支持通过pprof进行性能分析,并且已通过Configmap启用了该功能,您无需重新组装任何内容。

首先在开发人员中下载了新版本,然后在产品中下载了新版本。
三... 胜利
进程开始将其内存返回给系统,问题停止了。

在下图中,您可以看到图片:“吸烟者的DNS与 健康人的DNS。”

图片

发现


结论很简单:仔细检查几次您所做的事情,不要轻视社区的帮助。结果,我们在问题上花了比我们更多的时间,但是我们在容器中收到了DNS故障安全操作。感谢您阅读本文:)

有用的链接:

1. www.freecodecamp.org/news/how-i-investigated-memory-leaks-in-go-using-pprof-on-a-large-codebase-4bec4325e192
2 。habr.com/en/company/roistat/blog/413175
3. rakyll.org

All Articles