许多人习惯把容器理解为“轻量级虚拟机”,但在 Linux 上,容器从根本上说只是一个普通的进程——它是借助内核已有的两项基础能力(命名空间和控制组)为进程加上“围墙”和“天花板”而实现的。宿主机的同一个内核直接运行着所有容器进程,没有任何额外的模拟层或中间操作系统。
1. 命名空间(namespace):让进程只能看到自己的“世界”
Linux 命名空间负责为进程创建独立、隔离的资源视图。当创建一个新容器(比如用 docker run)时,底层的 clone() 系统调用会在创建新进程的同时,为它指定一系列命名空间标志。目前 Linux 提供了八种命名空间,最核心的几种是:
- Mount(mnt):容器拥有独立的文件系统挂载点视图,正是这个命名空间使容器只能看到属于自己的根文件系统(rootfs),而看不到宿主机的
/目录。 - PID:容器内进程看到的 PID 编号从 1 开始,形成一棵独立的进程树,而不会看到宿主机上的其他进程。容器内的 PID 1 就是该容器的“init”进程。
- Network(net):容器拥有独立的网络栈(网卡、IP 地址、路由表、防火墙规则等),从而让多个容器可以各自监听相同的端口而不会冲突。
- UTS:允许容器拥有自己的主机名和域名。
- IPC:隔离进程间通信资源(如信号量、消息队列、共享内存)。
- User:容器内可以使用与宿主机不同的用户/组 ID 映射,进一步提升安全性。
- Cgroup:为容器提供独立的 cgroup 文件系统视图,避免容器感知到宿主机的资源限制层级。
可以用 lsns 命令查看系统中所有命名的空间,或通过 /proc/<PID>/ns/ 目录查看某个进程所处的命名空间标识。在宿主机上运行 ps aux,你可以看到容器内的 Web 服务进程——它和任何普通进程长一模一样,只是在不同的命名空间里“坐井观天”。
2. 控制组(cgroup):给进程加上资源使用的天花板
如果命名空间解决的是“看得到什么”的问题,控制组则解决“能用多少”的问题。cgroup 通过伪文件系统(/sys/fs/cgroup/)将进程分组,并对每组实施 CPU、内存、磁盘 I/O、网络等资源的限制或权重分配。
例如,启动一个容器时指定 --memory=256m,底层操作大致是:
- 在
/sys/fs/cgroup/memory/下创建或使用一个子目录(比如docker/<容器ID>); - 将容器内的进程 PID 写入该目录的
cgroup.procs文件; - 向
memory.limit_in_bytes文件写入268435456(256MB 的字节值)。
内核会立即施加这些限制:如果容器内的进程试图使用超过 256MB 的物理内存,内核的 OOM Killer 就会介入,杀掉越界的进程。同理,CPU 权重、块设备 IOPS 限制、网络优先级等都可以通过修改对应 cgroup 子系统的参数文件来动态调整,无需重启容器。
3. 容器 = 命名空间 + cgroup + rootfs
从实现的角度来看,容器就是被一组命名空间隔离、受一组 cgroup 限制的普通进程,再配上一个独立挂载的文件树。你可以不用 Docker,直接用系统工具手动创建一个“手工容器”:
# 创建新的命名空间并指定根文件系统
unshare --mount --uts --ipc --pid --fork --mount-proc \
chroot /path/to/rootfs /bin/bash
这行命令启动的 Bash 进程就运行在隔离的 UTS、IPC、挂载和 PID 命名空间中,并使用指定的根文件系统,本质上已经相当接近一个容器的雏形。在此基础上,再通过写 cgroup 文件限制其内存和 CPU,就得到了一个完整的“容器”。
4. 这一事实引发的实用结论
- 容器不等于虚拟机:没有完整的操作系统内核在容器内启动,所以容器的启动时间接近进程启动时间(毫秒级),镜像中也不需要安装独立内核和固件。
- 进程在宿主机上直接可见:
ps、top、pidstat等工具都能看到容器进程,只不过需要留意 PID 在不同命名空间中的映射。调试时可以直接用宿主机的strace、gdb附加到容器进程上,而不必进入容器内部。 - 安全边界不等于虚拟机:容器共享宿主机内核,因此内核漏洞可能被容器突破。加固容器安全更多依赖正确配置的命名空间、cgroup、seccomp、SELinux/AppArmor,以及避免使用特权模式。
- 资源限制是刚性的(除了 CPU 软限制):超出内存限制会直接触发 OOM,不可能像虚拟机那样“用到别的虚机未用的内存”。
总而言之,容器的本质就是 Linux 的进程隔离和资源限制机制经过巧妙组合后的高级抽象。理解这一点后,排查容器故障、优化资源配额或评估安全风险时,就不必再从黑盒的角度猜测,而是可以直接用 Linux 基本功(检查 /proc、cgroup、命名空间)去分析和控制。