Docker 的基石之一:手动体验 chroot 文件系统隔离
聊到 Docker 容器,大家最常听到的词就是“隔离”。容器能把应用和它的依赖打包在一起,并在隔离的环境中运行。但这隔离到底是怎么实现的呢?虽然现代 Docker 运用了 Linux 内核的多种高级特性(比如 Namespaces 和 Cgroups),但理解一个更早、更简单的技术——chroot(Change Root,改变根目录),能帮我们极好地理解文件系统隔离这个核心概念。
下面,我们就跟着之前的命令行操作,一步步手动构建一个 chroot 环境,亲身体验一下文件系统“障眼法”的魅力。
什么是 chroot?
chroot 是 Unix/Linux 系统上的一个系统调用和命令。它的作用是改变一个正在运行的进程及其子进程眼中的“根目录”(/)。当一个进程被 chroot 到某个目录(比如 /my/new/root)后,对于这个进程来说,所有以 / 开头的路径,实际上访问的都是宿主机上的 /my/new/root 目录。它相当于为进程的文件系统访问创建了一个“监狱”(Jail)。
重要提醒: chroot 仅仅隔离了文件系统的视图。它并不能隔离进程 ID(PID)、网络接口、用户 ID,也不能限制资源使用。它本身不是一个强大的安全边界,尤其是面对有特殊权限的用户时。
手动搭建一个最小化的 chroot 环境:实战步骤
我们的目标是创建一个独立的目录,里面只包含运行一个基本 Shell (bash) 和一个简单命令 (ls) 所需的最少组件。
1. 创建“监狱”目录:
这个目录将成为我们 chroot 进程的新根目录 /。
# 这是我们的新根目录
mkdir oldboyedu-linux92
2. 拷贝基础程序(二进制文件):
我们需要一个 Shell 来交互,还需要一个命令来测试。
# 在“监狱”里创建标准的 'bin' 目录
mkdir oldboyedu-linux92/bin
# 拷贝 bash 这个 Shell 程序
cp /bin/bash oldboyedu-linux92/bin/
# 拷贝 ls 这个列目录程序
# 先找到 ls 在哪里: which ls (输出可能是 /usr/bin/ls 或 /bin/ls)
cp /usr/bin/ls oldboyedu-linux92/bin/ # 根据实际 `which ls` 的结果调整路径
3. 最关键的一步:拷贝依赖环境(共享库):
Linux 下大部分程序都不是完全独立的,它们需要依赖外部的共享库文件(.so 文件 - Shared Objects)才能运行。这些库包含了许多程序都会用到的通用代码。当我们 chroot 之后,进程只能看到新根目录里的文件,所以我们必须把所有需要的库文件也拷贝到“监狱”里。
ldd 命令是查找依赖的好帮手,它可以列出一个程序运行时需要哪些动态库。
# 查看 bash 需要哪些库
ldd /bin/bash
# 输出可能类似(不同系统和版本会不同):
# linux-vdso.so.1 (...)
# libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (...)
# libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (...)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (...)
# /lib64/ld-linux-x86-64.so.2 (...) <--- 这个是动态链接器/加载器本身!非常重要!
# 查看 ls 需要哪些库
ldd /usr/bin/ls # (或 /bin/ls)
# 输出可能类似:
# linux-vdso.so.1 (...)
# libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (...)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (...)
# libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (...)
# libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (...)
# /lib64/ld-linux-x86-64.so.2 (...)
# libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (...)
# 在“监狱”里创建对应的库目录
mkdir oldboyedu-linux92/lib
mkdir oldboyedu-linux92/lib64 # 通常用来放 64 位库和动态加载器
# 根据 ldd 的输出,把 bash 和 ls 需要的 *所有* 库都拷贝进去
# 注意:要使用你自己系统上 ldd 输出显示的真实路径!
# bash 需要的库:
cp /lib/x86_64-linux-gnu/libtinfo.so.6 oldboyedu-linux92/lib/
cp /lib/x86_64-linux-gnu/libdl.so.2 oldboyedu-linux92/lib/
cp /lib/x86_64-linux-gnu/libc.so.6 oldboyedu-linux92/lib/
cp /lib64/ld-linux-x86-64.so.2 oldboyedu-linux92/lib64/
# ls 需要的库 (只需拷贝 bash 没覆盖到的):
cp /lib/x86_64-linux-gnu/libselinux.so.1 oldboyedu-linux92/lib/
cp /lib/x86_64-linux-gnu/libpcre2-8.so.0 oldboyedu-linux92/lib/
cp /lib/x86_64-linux-gnu/libpthread.so.0 oldboyedu-linux92/lib/
# 注意:像 libc.so.6, libdl.so.2, ld-linux-x86-64.so.2 这些库 bash 也需要,之前应该已经拷贝过了。
# 验证一下目录结构
ll -R oldboyedu-linux92/
为什么依赖库这么重要? 如果没有这些 .so 文件,bash 和 ls 启动时会找不到需要的功能代码,直接报错退出,常见的错误是 "file not found" 或者 "error while loading shared libraries"。
4. 切换根目录,进入“监狱”:
现在,使用 chroot 命令。我们告诉它:用 oldboyedu-linux92/ 作为新的根目录,并且在新根目录下运行 /bin/bash 这个程序。
# 执行 chroot 命令。通常需要 root 权限
sudo chroot oldboyedu-linux92/ /bin/bash
# 或者如果你已经是 root 用户:
# chroot oldboyedu-linux92/ /bin/bash
# 成功后,你的命令提示符会变化,可能变成类似这样:
# bash-5.0#
在“监狱”内部:
- 试试运行
ls /。你会看到bin,lib,lib64这些目录,也就是宿主机上oldboyedu-linux92目录的内容,而不是宿主机真正的根目录/下的内容! - 试试运行
ls -l或者for i in "haha" "xixi"; do echo $i ; done。这些命令现在都能正常工作了,因为ls、bash以及它们依赖的库都在这个“监狱”里。 - 试试运行一个你没有拷贝进去的命令,比如
pwd或者cat(除非你的ls或bash依赖它们,否则大概率没拷贝)。你会得到 "command not found" 的错误,因为它在这个受限的文件系统里找不到对应的程序。
5. 模拟多个“容器”:
chroot 本身只改变了文件系统视图。如果我们复制这个“监狱”目录,并再次 chroot 到新的目录,会发生什么?
# (首先需要退出之前的 chroot 环境,输入 exit)
exit
# 复制整个"监狱"目录
cp -r oldboyedu-linux92 oldboyedu-linux93
# 在第一个终端,进入第一个"监狱"
sudo chroot oldboyedu-linux92/ /bin/bash
# bash-5.0# echo "haha" > /haha.txt
# bash-5.0# ls /
# bin haha.txt lib lib64
# 打开第二个终端,进入第二个"监狱"
sudo chroot oldboyedu-linux93/ /bin/bash
# bash-5.0# echo "xixi" > /xixi.log
# bash-5.0# ls /
# bin lib lib64 xixi.log
观察结果:
- 在第一个
chroot环境里,你创建了haha.txt,它位于这个环境的/下。 - 在第二个
chroot环境里,你创建了xixi.log,它位于这个环境的/下。 - 这两个环境的文件系统是相互隔离的。第一个环境看不到
xixi.log,第二个环境看不到haha.txt。
6. 数据到底在哪里?
现在,在宿主机上(不要在 chroot 环境里)查看:
# 退出所有 chroot 环境
# exit
# exit
# 查看宿主机的真实根目录,这里没有 haha.txt 或 xixi.log
ls /
# 查看我们创建的两个"监狱"目录
ls /root/oldboyedu-linux92/ # 假设你的"监狱"在 /root 下创建的
# 输出: bin haha.txt lib lib64
ls /root/oldboyedu-linux93/
# 输出: bin lib lib64 xixi.log
真相大白:
chroot 进程创建的文件,实际上是存储在宿主机上你指定的那个“监狱”目录里的!chroot 只是改变了进程看待文件系统的“视角”,并没有改变文件实际存储的位置。
chroot 与 Docker 的关系
- 概念基石:
chroot完美地演示了文件系统隔离的基本思想,这是容器技术的核心特征之一。Docker 容器镜像的层叠文件系统(OverlayFS 等)可以看作是chroot概念的极其复杂和高效的实现。当你docker run一个容器时,Docker 会准备一个类似chroot环境的根文件系统,但使用的是更先进的文件系统技术。 - 不只是
chroot: Docker 提供的隔离远不止文件系统。它还使用了 Linux Namespaces 来隔离:- PID Namespace: 每个容器有自己独立的进程树,容器内的 1 号进程是容器的入口进程,看不到宿主机的其他进程。
- Network Namespace: 每个容器有自己独立的网络栈(IP 地址、路由表、端口等)。
- Mount Namespace: 每个容器有自己独立的文件系统挂载点视图(这是对
chroot的极大增强)。 - UTS Namespace: 独立的主机名和域名。
- IPC Namespace: 独立的进程间通信资源。
- User Namespace: 独立的 User ID 和 Group ID 映射(可以将容器内的 root 用户映射为宿主机上的非特权用户,提升安全性)。
- 资源限制: Docker 还使用 Control Groups (Cgroups) 来限制容器可以使用的资源(CPU、内存、磁盘 I/O 等),这是
chroot完全不具备的能力。
结论:
chroot 是一个理解容器文件系统隔离原理的绝佳入门工具。它让我们直观地看到,通过改变进程的根目录,可以创建一个看似独立的文件环境。然而,它提供的隔离非常有限。Docker 则在此基础上,结合了 Namespaces 和 Cgroups 等现代 Linux 内核特性,提供了更为全面和安全的隔离机制,构成了我们今天使用的强大容器技术。
希望这个结合实例的讲解能帮助你更深入地理解 Docker 背后的技术原理!
浙公网安备 33010602011771号