学习目标
完成本课后,你将能够:
- 使用
unshare命令手动创建进程隔离环境,亲眼验证 Namespace 的效果 - 使用 Cgroups 手动限制进程的 CPU 和内存,观察超限后进程被杀死
- 理解 OverlayFS 分层挂载的工作原理
- 认识到容器不是黑魔法,而是内核特性的组合
前置知识
- Lesson 01 中关于 Namespace / Cgroups / OverlayFS 的概念
1. 用 unshare 亲手创建 Namespace 隔离
unshare 是 Linux 自带的命令,可以让我们手动创建新的 Namespace 并在其中运行进程。
实验 2.1:PID Namespace —— 让进程以为自己是 1 号
bash# 在宿主机上先看看当前的进程 ID
echo "当前 shell 的 PID: $$"
# 创建新的 PID + Mount Namespace 并启动一个 bash
sudo unshare --pid --mount --fork bash
# 现在我们在新的 Namespace 里了
# 重新挂载 /proc 让 ps 命令读取新 Namespace 的进程信息
mount -t proc proc /proc
# 查看进程列表
ps aux
预期结果:你会发现 ps aux 只显示了 2 个进程(bash 本身和 ps 命令),而且 bash 的 PID 变成了 1。在宿主机视角,这个 bash 可能是 PID 58392,但在新 Namespace 里,它"以为"自己是整个世界的 1 号进程。
bash# 退出隔离环境
exit
[!WARNING] 退出前不要忘记执行
exit。如果在隔离环境内做了破坏性操作(比如删除文件),由于 Mount Namespace 的存在,影响范围取决于你是否共享了宿主机的文件系统挂载。
实验 2.2:Network Namespace —— 独立的网络栈
bash# 创建一个名为 "test-ns" 的网络命名空间
sudo ip netns add test-ns
# 在这个独立的网络空间里查看网卡
sudo ip netns exec test-ns ip addr
# 对比宿主机的网卡
ip addr
预期结果:在 test-ns 中只有一个 lo(回环)网卡,且处于 DOWN 状态。它完全感知不到宿主机的 eth0 网卡。这就是每个 Pod 拥有独立 IP 地址的底层原理。
bash# 清理
sudo ip netns delete test-ns
为什么这很重要?
Kubernetes 中的每一个 Pod 实际上就是运行在独立 Namespace 中的一组进程。当你理解了 unshare 的原理,你就能理解:
- 为什么 Pod 内的容器可以通过
localhost互相通信(它们共享同一个 Network Namespace) - 为什么不同 Pod 之间需要通过 IP 地址通信(它们在不同的 Network Namespace 中)
- 为什么容器内看到的进程列表和宿主机不一样(PID Namespace 隔离)
2. 用 Cgroups 手动限制进程资源
Cgroups(Control Groups)是 Linux 内核提供的资源配额机制。在现代 Linux 系统中(Ubuntu 22.04+),使用的是 Cgroups v2。
实验 2.3:限制进程的内存上限
bash# 查看当前系统的 cgroup 版本
mount | grep cgroup
# 创建一个 cgroup 子组来限制内存
sudo mkdir -p /sys/fs/cgroup/memory-test
# 设置内存上限为 50MB
echo "52428800" | sudo tee /sys/fs/cgroup/memory-test/memory.max
# 在这个受限的 cgroup 中启动一个进程尝试分配 100MB 内存
sudo bash -c 'echo $$ > /sys/fs/cgroup/memory-test/cgroup.procs && python3 -c "
data = []
for i in range(100):
data.append(\"x\" * 1024 * 1024) # 每次分配 1MB
print(f\"已分配 {i+1} MB\")
"'
预期结果:当进程尝试分配超过 50MB 的内存时,Linux 内核会直接将它杀死。你会看到 Killed 输出。这就是 Kubernetes 中 OOMKilled(Out Of Memory Killed)的底层原理。
[!IMPORTANT] 在 Kubernetes 中,当你为 Pod 设置
resources.limits.memory: 512Mi时,K8s 底层就是通过 Cgroups 来设置这个内存上限。超出限制的进程会被内核无条件杀死——这不是 K8s 的功能,而是 Linux 内核的行为。
bash# 清理
sudo rmdir /sys/fs/cgroup/memory-test 2>/dev/null
实验 2.4:限制进程的 CPU 使用率
bash# 创建 CPU 限制的 cgroup
sudo mkdir -p /sys/fs/cgroup/cpu-test
# 限制为 10% 的单核 CPU(period=100000us 中只能使用 10000us)
echo "10000 100000" | sudo tee /sys/fs/cgroup/cpu-test/cpu.max
# 在受限的 cgroup 中运行一个 CPU 密集型任务
sudo bash -c 'echo $$ > /sys/fs/cgroup/cpu-test/cgroup.procs && dd if=/dev/zero of=/dev/null bs=1M' &
BG_PID=$!
# 观察它的 CPU 使用率(应该被限制在 10% 左右)
sleep 2 && top -b -n 1 -p $BG_PID | tail -2
# 清理
sudo kill $BG_PID 2>/dev/null
sudo rmdir /sys/fs/cgroup/cpu-test 2>/dev/null
预期结果:即使 dd 想全力运行,它的 CPU 使用率也被锁死在约 10%。这就是 K8s 中 resources.limits.cpu 的底层实现。
3. OverlayFS 分层文件系统实操
OverlayFS 是容器镜像"千层饼"结构的底层支撑。我们可以手动搭建一个来理解它。
实验 2.5:手工构建分层文件系统
bash# 创建实验目录
mkdir -p /tmp/overlay-demo/{lower,upper,work,merged}
# lower 层 = 只读的基础镜像层
echo "我是基础层的文件,不可修改" > /tmp/overlay-demo/lower/base.txt
echo "我是共享的配置文件" > /tmp/overlay-demo/lower/config.txt
# 使用 OverlayFS 把 lower(只读层)和 upper(可写层)合并到 merged(视图层)
sudo mount -t overlay overlay \
-o lowerdir=/tmp/overlay-demo/lower,upperdir=/tmp/overlay-demo/upper,workdir=/tmp/overlay-demo/work \
/tmp/overlay-demo/merged
# 在合并视图中查看文件——两层的文件都可见
ls -la /tmp/overlay-demo/merged/
# 在合并视图中修改一个"只读层"的文件
echo "我被修改了!" >> /tmp/overlay-demo/merged/config.txt
# 验证:原始的只读层完全没变(Copy-on-Write 机制)
echo "=== 只读层原始文件 ==="
cat /tmp/overlay-demo/lower/config.txt
echo "=== 可写层的副本(修改写在这里)==="
cat /tmp/overlay-demo/upper/config.txt
预期结果:
lower/config.txt内容不变(只读层被保护)upper/config.txt出现了修改后的内容(Copy-on-Write:第一次写入时复制到上层)
这就是容器镜像的工作原理:
lower= 镜像的只读层(基础 OS + 应用程序)upper= 容器运行时的可写层merged= 容器进程实际看到的文件系统- 100 个容器共享同一个
lower,各自有独立的upper
bash# 清理
sudo umount /tmp/overlay-demo/merged
rm -rf /tmp/overlay-demo
[!NOTE] 当容器被删除时,它的
upper(可写层)一起被销毁,所有在容器运行期间写入的数据全部丢失。这就是为什么在后续课程中我们要学习 PV(持久卷)来保存重要数据。
4. 综合理解:容器 = Namespace + Cgroups + OverlayFS
现在你应该能清晰地理解:
容器 = Namespace(隔离视线) + Cgroups(限制资源) + OverlayFS(分层文件系统)
Docker 和 Containerd 做的事情,本质上就是把上面三个内核特性的操作封装成了简单的命令行工具。当你执行 docker run nginx 时,底层发生的就是:
- 用 OverlayFS 组装 Nginx 镜像的分层文件系统
- 用
clone()系统调用创建新的 Namespace - 用 Cgroups 设置资源限制
- 在隔离环境中启动 Nginx 进程
检查点
- 能用
unshare创建 PID 隔离的环境,并验证进程看到的 PID 变为 1 - 能用 Cgroups 限制进程内存,观察到 OOMKilled 现象
- 能解释 OverlayFS 的 Copy-on-Write 机制
- 能说清楚"容器 = Namespace + Cgroups + OverlayFS"
面试高频题
Q:请解释容器的 Copy-on-Write 机制,它对生产环境有什么影响?
满分答案思路:
- 容器镜像由多个只读层组成,容器启动时在最上层添加一个可写层
- 当容器修改只读层的文件时,先将文件复制到可写层再修改(Copy-on-Write)
- 生产影响:频繁修改大文件(如数据库)的写入性能较差,因为每次修改都要先复制。所以数据库等有状态应用应使用外部持久化存储(Volume),而不是直
Comments NOTHING