在同一个操作系统上运行多个服务时,我们希望它们相互不干扰——一个服务看不见其他服务的进程、文件系统、网络、用户等。传统的 chroot 只能隔离文件系统,隔离能力有限。Linux namespace 是内核提供的一种轻量级虚拟化技术,它将不同类型的全局资源封装到一个独立的命名空间中,使得进程只能看到该命名空间内的资源。
| Namespace | 隔离资源 | 作用 |
|---|---|---|
| Mount (mnt) | 挂载点列表 | 每个容器拥有独立的文件系统视图,挂载操作互不影响 |
| PID | 进程 ID 空间 | 容器内 PID 从 1 开始,看不到宿主机进程,解决了 PID 冲突 |
| Network (net) | 网络栈(设备、路由、防火墙) | 容器拥有独立的网络接口、IP、路由表等 |
| IPC | System V IPC 和 POSIX 消息队列 | 容器内部 IPC 与其他容器隔离,避免冲突 |
| UTS | 主机名和域名 | 容器可以拥有独立的主机名,不影响宿主机 |
| User | 用户 ID 和组 ID 映射 | 容器内的 root 可以映射为宿主机上的非特权用户,提升安全性 |
# 创建一个新的 PID 和 Network namespace 的 bash 进程
sudo unshare --pid --net --fork /bin/bash
# 在容器内查看 PID
ps aux
# 输出:只有当前进程和它的子进程,PID 从 1 开始
# 查看网络接口 (容器内只有 lo)
ip addr
# 输出:1: lo: ...
# 查看主机名 (容器内可设置独立主机名)
hostname container1
# 离开 namespace (退出容器)
exit
namespace 解决了「能看见什么」的问题,但无法解决「能用多少资源」的问题。一个容器如果持续占用大量 CPU 或内存,可能会影响到宿主机上其他容器的正常运行。cgroup (control group) 是 Linux 内核提供的资源控制机制,它可以对进程组进行CPU、内存、I/O、网络等资源的限制和隔离。
cgroup v2 统一层级: 所有子系统挂载在同一个控制树中,每个进程属于一个控制组,控制组可以嵌套。
# 创建容器组对应的 cgroup (cgroup v2)
sudo mkdir /sys/fs/cgroup/container
# 设置 CPU 限制 (100000 微秒内最多使用 50000 微秒)
echo '50000 100000' > /sys/fs/cgroup/container/cpu.max
# 设置内存限制 (512MB)
echo 536870912 > /sys/fs/cgroup/container/memory.max
# 将当前进程加入 cgroup
echo $$ > /sys/fs/cgroup/container/cgroup.procs
# 查看限制是否生效
cat /sys/fs/cgroup/container/cpu.max
cat /sys/fs/cgroup/container/memory.current
传统虚拟机的每个实例都拥有一份完整的文件系统,占用大量存储空间。Docker 引入了UnionFS(联合文件系统),通过分层镜像的方式,让多个容器共享同一份只读镜像层,只有差异部分写入可写层。OverlayFS 是目前最常用的 UnionFS 实现,它通过合并多个目录来提供统一视图。
OverlayFS 结构: 由 lowerdir(下层只读目录)和 upperdir(上层可写目录)组成,通过 workdir 临时工作目录,最终合并到 merged 视图。
# 创建 OverlayFS 目录结构
mkdir -p lower1 lower2 upper work merged
# 挂载 OverlayFS
sudo mount -t overlay overlay \
-o lowerdir=lower1:lower2,upperdir=upper,workdir=work \
merged
# 查看挂载结果
mount | grep overlay
# 查看容器镜像分层
docker image inspect nginx:latest
# 输出中包含 RootFS.Layers 列表
# 查看容器层
docker diff container_id
Docker 出现早期,它的镜像格式和运行时是自成一派的。其他容器运行时(如 rkt、LXC)无法直接运行 Docker 镜像,导致生态系统碎片化。2015 年,Docker 贡献了其镜像和运行时规范,成立了 OCI (Open Container Initiative),致力于制定容器行业的开放标准。
OCI 镜像规范: 定义镜像由 manifest、config、layers 组成,使用 digest 确保内容完整性。
OCI 运行时规范: 定义运行时配置文件 config.json,包含 rootfs、mounts、process、hooks、annotations 等字段。
// OCI 运行时配置文件 (config.json 片段)
{
"ociVersion": "1.0.2",
"process": {
"terminal": true,
"user": { "uid": 0, "gid": 0 },
"args": ["sh"],
"env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]
},
"root": {
"path": "rootfs",
"readonly": true
},
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc"
}
],
"hooks": { ... },
"annotations": { ... },
"linux": {
"namespaces": [
{ "type": "pid" },
{ "type": "network" },
{ "type": "mount" }
],
"resources": { ... }
}
}
每个容器拥有独立的网络命名空间,意味着它看不到其他容器的网络设备。为了让容器之间能够相互通信,并且能够访问外部网络,容器运行时必须建立网络桥接或路由。Docker 默认采用 bridge 网络模式,通过 docker0 网桥连接容器。
Docker 默认 bridge 网络模式:
# 创建自定义网络
docker network create --subnet=192.168.1.0/24 mynet
# 运行容器并连接到网络
docker run -d --name web1 --network mynet nginx
docker run -d --name web2 --network mynet nginx
# 容器之间通信 (通过容器名)
docker exec web1 ping web2
# 端口映射
docker run -d -p 8080:80 --name web nginx
# 查看网络配置
docker network inspect mynet
虚拟机 (VM) 通过 Hypervisor 模拟完整的硬件,每个 VM 运行一个独立的操作系统内核。容器则共享宿主机的内核,只隔离进程和资源。这种根本性的设计差异,导致容器在启动速度、资源利用率、性能等方面都优于虚拟机。
| 特性 | 容器 | 虚拟机 |
|---|---|---|
| 隔离级别 | 进程级 (namespace + cgroup) | 硬件级 (Hypervisor) |
| 内核 | 共享宿主机内核 | 独立内核 |
| 启动时间 | 秒级 (毫秒级可能) | 分钟级 (通常几十秒) |
| 内存占用 | MB 级别 | GB 级别 |
| 存储占用 | MB ~ GB (共享层) | GB ~ TB (完整镜像) |
| 性能损耗 | 接近原生 (≈ 0-5%) | 5-20% (硬件虚拟化开销) |
| 可迁移性 | 镜像标准化 (OCI) | 依赖 Hypervisor 格式 |
| 安全性 | 较弱 (内核共享) | 较强 (硬件隔离) |
# 容器启动 (秒级)
time docker run -d --name test nginx
# real 0m0.345s
# 虚拟机启动 (分钟级)
time virsh start vm-test
# real 0m25.123s
# 查看容器资源占用
docker stats
# 输出: CONTAINER ID NAME CPU % MEM USAGE
# 示例: 0.1% 12.5MiB
# 查看虚拟机资源占用
virt-top
# 输出: 虚拟机内存占用通常几百 MB 起步