08-RootFS与文件系统隔离
学习目标
- 深入理解 Mount Namespace 的工作原理
- 掌握 chroot 和 pivot_root 的区别和使用
- 了解特殊文件系统的作用和配置
- 能够准备和管理容器 rootfs
- 掌握文件系统隔离的调试技术
前置知识
- Linux 文件系统基础
- 挂载点概念
- 进程和文件描述符
- 容器基础原理
一、文件系统隔离概述
1.1 文件系统隔离需求
容器需要独立的文件系统视图:
graph TD
A[容器文件系统需求] --> B[独立根目录]
A --> C[独立挂载点]
A --> D[独立文件权限]
A --> E[独立设备文件]
B --> B1[容器只能看到自己的根目录]
C --> C1[独立的 /proc, /sys, /dev]
D --> D1[文件权限隔离]
E --> E1[设备访问控制]
1.2 文件系统隔离技术
graph LR
A[文件系统隔离] --> B[Mount Namespace]
A --> C[chroot/pivot_root]
A --> D[特殊文件系统]
A --> E[权限控制]
B --> B1[挂载点隔离]
C --> C1[根目录切换]
D --> D1[/proc, /sys, /dev]
E --> E1[文件访问控制]
二、Mount Namespace 详解
2.1 Mount Namespace 原理
Mount Namespace 为每个容器提供独立的挂载点表:
graph TD
A[Linux 内核] --> B[挂载点表1]
A --> C[挂载点表2]
A --> D[挂载点表3]
B --> B1[宿主机挂载点]
B1 --> B2[/proc]
B1 --> B3[/sys]
B1 --> B4[/dev]
C --> C1[容器1挂载点]
C1 --> C2[/proc - 独立]
C1 --> C3[/sys - 独立]
C1 --> C4[/dev - 独立]
D --> D1[容器2挂载点]
D1 --> D2[/proc - 独立]
D1 --> D3[/sys - 独立]
D1 --> D4[/dev - 独立]
2.2 Mount Namespace 特性
特性 | 说明 | 示例 |
---|---|---|
挂载点隔离 | 每个 namespace 有独立的挂载点表 | mount 命令显示不同结果 |
挂载传播 | 子 namespace 可以继承父 namespace 的挂载点 | 使用 MS_SHARED 标志 |
私有挂载 | 使用 MS_PRIVATE 标志创建私有挂载 | 容器内挂载不影响宿主机 |
绑定挂载 | 使用 MS_BIND 标志创建绑定挂载 | 将文件或目录绑定到另一个位置 |
2.3 Mount Namespace 实战演示
2.3.1 创建 Mount Namespace
# 1. 创建 Mount Namespace
unshare -m bash
# 2. 查看当前挂载点
mount | head -10
# 输出: 显示当前 namespace 的挂载点
# 3. 在另一个终端查看宿主机挂载点
mount | head -10
# 输出: 显示宿主机的挂载点(可能不同)
# 4. 重新挂载 /proc
mount -t proc proc /proc
# 5. 验证隔离效果
mount | grep proc
# 输出: proc on /proc type proc (rw,relatime)
2.3.2 挂载传播测试
# 1. 创建共享挂载点
mkdir /tmp/shared
mount --bind /tmp/shared /tmp/shared
mount --make-shared /tmp/shared
# 2. 创建子 namespace
unshare -m bash
# 3. 在子 namespace 中挂载
mount --bind /tmp/shared /mnt
# 4. 在宿主机中查看
mount | grep shared
# 输出: 应该能看到子 namespace 的挂载
三、chroot vs pivot_root
3.1 chroot 系统调用
chroot 是改变根目录的系统调用:
graph TD
A[进程] --> B[chroot 调用]
B --> C[改变根目录]
C --> D[进程只能访问新根目录下的文件]
E[原根目录] --> F[/bin, /usr, /etc...]
G[新根目录] --> H[容器文件系统]
D --> G
3.2 chroot 特性
- 简单易用:只需要一个系统调用
- 安全性问题:进程可以退出 chroot 环境
- 挂载点问题:不能卸载原根目录
- 权限问题:需要 root 权限
3.3 chroot 实战演示
# 1. 准备 rootfs
mkdir -p /tmp/rootfs/{bin,usr,etc,proc,sys,dev}
cp -r /bin/* /tmp/rootfs/bin/
cp -r /usr/* /tmp/rootfs/usr/
cp -r /etc/* /tmp/rootfs/etc/
# 2. 使用 chroot
sudo chroot /tmp/rootfs /bin/bash
# 3. 在 chroot 环境中
pwd
# 输出: /
ls /
# 输出: bin usr etc proc sys dev
# 4. 退出 chroot
exit
3.4 pivot_root 系统调用
pivot_root 是更安全的根目录切换方法:
graph TD
A[进程] --> B[pivot_root 调用]
B --> C[切换根目录]
C --> D[卸载原根目录]
D --> E[进程完全隔离]
F[原根目录] --> G[移动到 put_old]
H[新根目录] --> I[成为新的根目录]
E --> I
3.5 pivot_root 特性
- 更安全:原根目录被移动到 put_old
- 完全隔离:进程无法访问原根目录
- 需要绑定挂载:新根目录必须是绑定挂载
- 权限要求:需要 root 权限
3.6 pivot_root 实战演示
# 1. 准备 rootfs
mkdir -p /tmp/rootfs/{bin,usr,etc,proc,sys,dev,oldroot}
# 2. 绑定挂载 rootfs
mount --bind /tmp/rootfs /tmp/rootfs
# 3. 使用 pivot_root
cd /tmp/rootfs
sudo pivot_root . oldroot
# 4. 卸载原根目录
umount -l /oldroot
# 5. 在 pivot_root 环境中
pwd
# 输出: /
ls /
# 输出: bin usr etc proc sys dev oldroot
# 6. 重新挂载 /proc
mount -t proc proc /proc
四、特殊文件系统
4.1 /proc 文件系统
/proc 是进程信息文件系统:
graph TD
A[/proc 文件系统] --> B[进程信息]
A --> C[系统信息]
A --> D[内核参数]
B --> B1[/proc/PID/]
B --> B2[/proc/self/]
B --> B3[/proc/stat]
C --> C1[/proc/cpuinfo]
C --> C2[/proc/meminfo]
C --> C3[/proc/version]
D --> D1[/proc/sys/]
D --> D2[/proc/sysctl.conf]
4.2 /proc 文件系统配置
# 1. 挂载 /proc
mount -t proc proc /proc
# 2. 查看进程信息
cat /proc/self/status
cat /proc/self/cgroup
cat /proc/self/mountinfo
# 3. 查看系统信息
cat /proc/cpuinfo
cat /proc/meminfo
cat /proc/version
# 4. 查看内核参数
cat /proc/sys/kernel/hostname
cat /proc/sys/net/ipv4/ip_forward
4.3 /sys 文件系统
/sys 是系统设备文件系统:
graph TD
A[/sys 文件系统] --> B[设备信息]
A --> C[内核模块]
A --> D[电源管理]
B --> B1[/sys/class/]
B --> B2[/sys/devices/]
B --> B3[/sys/bus/]
C --> C1[/sys/module/]
C --> C2[/sys/kernel/]
D --> D1[/sys/power/]
D --> D2[/sys/thermal/]
4.4 /sys 文件系统配置
# 1. 挂载 /sys
mount -t sysfs sysfs /sys
# 2. 查看设备信息
ls /sys/class/net/
ls /sys/devices/
# 3. 查看内核模块
ls /sys/module/
# 4. 查看电源管理
cat /sys/power/state
4.5 /dev 文件系统
/dev 是设备文件系统:
graph TD
A[/dev 文件系统] --> B[字符设备]
A --> C[块设备]
A --> D[特殊设备]
B --> B1[/dev/null]
B --> B2[/dev/zero]
B --> B3[/dev/random]
C --> C1[/dev/sda]
C --> C2[/dev/sdb]
D --> D1[/dev/tty]
D --> D2[/dev/pts/]
D --> D3[/dev/shm/]
4.6 /dev 文件系统配置
# 1. 挂载 /dev
mount -t devtmpfs devtmpfs /dev
# 2. 创建必要的设备文件
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/random c 1 8
mknod /dev/urandom c 1 9
# 3. 创建 /dev/pts
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
# 4. 创建 /dev/shm
mkdir -p /dev/shm
mount -t tmpfs tmpfs /dev/shm
️ 五、RootFS 准备
5.1 RootFS 结构
graph TD
A[RootFS] --> B[bin/]
A --> C[usr/]
A --> D[etc/]
A --> E[proc/]
A --> F[sys/]
A --> G[dev/]
A --> H[tmp/]
A --> I[var/]
B --> B1[基础命令]
C --> C1[用户程序]
D --> D1[配置文件]
E --> E1[进程信息]
F --> F1[系统信息]
G --> G1[设备文件]
H --> H1[临时文件]
I --> I1[可变数据]
5.2 RootFS 准备方法
5.2.1 使用 busybox
# 1. 下载 busybox
wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
# 2. 创建 rootfs 目录
mkdir -p /tmp/rootfs
# 3. 复制 busybox
cp busybox-x86_64 /tmp/rootfs/busybox
chmod +x /tmp/rootfs/busybox
# 4. 创建符号链接
cd /tmp/rootfs
./busybox --install -s .
# 5. 创建必要目录
mkdir -p proc sys dev tmp var
# 6. 创建必要文件
echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
echo "root:x:0:" > etc/group
5.2.2 使用 debootstrap
# 1. 安装 debootstrap
apt-get install debootstrap
# 2. 创建 rootfs
debootstrap --arch=amd64 focal /tmp/rootfs
# 3. 进入 rootfs
chroot /tmp/rootfs /bin/bash
# 4. 安装必要软件
apt-get update
apt-get install -y vim curl wget
5.2.3 使用 Docker 导出
# 1. 运行容器
docker run -d --name temp-container ubuntu:20.04 sleep 3600
# 2. 导出容器文件系统
docker export temp-container > rootfs.tar
# 3. 解压到 rootfs 目录
mkdir -p /tmp/rootfs
tar -xf rootfs.tar -C /tmp/rootfs
# 4. 清理临时容器
docker rm -f temp-container
5.3 RootFS 配置
5.3.1 基础配置
#!/bin/bash
# 配置 RootFS
ROOTFS="/tmp/rootfs"
# 1. 创建必要目录
mkdir -p $ROOTFS/{bin,usr,etc,proc,sys,dev,tmp,var,home,root}
# 2. 创建必要文件
cat > $ROOTFS/etc/passwd << 'EOF'
root:x:0:0:root:/root:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/false
EOF
cat > $ROOTFS/etc/group << 'EOF'
root:x:0:
nogroup:x:65534:
EOF
# 3. 创建主机名文件
echo "container" > $ROOTFS/etc/hostname
# 4. 创建 hosts 文件
cat > $ROOTFS/etc/hosts << 'EOF'
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
EOF
# 5. 创建 resolv.conf
cat > $ROOTFS/etc/resolv.conf << 'EOF'
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
echo "RootFS 配置完成"
5.3.2 权限配置
# 1. 设置文件权限
chmod 644 $ROOTFS/etc/passwd
chmod 644 $ROOTFS/etc/group
chmod 644 $ROOTFS/etc/hostname
chmod 644 $ROOTFS/etc/hosts
chmod 644 $ROOTFS/etc/resolv.conf
# 2. 设置目录权限
chmod 755 $ROOTFS/bin
chmod 755 $ROOTFS/usr
chmod 755 $ROOTFS/etc
chmod 755 $ROOTFS/proc
chmod 755 $ROOTFS/sys
chmod 755 $ROOTFS/dev
chmod 1777 $ROOTFS/tmp
chmod 755 $ROOTFS/var
chmod 755 $ROOTFS/home
chmod 700 $ROOTFS/root
六、文件系统隔离实战
6.1 完整隔离环境
#!/bin/bash
# 创建完整的文件系统隔离环境
echo "=== 创建文件系统隔离环境 ==="
# 1. 准备 rootfs
ROOTFS="/tmp/container-rootfs"
mkdir -p $ROOTFS
# 2. 使用 busybox 创建最小 rootfs
if [ ! -f "$ROOTFS/busybox" ]; then
wget -q https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64 -O $ROOTFS/busybox
chmod +x $ROOTFS/busybox
cd $ROOTFS
./busybox --install -s .
fi
# 3. 创建必要目录和文件
mkdir -p $ROOTFS/{proc,sys,dev,tmp,etc}
echo "root:x:0:0:root:/root:/bin/sh" > $ROOTFS/etc/passwd
echo "root:x:0:" > $ROOTFS/etc/group
echo "container" > $ROOTFS/etc/hostname
# 4. 创建隔离环境
unshare -U -p -m -n -i -C -f bash << 'EOF'
echo "=== 进入隔离环境 ==="
# 5. 重新挂载 /proc
mount -t proc proc /proc
# 6. 重新挂载 /sys
mount -t sysfs sysfs /sys
# 7. 重新挂载 /dev
mount -t devtmpfs devtmpfs /dev
# 8. 创建必要设备文件
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/random c 1 8
mknod /dev/urandom c 1 9
# 9. 切换根目录
mount --bind /tmp/container-rootfs /tmp/container-rootfs
cd /tmp/container-rootfs
mkdir -p oldroot
pivot_root . oldroot
umount -l /oldroot
# 10. 重新挂载特殊文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
# 11. 设置主机名
hostname container
# 12. 验证隔离效果
echo "=== 验证隔离效果 ==="
echo "当前目录: $(pwd)"
echo "主机名: $(hostname)"
echo "进程ID: $$"
echo "挂载点:"
mount | head -5
echo "网络接口:"
ip addr show 2>/dev/null || echo "无网络接口"
echo "文件系统:"
ls -la /
echo "=== 隔离环境创建完成 ==="
echo "输入 'exit' 退出隔离环境"
EOF
6.2 Go 代码实现
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
"golang.org/x/sys/unix"
)
func createIsolatedEnvironment(rootfs string) error {
// 1. 创建所有 namespace
flags := syscall.CLONE_NEWUTS |
syscall.CLONE_NEWPID |
syscall.CLONE_NEWNS |
syscall.CLONE_NEWNET |
syscall.CLONE_NEWIPC |
syscall.CLONE_NEWUSER |
syscall.CLONE_NEWCGROUP
// 2. 准备子进程命令
cmd := exec.Command("/bin/bash")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: flags,
}
// 3. 设置标准输入输出
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// 4. 启动子进程
if err := cmd.Start(); err != nil {
return fmt.Errorf("启动子进程失败: %v", err)
}
// 5. 等待子进程完成
return cmd.Wait()
}
func setupFilesystem(rootfs string) error {
// 1. 重新挂载 /proc
if err := unix.Mount("proc", "/proc", "proc", 0, ""); err != nil {
return fmt.Errorf("挂载 /proc 失败: %v", err)
}
// 2. 重新挂载 /sys
if err := unix.Mount("sysfs", "/sys", "sysfs", 0, ""); err != nil {
return fmt.Errorf("挂载 /sys 失败: %v", err)
}
// 3. 重新挂载 /dev
if err := unix.Mount("devtmpfs", "/dev", "devtmpfs", 0, ""); err != nil {
return fmt.Errorf("挂载 /dev 失败: %v", err)
}
// 4. 创建必要设备文件
devices := []struct {
name string
mode uint32
dev int
}{
{"/dev/null", syscall.S_IFCHR | 0666, 0x0103},
{"/dev/zero", syscall.S_IFCHR | 0666, 0x0105},
{"/dev/random", syscall.S_IFCHR | 0666, 0x0108},
{"/dev/urandom", syscall.S_IFCHR | 0666, 0x0109},
}
for _, device := range devices {
if err := unix.Mknod(device.name, device.mode, device.dev); err != nil {
return fmt.Errorf("创建设备文件 %s 失败: %v", device.name, err)
}
}
// 5. 切换根目录
if err := pivotRoot(rootfs); err != nil {
return fmt.Errorf("切换根目录失败: %v", err)
}
return nil
}
func pivotRoot(newroot string) error {
// 1. 绑定挂载 newroot
if err := unix.Mount(newroot, newroot, "", unix.MS_BIND|unix.MS_REC, ""); err != nil {
return fmt.Errorf("绑定挂载 newroot 失败: %v", err)
}
// 2. 创建 put_old 目录
putold := "/.oldroot"
if err := os.MkdirAll(putold, 0700); err != nil {
return fmt.Errorf("创建 put_old 目录失败: %v", err)
}
// 3. 执行 pivot_root
if err := unix.PivotRoot(newroot, putold); err != nil {
return fmt.Errorf("pivot_root 失败: %v", err)
}
// 4. 切换到新根目录
if err := os.Chdir("/"); err != nil {
return fmt.Errorf("切换工作目录失败: %v", err)
}
// 5. 卸载原根目录
if err := unix.Unmount(putold, unix.MNT_DETACH); err != nil {
return fmt.Errorf("卸载原根目录失败: %v", err)
}
// 6. 删除 put_old 目录
if err := os.RemoveAll(putold); err != nil {
return fmt.Errorf("删除 put_old 目录失败: %v", err)
}
return nil
}
func main() {
rootfs := "/tmp/container-rootfs"
// 检查 rootfs 是否存在
if _, err := os.Stat(rootfs); os.IsNotExist(err) {
fmt.Printf("RootFS 不存在: %s\n", rootfs)
fmt.Println("请先创建 RootFS")
os.Exit(1)
}
// 创建隔离环境
if err := createIsolatedEnvironment(rootfs); err != nil {
fmt.Printf("创建隔离环境失败: %v\n", err)
os.Exit(1)
}
}
七、调试技巧
7.1 文件系统调试
# 1. 查看挂载点
mount | grep -E "(proc|sys|dev)"
cat /proc/mounts
# 2. 查看文件系统类型
df -T
lsblk
# 3. 查看设备文件
ls -la /dev/
ls -la /dev/pts/
# 4. 查看进程文件系统
ls -la /proc/self/
cat /proc/self/mountinfo
7.2 权限调试
# 1. 查看文件权限
ls -la /etc/passwd
ls -la /etc/group
# 2. 查看目录权限
ls -ld /tmp
ls -ld /var
# 3. 查看设备文件权限
ls -la /dev/null
ls -la /dev/zero
7.3 常见问题排查
7.3.1 挂载点问题
# 问题:无法挂载 /proc
# 解决:检查权限和文件系统类型
sudo mount -t proc proc /proc
# 问题:挂载点不生效
# 解决:检查 Mount Namespace
unshare -m bash
mount -t proc proc /proc
7.3.2 权限问题
# 问题:无法访问文件
# 解决:检查文件权限
chmod 644 /etc/passwd
chmod 644 /etc/group
# 问题:无法创建设备文件
# 解决:检查权限和类型
sudo mknod /dev/null c 1 3
八、验证检查清单
基础理解
- [ ] 理解 Mount Namespace 的作用和原理
- [ ] 掌握 chroot 和 pivot_root 的区别
- [ ] 了解特殊文件系统的作用
- [ ] 理解文件系统隔离的原理
实践能力
- [ ] 能够创建和配置 Mount Namespace
- [ ] 能够使用 chroot 和 pivot_root
- [ ] 能够准备和管理 RootFS
- [ ] 能够配置特殊文件系统
调试技能
- [ ] 掌握文件系统调试技术
- [ ] 能够排查权限问题
- [ ] 能够解决挂载点问题
- [ ] 理解文件系统隔离的最佳实践
相关链接
- 02-Namespace隔离机制 - 进程隔离技术
- 09-OverlayFS镜像分层 - 镜像分层技术
- 14-调试技术与工具 - 调试技术详解
下一步:让我们学习 OverlayFS 镜像分层技术,这是容器存储的高级应用!