首页
Preview

调试 containerd

containerd是容器运行时接口(CRI),将(很快)成为Zendesk的支持者。

新的容器运行时意味着需要新的调试技术。

其中许多技术是从我们在使用containerd 1.6.2时在暂存环境中观察到的问题中学习到的。

containerd堆栈由几个不同的二进制文件组成,所有这些二进制文件都执行一定的任务。

要正确调试containerd,我们需要知道如何检查每个组件。

非常基本的堆栈概述如下:

例如,当要创建一个pod时,kubelet使用CRI接口与containerd通信。containerd将fork / exec一个containerd-shim实例,该实例再调用runc二进制文件。由runc创建,运行,删除和清理容器。

1. Kubelet

从kubelet的角度调试containerd非常简单。我们要检查kubelet日志,看是否有任何明显的问题。

请注意,来自较低级别的问题(例如runc)希望冒泡到kubelet日志中。如果由于runc问题而无法创建容器,则kubelet可能会抱怨pod创建的截止日期已过。

确认kubelet已配置为实际使用containerd。需要设置标志--container-runtime=remote--container-runtime-endpoint=unix:///run/containerd/containerd.sock

2. Containerd

首先要检查的是服务状态和日志,与kubelet类似。

更进一步,我们可以查看containerd正在使用的配置文件,并可以将其与默认配置进行比较。如果你注意到任何问题,无论是日志行还是一些意外的配置,请查看问题。

crictl工具非常有用,因为它使你可以以与kubelet相同的方式与容器运行时进行交互。请参阅https://github.com/kubernetes/cri-api。

启用containerd上的调试非常简单,只需编辑containerd配置以启用套接字。

这允许你执行诸如转储containerd启动的所有goroutine,获取跟踪等操作。

3. containerd-shim

这里的调试方法可能有点过度,但如果你从kubelet进入此文章,并且现在到达此处,那么它可能值得!

每个shim都负责单个pod。如果你运行上面的ps aux --forest命令,则应该看到:

  • 全能的暂停容器
  • 容器入口点中定义的进程

以上示例有点复杂,我们正在运行暂停容器,加上已运行bash脚本的sh。然后,这启动了presumably正在被馈入tee的aws-k8s-agent。最后,所有这些都通过logrecycler进行管道传输。

shim还充当子reaper,即它将reap zombie进程。如果shim进程消失,则下面的进程不会停止,而是被孤立到containerd。

如果由于某种原因containerd-shim未运行暂停容器,则可能存在创建pod沙箱的问题。请对有关节点进行健全性检查,并查看性能(这是一篇启发了本文格式的好文章)。

如果你怀疑containerd-shim出现问题,例如它消耗大量资源(它们通常会自愿睡眠),则可以执行许多操作。即,转储goroutine。

4. runc / container

这就是关键:调试的下一步取决于你要调试的问题类型。在这个级别上特定调试的位置非常依赖于你要调试的问题。接下来,我将提供有关runc的工作原理以及可以查找进一步故障排除的位置的一些见解。当一个容器作为 Kubernetes 进程的一部分被创建时,容器会在 <a class="af np" href="http://k8s.io" rel="noopener ugc nofollow" target="_blank"><em>http://k8s.io</em></a> 命名空间下创建(这不是一个 Kubernetes 命名空间,而是存储与 Kubernetes 相关的所有容器运行时 pod 状态的地方)。

要运行各种 runc 子命令,通常需要提供一个 --root 标志。这将是位于 <em>/run/containerd/runc/k8s.io/</em> 的状态目录。

runc 启动一个容器时,实际上是一些不同的动作:

  • runc create 创建一个容器,并在其中启动一个裸的 runc init 进程。然后,这个 runc init 等待另一侧打开 exec FIFO 文件,作为同步机制。一旦打开,它会向其中写入一个 0 字节,并继续执行容器的入口点。
  • runc start 实际上启动了该容器(通过打开 exec FIFO 文件并从中读取数据),并向 runc init 发出信号,表示它应该继续进行。

来源:

Kir Kolyshkin

@ https://github.com/opencontainers/runc/issues/3448

如果出现问题,例如 containerd 处于死锁状态就像我们之前看到的

  • runc init 进程将永远等待 runc start
  • runc init 进程会每隔几分钟创建一次
  • Pod 无法运行
  • Pod 无法终止

感谢

非常感谢 Fu Wei 和 containerd 团队,他们帮助我们调试并最终解决了在我们的 staging 环境中遇到的死锁问题。

附录

可以通过运行 gdb,附加到进程并运行 generate-core-dump 来创建核心转储。我建议在传输前对这些核心转储进行 gzip 压缩(它们非常冗长,但是很适合压缩)。

这些核心转储可以与 delve 一起使用,但是如上所述,这些二进制文件必须具有调试符号!

请注意,附加调试器到任何正在运行的进程可能会显著减慢它的速度。这是因为调试器(如 delvegdb)将钩子插入进程和导入库的各个部分。这些钩子是额外的代码路径,会增加执行时间。例如,可能已经存在性能问题的进程将受到更大的影响。

在生产环境中运行这些操作要小心。

链接和进一步阅读

译自:https://zendesk.engineering/debugging-containerd-a20f28a2a8bf

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
菜鸟一只
你就是个黄焖鸡,又黄又闷又垃圾。

评论(0)

添加评论