我正在测试 k8s 调试功能,包括调试 pod 和临时容器,但我就是搞不清楚如何正确映射“目标”pod 的文件系统进入调试容器。
我想要使用递归绑定挂载链接两个不相交的挂载命名空间* 因此容器 A 会将容器 B 的根视为容器 A 的根,反之亦然/containerB
。包括所有卷和其他挂载。
目标:同时访问调试和目标容器文件系统
目标是让目标吊舱完整的文件系统树,包括卷和其他挂载映射到调试容器的子目录,例如/run/target
。如果目标容器挂载了持久卷,则应该映射这些挂载点,因此例如如果目标容器有,/data
那么调试容器应该有一个挂载的/run/target/data
。
或者,也可以将调试容器文件系统树“注入”到目标容器中,这样在启动调试容器/run/debug
时,例如会显示可用的调试容器根目录nsenter
。包括其挂载(如 procfs),因此它完全可以正常工作。
我希望能够例如由调试容器提供的gdb -p $target_pid
位置。必须能够从中找到进程可执行文件gdb
gdb
目标容器为了这。
我探索了一些解决方法。但我真的想要做的是将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
- 附加到进程等
strace
,gdb
因为特权调试容器具有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
volume
mountPropagation: Bidirectional
containerd
将挂载传播设置为私有然后挂载内部卷。因此,主机挂载命名空间看不到容器根映像内部进行的挂载,容器中的进程看不到容器第一个进程启动后主机添加的新挂载。请参阅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.
有提示吗?
参考链接:
- https://unix.stackexchange.com/q/473717/45708
- https://medium.com/kokster/kubernetes-mount-propagation-5306c36a4a2d
- https://man7.org/linux/man-pages/man1/nsenter.1.html
- https://man7.org/linux/man-pages/man7/user_namespaces.7.html
- https://man7.org/linux/man-pages/man7/namespaces.7.html
- https://man7.org/linux/man-pages/man1/unshare.1.html
- https://unix.stackexchange.com/questions/594545/can-i-move-mount-to-other-mount-namespace
- https://unix.stackexchange.com/questions/693822/bind-mount-across-namespaces-with-disjoint-roots
答案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……)