手工模拟一个容器
光有隔离(Namespace)还不够,容器还要有独立的文件视图和网络设备,这才是完整的 “手工容器”。你会发现,虽然用了--pid隔离,但如果不挂载新的 proc,执行ps -ef还是能看到宿主机进程。原理/proc目录是内核给进程看的资源列表。操作:效果:重新执行ps -ef你只能看到自己的 bash 进程了!PID 命名空间彻底生效。“进程本质上是 /proc 挂载点中的文件描述符”。容器需要
一、阶段 1:没有隔离的 “假容器”(先看问题,再看解决)
1. 实验核心动作:导出 Alpine 容器文件系统,手工解压
-
操作:用
docker export conns -o dockercontainer.tar导出一个运行中的 Alpine 容器,解压到rootfs目录。 -
目的:我们要手工造一个容器的根文件系统(Rootfs)。这是容器能跑的基础,相当于把 “操作系统的根目录” 单独拿出来了。
-
效果:
rootfs里包含了bin、etc、lib等所有 Linux 基础目录,看起来像一个独立的 Linux 系统。
2. 关键测试:用 chroot 切换根目录
sudo chroot $PWD/rootfs ash
-
现象:你进入了一个新的 Shell,执行
ls能看到bin、dev等目录。 -
问题:
虽然你看到了独立的文件系统,但你并没有被隔离!
-
执行
ip link:能看到宿主机的网卡(ens33、docker0),网络没隔离; -
执行
ps -ef:能看到宿主机的所有进程(systemd、kthreadd),进程没隔离; -
执行
id:虽然你在容器里显示是 root,但实际你还是宿主机的 user1 用户,权限没有隔离。
-
结论:
chroot只是改变了当前目录的根视图,没有做任何隔离。 这是早期的简陋容器技术,安全性极差。
二、阶段 2:真正的隔离核心 —— unshare 命令(Namespace 隔离)
1. 核心概念:Linux Namespace(命名空间)
容器之所以叫 “容器”,核心就是 Namespace。它相当于给进程加了 “视觉滤镜”,让进程只能看到属于自己的那部分资源。
6 种最核心的隔离类型(对应 unshare 参数):
| Namespace 类型 | 隔离内容 | 图中对应参数 | 大白话解释 |
|---|---|---|---|
| PID | 进程 ID | --pid |
容器里的 PID 是 1,看不到宿主机进程 |
| NET | 网络栈 | --net |
容器有独立的 IP、网卡,看不到宿主机网卡 |
| UTS | 主机名、域名 | --uts |
容器可以有独立的主机名,不影响宿主机 |
| IPC | 进程间通信 | --ipc |
容器内进程只能和自己通信 |
| MNT | 挂载点 | --mount |
容器有独立的文件系统挂载视图 |
| USER | 用户 / 组 ID | --user |
容器内的 root 映射到宿主机的普通用户,更安全 |
2. 实验动作:用 unshare 创建隔离环境
unshare --mount --uts --ipc --net --pid --fork --map-root-user /bin/bash
-
--fork:创建一个子进程来执行 bash; -
--map-root-user:把容器内的 UID 0(root)映射到宿主机的 UID,虽然容器内是 root,实际宿主机看是普通用户,安全隔离; -
参数组合:同时开启了 5 种隔离,这就是容器的核心底层。
3. 效果验证:此时你进入了一个真正的隔离环境
-
网络隔离(NET):执行
ip link,只能看到lo(本地回环),看不到宿主机的 ens33/docker0 了! -
进程隔离(PID):执行
ps -ef,只能看到当前的 bash 进程(PID 1),看不到宿主机的 systemd 等进程了! -
用户隔离(USER):执行
id,显示 uid=0 (root),但是宿主机上看这个进程,它还是 user1 用户执行的。
三、阶段 3:完善 Rootfs 与资源隔离(补全最后两块拼图)
光有隔离(Namespace)还不够,容器还要有独立的文件视图和网络设备,这才是完整的 “手工容器”。
1. 进程隔离的最后一块拼图:Proc 文件系统
你会发现,虽然用了 --pid 隔离,但如果不挂载新的 proc,执行 ps -ef 还是能看到宿主机进程。
-
原理:
/proc目录是内核给进程看的资源列表。 -
操作:
mount -t proc proc /proc
-
效果:重新执行
ps -ef
你只能看到自己的 bash 进程了!
PID 命名空间彻底生效。
“进程本质上是 /proc 挂载点中的文件描述符”。
2. 网络隔离的最后一块拼图:Veth Pair(虚拟网卡对)
容器需要独立的网络,但又要能和宿主机通信。所以用 Veth Pair(虚拟网线)。
-
原理:Veth Pair 就像一根虚拟的网线,两端分别连接 “容器” 和 “宿主机网桥”。
-
操作:
# 1. 创建一对虚拟网卡 sudo ip link add vethLocal type veth peer name vethContainer # 2. 把其中一端放入容器的网络命名空间 sudo ip link set vethContainer netns 3585 # 3585是unshare进程的PID # 3. 在容器内查看 ip link
-
效果:在容器里执行
ip link
,会看到
vethContainer
这个新网卡出现了。
这就是容器的网络结构:一端在容器(vethContainer),一端在宿主机(vethLocal),通过这根 “虚拟线” 连通。
四、完整总结:手工容器 vs Docker 容器
| 实验步骤(手工) | Docker 容器(实际) | 核心作用 |
|---|---|---|
| 解压 rootfs | 镜像层(layer.tar) | 提供容器的根文件系统(Rootfs) |
| chroot 切换根 | Overlay2 挂载 | 让容器看到独立的文件视图 |
| unshare --mount/uts/ipc/net/pid | Linux Namespace | 核心隔离! 让进程看不到宿主机资源 |
| 挂载 proc | Docker 自动挂载 | 让 ps 命令只显示当前容器进程 |
| ip link add veth pair | Docker 网络 Bridge | 给容器配独立网卡,连通宿主机 |
| chroot 启动进程 | runc 启动容器 | 最终生成一个独立运行的容器进程 |
容器本质上不是虚拟机,容器本质就是一个被
unshare命令隔离了资源(PID/Net/IPC 等),并且有独立 Rootfs 的普通 Linux 进程。Docker 只是把这些复杂的 Linux 命令(unshare、chroot、mount)封装成了一个好用的工具,让我们不用手工敲命令就能跑容器而已。
五、常见误区与核心考点
-
为什么不直接用 chroot 而要用 unshare?
-
因为 chroot 只是改了目录,没有隔离。黑客如果突破了 chroot 根目录,就能直接访问宿主机文件;而 unshare 生成的新命名空间,是真正的独立环境,突破难度极大。
-
-
PID 1 的含义
-
在手工容器里,
ps -ef看到的第一个进程是 PID 1。这就是容器的 “init 进程”,如果这个进程挂了,容器就会退出。K8s 的 Pod 管理,本质就是管理这组 PID 命名空间下的进程。
-
-
Veth Pair 的作用
-
它是连接容器和宿主机的桥梁。没有 Veth Pair,容器就是孤岛(只有 lo 接口),连不上网;有了它,容器就能通过 docker0 网桥与宿主机通信。
-
更多推荐


所有评论(0)