将一个容器 FS 树绑定挂载到另一个容器 FS 树中,以进行调试或临时容器?

将一个容器 FS 树绑定挂载到另一个容器 FS 树中,以进行调试或临时容器?

我正在测试 k8s 调试功能,包括调试 pod 和临时容器,但我就是搞不清楚如何正确映射“目标”pod 的文件系统进入调试容器。

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

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

目标是让目标吊舱完整的文件系统树,包括卷和其他挂载映射到调试容器的子目录,例如/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

调试容器被启动到同一节点作为目标容器,我可以:

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

但是我不能:

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

是否有任何公认的方法可以做到这一点?

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

您可能希望通过使用withmount --rbind通过主机容器命名空间将调试容器“注入”到目标容器中。但是挂载容器根镜像,hostPath volumemountPropagation: Bidirectionalcontainerd将挂载传播设置为私有然后挂载内部卷。因此,主机挂载命名空间看不到容器根映像内部进行的挂载,容器中的进程看不到容器第一个进程启动后主机添加的新挂载。请参阅https://man7.org/linux/man-pages/man7/mount_namespaces.7.html了解详情。

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

nsenter -t $some_target_container_pid --root=/ -m /bin/bash

它给了我一个 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.

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

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

我不知道为什么:dmesg 中没有任何信息,错误非常普通。我猜这是由于根不相交和/或挂载命名空间不相交造成的。这似乎不是由于内核对绑定挂载循环的保护。而且我使用的是递归绑定,所以这不应该是由于 linux 用户命名空间中对挂载树逃逸的保护。

另一种方法--rbind是,如果我有mount --bind办法安装 ID如图所示/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

目前我最接近的解决方案是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

但是我无法在同一个 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.

有提示吗?


参考链接:

答案1

有可能做一个符号链接通过 到目标容器的上下文/proc/${target_container_pid}/root

ln -s /proc/$pid/root /target

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

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

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

相关内容