HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 手撸容器系统

    • 完整手撸容器技术文档系列
    • 01-容器本质与基础概念
    • 02-Namespace隔离机制
    • 03-CGroup资源控制
    • 04-Capabilities与安全机制
    • 05-容器网络原理
    • 06-网络模式与实现
    • 07-CNI插件开发
    • 08-RootFS与文件系统隔离
    • 09-OverlayFS镜像分层
    • 10-命令行手撸容器
    • 11-Go实现最小容器
    • 12-Go实现完整容器
    • 13-容器生命周期管理
    • 14-调试技术与工具
    • 15-OCI规范与标准化
    • 16-进阶场景与优化
    • 常见问题与故障排查
    • 参考资料与延伸阅读

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 镜像分层技术,这是容器存储的高级应用!

Prev
07-CNI插件开发
Next
09-OverlayFS镜像分层