将一个容器 FS 树绑定挂载到另一个容器以用于调试或临时容器?

Cra*_*ger 4 mount namespaces linux-kernel docker kubernetes

我正在测试 k8s 调试功能,包括调试 pod 和临时容器,但我无法弄清楚如何将“目标”pod 的文件系统正确映射到调试容器中。

我想使用递归绑定挂载 * 链接两个不相交的挂载命名空间,以便容器 A 将容器 B 的根视为容器 B 的根,/containerB反之亦然。包括所有卷和其他安装。

目标:同时访问调试和目标容器文件系统

目标是让目标 pod 的完整文件系统树,包括映射到调试容器的子目录(例如 )的卷和其他挂载/run/target。如果目标容器挂载持久卷,则应映射这些挂载点,因此,例如,如果目标容器具有,/data则调试容器应具有已安装的/run/target/data.

或者,可以将调试容器文件系统树“注入”到目标容器中,因此可以在调试容器/run/debug时公开可用的调试容器根。nsenter包括它的像 procfs 这样的挂载,所以它的功能齐全。

我希望能够例如调试容器提供的gdb -p $target_pid位置。为此,必须能够从目标容器中找到进程可执行文件。gdbgdb

我探索了一些解决方法。但我真正想做的是将mount --rbind目标容器 FS 树转移到来宾上,反之亦然。给定一个定制的特权调试容器,例如:

apiVersion: v1
kind: Pod
metadata:
  name: debugcontainer
  namespace: default
spec:
  nodeName: TARGET_NODE_NAME_HERE
  enableServiceLinks: true
  hostIPC: true
  hostNetwork: true
  hostPID: true
  restartPolicy: Never
  containers:
  - image: DIAG_CONTAINER_IMAGE_HERE # you can experiment using something like ubuntu:20.04 
    name: debugger
    stdin: true
    tty: true
    volumeMounts:
    - mountPath: /target
      name: target
    #- mountPath: /host
    #  mountPropagation: None
    #  name: host-root
    securityContext:
      privileged: true
      runAsGroup: 0
      runAsUser: 0
  volumes:
  - emptyDir: {}
    name: target
  #- hostPath:
  #    path: "/"
  #    type: ""
  #  name: host-root

Run Code Online (Sandbox Code Playgroud)

当调试容器启动到与目标容器相同的节点时,我可以:

  • 请参阅目标容器进程ps
  • 使用 等附加到进程stracegdb因为特权调试容器具有CAP_SYS_PTRACE
  • nsenter -t $some_target_container_pid --all“成为”目标容器中的一个过程,就像我所做的那样kubectl exec。我无法再“查看”或访问调试容器文件/工具。
  • nsenter -t $some_target_container_pid -m --root=/ --wd=/进入目标进程的挂载命名空间,但保留调试容器的权限。我无法再“查看”或访问调试容器文件/工具。

但是我不能:

  • 在访问调试容器中的工具的同时查看目标容器中的文件 - 例如gdb找不到正在调试的可执行文件
  • 查看目标容器中卷的内容并对它们应用调试容器工具

有没有公​​认的方法可以做到这一点?

这并不完全是 k8s 特有的:同样的问题也适用于 Docker、containerdrunc等。

您可能希望通过使用withmount --rbind经由主机容器命名空间将调试容器“注入”到目标容器中来实现这一点。但安装容器根映像,将安装传播设置为私有,然后安装内部卷。因此,主机挂载命名空间看不到容器根映像内进行的挂载,并且容器中的进程看不到容器第一个进程启动后主机添加的新挂载。有关详细信息,请参阅https://man7.org/linux/man-pages/man7/mount_namespaces.7.htmlhostPath volumemountPropagation: Bidirectionalcontainerd

我尝试过使用nsenter“跨”挂载命名空间,但我无法让绑定挂载工作。例如在调试容器中我可以

nsenter -t $some_target_container_pid --root=/ -m /bin/bash
Run Code Online (Sandbox Code Playgroud)

这给了我一个 shell,其中.(CWD) 是调试容器 rootfs,并且/目标容器rootfs。但我似乎无法绑定安装它们:

$ mkdir /run/debug
$ mount --rbind . /run/debug
mount: /run/debug: wrong fs type, bad option, bad superblock on ., missing codepage or helper program, or other error.
Run Code Online (Sandbox Code Playgroud)

如果我使用nsenter --wd=/without --root, 并尝试 to ,也会发生同样的情况mount --rbind / ./run/debug

我尝试unshare -m首先创建一个新的内部安装命名空间。我mount --make-rprivate /在绑定安装之前尝试过调试容器树。同样的交易。

我不明白为什么: dmesg 中没有任何内容,并且错误非常普遍。我猜这是由于不相交的根和/或不相交的安装命名空间造成的。这似乎不是由于内核对绑定安装循环的保护。而且我使用的是递归绑定,所以这不应该是由于对 Linux 用户命名空间中的安装树转义的保护。

--rbind如果我有办法通过挂载 ID 来mount --bind替代FS树,如图所示/proc/$target_pid/mountinfo。然后我可以将目标 pid 中的所有挂载克隆到调试容器的挂载命名空间中。但我无法mount --bind使用正常的绝对路径,因为目标和调试容器的挂载命名空间是不相交的,并且两者都具有具有私有传播的挂载子树。

我尝试过使用目标进程的/proc/$pid/ns/mnt安装命名空间,因为我已经看到了使用它进行绑定安装的参考。但在我的内核 5.16 上,它是一棵假符号链接树,而不是 fs 树:

$ readlink /proc/self/ns/mnt
mnt:[4026531840]
$ ls /proc/self/ns/mnt/
ls: cannot access '/proc/self/ns/mnt/': Not a directory
Run Code Online (Sandbox Code Playgroud)

目前我最接近的解决方法是nsenter对工作目录进行黑客攻击。这提供了非常有限的工具注入到目标容器中。其中 pid 1055 是目标容器中的 pid:

# nsenter -t 1055 -p -m --wd=/ /bin/bash
shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory

# ls /
...target container rootfs contents here...

# ls .
...debug container rootfs here...

# ls ..
...debug container rootfs here too because . is a root...

# pwd
pwd: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory

# ls usr/bin/gdb
usr/bin/gdb

# ls /usr/bin/gdb
ls: cannot access '/usr/bin/gdb': No such file or directory
Run Code Online (Sandbox Code Playgroud)

但我无法像我想要的那样在同一个 nsenter 会话中绑定安装:

# mkdir /run/debug
# mount --rbind . /run/debug
mount: /run/debug: wrong fs type, bad option, bad superblock on ., missing codepage or helper program, or other error.
Run Code Online (Sandbox Code Playgroud)

提示?


参考链接:

Cra*_*ger 5

可以通过 建立到目标容器上下文的符号链接/proc/${target_container_pid}/root

ln -s /proc/$pid/root /target
Run Code Online (Sandbox Code Playgroud)

/proc/$pid/root 看起来像一个符号链接。如果你readlink /proc/$pid/root它指向/. 但它是目标进程的根,如果您在内核 vfs 层中取消引用它,您将看到目标进程的根。如果您解析用户空间中的符号链接,您将看到执行取消引用的处理的根源。

我无法绑定挂载树 -mount -o bind /proc/$pid/root/ /target将进程本身的 rootfs 绑定mount/target,而不是目标进程的 rootfs 。但这并不重要,因为符号链接就足够了。

(我会为文档编写一个补丁kubectl debug,但我无法让我的组织同意即使是简单的文档补丁所需的强制性 CLA...)