共计 2716 个字符,预计需要花费 7 分钟才能阅读完成。
调试 Docker 容器可能是一个非常具有挑战性的过程。在这里,我将分享一些调试容器的基本技术,主要是 Docker 的技术,但是这些技术也适用于许多其他类型的 Linux 容器引擎。我将在此处详细介绍的方法适用于基于 Linux 的系统。
撰写此博客文章的灵感来自于 externalsecret-operator 的开发团队在实施 1password 后端期间遇到的一些近期问题,该库提供了与 1password API 进行通信的机制。
提供一些背景信息:externalsecret-operator 是一个很棒的 Kubernetes operator,它使 secret 管理的挑战性降低,并帮助您将来自第三方凭证存储的秘密直接注入到 Kubernetes 集群中。(如果您还没有听说过,那么绝对值得尝试一下。我们的团队正在努力开发它,并且每周都会添加新功能。)
但是回到调试。在以下情况下,如果我们在主机上运行 operator,那么一切都会按预期进行,并且可以登录到 1password 后端:
但是在容器中运行时,我们会看到以下错误消息:
看起来没有正常运行,但是我们应该如何以及在哪里开始调试?
从代码分析开始
最好的起点总是源代码,并且因为 externalsecret-operator 是开源的,所以我们可以很容易地看到问题出在哪里。我建议使用 Sourcegraph 工具,该工具可直接在浏览器中工作并且可以进行代码分析。使用 Sourcegraph 识别代码中有问题的部分的示例:
这种快速的代码分析显示出一个好消息和一个坏消息:幸运的是,因为我们的代码不是问题的根源。但也很不幸,因为 1password 客户端二进制文件(op)用于直接从 1password 秘密存储区中获取凭证 - 不幸的是,这两个都不是开源的。
这才是真正的乐趣开始的地方。
调试在主机系统上有效但在容器中不起作用的那些东西的线索是了解什么是容器。容器是一种在同一主机上运行时将进程彼此隔离的机制,这意味着其管理员可以直接从主机系统访问任何容器。
有了这些知识,让我们看看 1password 二进制文件是否损坏。首先,我们必须验证我们的 1password 二进制文件不适用于容器的理论是否有效。让我们手动使用它,使用伪造的登录数据:
答对了!我们的理论得到证实。1password 二进制文件不适用于该容器;恐慌而不是优雅地退出。现在我们有了这些信息,我们应该确认问题是二进制本身还是容器环境造成的。最好的方法是重新使用与容器镜像中完全相同的二进制文件。开始吧!
假设我们对正在运行容器的主机具有 root 访问权,那么我们可以使用 docker top <container id> 来查看主机上有问题的容器的 PID(进程标识符)是什么:
我们确定了容器 PID,因此现在可以在 Linux 系统进程表中找到此进程。进入 process 目录后,我们应该查看根文件夹,因为它是包含正在运行的容器的文件系统的根文件夹:
检查 operator
我们可以访问容器中的所有二进制文件,并在主机名称空间中执行它们。
因此,让我们看看 operator 的问题是否来自使用错误的 1password 二进制文件:
1password op 二进制文件的行为正确:由于我们提供了虚假数据,因此无法登录 1password 域,但最终并没有收到紧急消息。因此,问题不在 1password 二进制文件之内。
我们可以看到主机名称空间中的执行错误消息提到了有关无效请求的内容。我们可能会怀疑容器中的网络是否存在问题,以及操作二进制的 panic,如果它无法到达某些外部服务时,这是另一个需要测试的理论。
如前所述,1password 二进制文件不是开源的,因此我们无法知道它在哪里尝试连接。我们可以尝试使用 Wireshark 或 tcpdump 等网络嗅探器捕获流量。但…
…容器镜像不包含任何这些二进制文件。我们可以将它们安装在 Docker 镜像中。但是,由于我们具有对主机的 root 访问权限,因此可以通过使用 nsenter 来更轻松地进行操作。
nsenter 工具允许我们输入特定的进程名称空间 - 因此,例如,我们可以输入容器的网络名称空间,并且仍然可以访问我们的宿主工具。让我们看看实际情况:
我们位于容器网络名称空间中,可以从主机系统访问所有工具。
在另一个 shell 中,我们可以使用容器中的 op 二进制文件再次登录 1password。但是,即使已连接到互联网,也没有生成任何数据包:
重新排查 1Password Binary
因此,在这一点上,我们可以假设 op 二进制 panic 甚至在建立与外部服务的连接之前就已出现。
现在,我们可以尝试使用 GDB(GNU 项目调试器)调试 op 二进制文件,以查看执行失败的时间。但是 1password 可能不包含调试符号,因此 GBD 无法为我们提供有关执行的任何信息。通过检查操作二进制文件中是否存在调试符号来进行确认:
如所假定的,op 二进制根本没有调试符号。如果它具有这些符号,那么在几行中将显示除零以外的数字,如在运算符中一样。使用 GDB 没有任何意义。
另外,GDB 不支持跨命名空间调试,因此不可能从主机命名空间中对其进行调试。我们将必须在 Docker 映像中安装 GDB 或从源代码编译 GDB。
我们无法使用 GDB,但可以在此过程中“strace”或跟踪系统调用和信号:查看执行二进制文件中 op 二进制文件调用了哪些系统调用。为此,我们运行另一个具有相同镜像的容器,但修改后的入口点将直接指向 op 二进制文件,并使用先前使用的参数:
在另一个 Shell 窗口中,我们将附加到该容器的 PID 并输入密码:
我们发现它存在一些严重的问题。二进制文件尝试在仅允许超级用户的文件系统的根目录 / 中创建.op 目录:
mkdirat(AT_FDCWD,“/.op”,0700)= -1 EACCES(权限被拒绝)
让我们检查一下如果以 root 用户身份运行此容器会发生什么情况:
Binary 并没有 panic,而 strace 表明 op 实际上试图在超级用户主目录而不是 / 中创建.op 二进制文件。
这使我们得出结论,即默认用户可能在 Docker 镜像中配置错误。让我们检查一下:
答对了!/ etc / passwd 中缺少默认用户,系统无法识别该默认用户,因此 op binary 无法确定其主目录,并尝试在当前工作目录中创建目录。这是不允许的。因此,它会 panic。
我们永远不会忘记 Docker 容器只是进程,大多数 Linux 标准调试工具都可以在它们上使用,并消除猜测驱动的调试。
我想再次强调,这些技术主要适用于 Linux 系统。但是对于那些在 Mac 上运行容器的人,我建议您研究其他技术,例如使用所有调试工具运行 sidecar 容器。无论使用什么系统,祝您调试侦探工作顺利!