pivot_root:原理、用途及最简单 Demo
什么是 pivot_root
pivot_root
是 Linux 系统中的一个系统调用(和对应的命令行工具),用于更改进程的根文件系统。与 chroot
类似,pivot_root
将一个指定目录设置为进程的新根目录(/
),但它比 chroot
更灵活,特别适合容器环境。它的核心功能是将当前根文件系统(旧根)移动到另一个位置,并将新目录设置为根文件系统。
主要特点
-
交换根文件系统:
pivot_root
将一个新目录设置为进程的根(/
),并将旧根移动到指定位置(通常是一个子目录)。- 例如,
pivot_root(new_root, put_old)
将new_root
设置为新根,旧根挂载到put_old
。
-
挂载点隔离:
- 与
chroot
不同,pivot_root
要求新根和旧根是独立的挂载点,通常需要 Mount Namespace 支持。 - 它可以完全隔离旧根的挂载点,防止进程访问主机文件系统。
- 与
-
容器初始化:
pivot_root
是容器运行时(如 Docker、containerd)的标准工具,用于将容器镜像的根文件系统设置为容器进程的根。
主要用途
- 容器根文件系统切换:在容器启动时,将镜像的根文件系统(例如
/var/lib/docker/...
)设置为容器的新根,隔离主机文件系统。 - 强隔离环境:比
chroot
提供更彻底的隔离,防止进程通过挂载点逃逸。 - 系统初始化:在 Linux 启动过程中(initramfs),将临时根切换到真实根文件系统。
- 测试和开发:为进程提供独立的根文件系统,测试特定环境。
与 chroot 的区别
- 隔离程度:
chroot
仅更改根目录,进程仍可能看到主机挂载点(如/proc
、/dev
)。pivot_root
要求新根是独立挂载点,旧根被移动到新位置,提供更强的隔离。
- 使用场景:
chroot
适合简单隔离(如测试或修复)。pivot_root
适合容器,需配合 Mount Namespace。
- 要求:
chroot
直接作用于目录。pivot_root
需要新根和旧根是挂载点,且进程在 Mount Namespace 中。
与 Mount Namespace 的关系
你的前文提到 Mount Namespace 和 nsenter
的问题(在 Mount Namespace 中挂载 tmpfs 到 /tmp/mnt
,但看到主机目录的其他文件)。pivot_root
常与 Mount Namespace 结合使用:
- Mount Namespace 提供独立的挂载点视图,确保新根挂载(如 tmpfs 或容器镜像)仅在命名空间内可见。
pivot_root
将新根设置为进程的根目录,隔离主机文件系统。- 结合
nsenter
,可以进入 Mount Namespace 验证pivot_root
的隔离效果。
pivot_root 最简单 Demo
以下是一个最简单的 pivot_root
示例,展示如何在 Mount Namespace 中使用 pivot_root
切换根文件系统,确保隔离主机文件系统。我们将:
- 创建一个新目录作为新根。
- 使用 Mount Namespace 和 tmpfs 模拟容器根文件系统。
- 通过
pivot_root
切换根,并验证隔离。 - 使用
nsenter
进入 Mount Namespace 检查结果。
环境要求
- Linux 系统(支持
pivot_root
和 Mount Namespace,例如 Ubuntu、CentOS) unshare
和nsenter
命令(util-linux
包)- root 权限(
pivot_root
和挂载需要管理员权限) bash
和必要库(用于新根环境)
步骤
- 清理旧环境:
确保/tmp/mnt
干净,清理旧进程(基于你的ps aux
输出,PID15375
和15376
):
sudo umount /tmp/mnt 2>/dev/null
sudo rm -rf /tmp/mnt
sudo mkdir -p /tmp/mnt
验证进程已清理:
ps aux | grep '[u]nshare --mount'
预期输出为空。
- 创建新根目录:
创建一个新根文件系统,包含bash
和必要库:
sudo mkdir -p /tmp/mnt/bin /tmp/mnt/lib /tmp/mnt/lib64 /tmp/mnt/old_root
sudo cp /bin/bash /tmp/mnt/bin/
复制 bash
依赖库(检查 ldd /bin/bash
输出):
ldd /bin/bash
输出示例:
linux-vdso.so.1 (0x00007fffc8bff000)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f0c6c5c9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0c6c3c7000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0c6c628000)
复制库:
sudo cp /lib/x86_64-linux-gnu/libtinfo.so.6 /tmp/mnt/lib/
sudo cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/mnt/lib/
sudo cp /lib64/ld-linux-x86-64.so.2 /tmp/mnt/lib64/
- 启动 Mount Namespace 并挂载新根:
sudo unshare --mount /bin/bash
在新的 bash shell 中,设置挂载隔离并挂载新根:
mount --make-rprivate -o rec /
mount --bind /tmp/mnt /tmp/mnt
echo "Hello from Mount Namespace!" > /tmp/mnt/testfile
ls /tmp/mnt
输出:
bin lib lib64 old_root testfile
说明:
mount --bind /tmp/mnt /tmp/mnt
使/tmp/mnt
成为独立挂载点,满足pivot_root
的要求。old_root
是旧根的存放目录。
- 执行 pivot_root:
切换根文件系统,将/tmp/mnt
设为新根,旧根移到/tmp/mnt/old_root
:
cd /tmp/mnt
pivot_root . old_root
修复环境:
pivot_root
后,/bin/bash
仍在旧根中,需更新路径:
export PATH=/bin
验证新根:
ls /
输出:
bin lib lib64 old_root testfile
尝试访问旧根:
ls /old_root
输出示例:
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
隔离验证:
尝试访问主机 /tmp
:
ls /old_root/tmp
输出显示主机 /tmp
的内容,但新根(/
)只包含 /tmp/mnt
的内容,表明隔离成功。
- 验证全局命名空间隔离:
- 保持
unshare
shell 运行,打开另一个终端:
ls /tmp/mnt
输出:
bin lib lib64 old_root
全局命名空间看不到 testfile
,因为它仅在 Mount Namespace 中创建。
- 使用 nsenter 进入 Mount Namespace:
在另一个终端,查找 PID:
ps aux | grep '[u]nshare --mount'
输出示例:
root 16000 0.0 0.1 16732 7040 pts/0 S+ 09:00 0:00 sudo unshare --mount /bin/bash
进入 Mount Namespace:
sudo nsenter --mount=/proc/16000/ns/mnt /bin/bash
验证新根:
ls /tmp/mnt
输出:
bin lib lib64 old_root testfile
注意:如果 pivot_root
已执行,/tmp/mnt
是旧路径,可能不可见。直接检查新根:
ls /
输出:
bin lib lib64 old_root testfile
- 退出并清理:
- 在
nsenter
shell:
exit
- 在
unshare
shell:
umount old_root
umount /tmp/mnt
exit
- 清理目录:
sudo rm -rf /tmp/mnt
解决主机文件可见问题
在 Mount Namespace 中挂载 tmpfs 到 /tmp/mnt
后,通过 nsenter
进入仍看到主机目录的其他文件。pivot_root
可以解决此问题,因为它完全替换根文件系统,隔离主机文件系统。关键点:
- Mount Namespace 隔离:
mount --make-rprivate
确保挂载不传播。 - pivot_root 切换:将
/tmp/mnt
设为新根,旧根移到old_root
,防止主机文件(如/tmp/mnt
的其他文件)可见。 - nsenter 验证:进入 Mount Namespace 检查新根只包含预期内容(如
testfile
)。
如果仍看到主机文件,检查:
- 是否正确执行
pivot_root
(需要cd /tmp/mnt
和挂载点)。 /tmp/mnt
是否干净(步骤 1)。- PID 是否正确(步骤 6)。
注意事项
- 权限:
pivot_root
、unshare
和nsenter
需要 root 权限。 - 挂载点要求:
pivot_root
要求新根和旧根是独立挂载点,使用mount --bind
创建。 - 依赖完整性:新根需包含必要文件(如
bash
和库),否则报错。 - 清理:实验完成后,卸载挂载点并删除临时目录。
- 与 chroot 对比:
pivot_root
提供更强隔离,适合容器场景。
总结
pivot_root
是一种强大的根文件系统切换工具,通过将新目录设为根并移动旧根,实现比 chroot
更彻底的隔离,常用于容器初始化。结合 Mount Namespace 和 nsenter
,可以确保隔离主机文件系统,解决你在 Mount Namespace 中看到主机目录其他文件的问题。本文通过一个简单 Demo 展示了 pivot_root
的使用,确保新根只包含预期内容(如 testfile
)。如果你在执行 pivot_root
时遇到问题(如报错或仍看到主机文件),请提供以下信息,我可以进一步协助:
pivot_root . old_root
的具体错误信息。unshare
shell 中mount | grep /tmp/mnt
和ls /tmp/mnt
的输出。- 全局命名空间中
ls /tmp/mnt
的输出(显示哪些“其他文件”)。 /proc/16000/ns/mnt
的ls -l
输出。