目录
namespace 六种隔离 cgroup 资源限制 UnionFS 镜像分层 OCI 标准 容器网络模型 容器与虚拟机的区别
01

namespace 六种隔离

背景 为什么需要进程隔离?

在同一个操作系统上运行多个服务时,我们希望它们相互不干扰——一个服务看不见其他服务的进程、文件系统、网络、用户等。传统的 chroot 只能隔离文件系统,隔离能力有限。Linux namespace 是内核提供的一种轻量级虚拟化技术,它将不同类型的全局资源封装到一个独立的命名空间中,使得进程只能看到该命名空间内的资源。

第一性原理: namespace 的本质是「让进程只看得到它应该看到的东西」。每个命名空间拥有自己独立的视图,进程无法感知其他命名空间的存在。Linux 内核目前支持 8 种 namespace,容器技术主要使用其中的 6 种:Mount(挂载)、PID(进程 ID)、Network(网络)、IPC(进程间通信)、UTS(主机名)、User(用户 ID)。每种 namespace 隔离一种全局资源,共同构成了容器的「虚拟化」基础。

原理 六种 namespace 及其隔离对象

Namespace隔离资源作用
Mount (mnt)挂载点列表每个容器拥有独立的文件系统视图,挂载操作互不影响
PID进程 ID 空间容器内 PID 从 1 开始,看不到宿主机进程,解决了 PID 冲突
Network (net)网络栈(设备、路由、防火墙)容器拥有独立的网络接口、IP、路由表等
IPCSystem V IPC 和 POSIX 消息队列容器内部 IPC 与其他容器隔离,避免冲突
UTS主机名和域名容器可以拥有独立的主机名,不影响宿主机
User用户 ID 和组 ID 映射容器内的 root 可以映射为宿主机上的非特权用户,提升安全性
六种 namespace 隔离示意图 宿主机 (Host OS) 容器 1 Mount: 独立挂载 PID: PID 1 (init) Network: veth0, 172.17.0.2 UTS: container1 容器 2 Mount: 独立挂载 PID: PID 1 (init) Network: veth1, 172.17.0.3 UTS: container2 图:每个容器拥有独立的 namespace 视图,互不干扰
图:每个容器拥有独立的 namespace 视图,互不干扰
▸ 使用 unshare 创建 namespace (Shell)
# 创建一个新的 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

演进 chroot → namespace → 容器

  • chroot (1979): 只隔离文件系统,其他资源不隔离
  • FreeBSD jail (2000): 提供更完整的隔离(文件、网络、进程),但仅限于 FreeBSD
  • Linux namespace (2002+): 逐步加入 mount、UTS、IPC、PID、net、user 等 namespace
  • Docker (2013): 组合 namespace + cgroup + UnionFS,提供完整的容器体验
"namespace 是容器隔离的第一层基石。它让容器中的进程以为自己是系统唯一的进程,拥有独立的资源视图。这种『错觉』是容器技术轻量化的关键——不需要虚拟化硬件,就能实现进程级的隔离。"
—— 容器隔离设计哲学

取舍 设计中的权衡

🔒 隔离 vs 性能
namespace 的隔离是进程级的,比虚拟机轻量得多,但隔离性不如硬件虚拟化。特别是在 user namespace 中,UID 映射会带来额外的开销。
🔧 安全性 vs 灵活性
user namespace 允许容器内 root 映射到宿主机非 root,提升安全性但增加了配置复杂度。未正确配置可能导致容器逃逸漏洞。
📦 新增 namespace
随着新需求出现,内核不断加入新的 namespace(如 cgroup、time)。每增加一种 namespace 就多一层隔离,但也会增加系统调用的复杂度。
02

cgroup 资源限制

背景 如何防止容器占用过多资源?

namespace 解决了「能看见什么」的问题,但无法解决「能用多少资源」的问题。一个容器如果持续占用大量 CPU 或内存,可能会影响到宿主机上其他容器的正常运行。cgroup (control group) 是 Linux 内核提供的资源控制机制,它可以对进程组进行CPU、内存、I/O、网络等资源的限制和隔离。

第一性原理: cgroup 的本质是「将进程分组,并对组内进程施加资源配额」。它通过子系统 (subsystem) 管理不同类型的资源,如 cpu 控制 CPU 份额,memory 控制内存上限,blkio 控制 I/O 带宽。cgroup v2 使用统一的层级结构,管理更加清晰。这种机制使得容器可以拥有明确的资源边界,防止单容器耗尽宿主机资源。

原理 cgroup v2 · 子系统 · 资源控制

cgroup v2 统一层级: 所有子系统挂载在同一个控制树中,每个进程属于一个控制组,控制组可以嵌套。

cgroup v2 资源控制层级 根控制组 (/) 容器组 A CPU: 50% · 内存: 1GB 容器组 B CPU: 30% · 内存: 512MB 容器组 C CPU: 20% · 内存: 256MB 进程 P1 进程 P2 进程 P3 进程 P4 进程 P5 图:cgroup v2 统一层级,每个控制组内进程共享资源配额
图:cgroup v2 统一层级,每个控制组内进程共享资源配额
▸ cgroup 操作示例 (Shell)
# 创建容器组对应的 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

演进 cgroup v1 → cgroup v2 → 统一层级

  • cgroup v1 (2006): 每个子系统独立挂载,多个层级,管理混乱,存在资源竞争
  • cgroup v2 (2016): 统一层级结构,所有子系统共享一棵树,管理更清晰,支持更多资源类型
  • 容器与 cgroup: Docker 默认使用 cgroup v2,每个容器对应一个控制组,实现精细化资源管控
"cgroup 是容器资源控制的『执法者』。它确保每个容器占用不超过分配的资源,让多租户共享同一台物理机成为可能。cgroup v2 的统一层级设计,使得资源管理更加高效和直观。"
—— 资源隔离设计哲学

取舍 设计中的权衡

⚡ 配额 vs 利用率
严格的资源配额可以防止干扰,但可能导致资源闲置。合理设置配额,在隔离和利用率之间取得平衡。
🔧 内存限制 vs 内存交换
内存限制过严可能导致容器 OOM;允许内存交换会降低性能。需要根据容器业务的特性合理配置。
📈 cgroup v1 vs v2
cgroup v2 解决了 v1 的混乱问题,但部分老应用可能不兼容。过渡期需要同时支持两种版本。
03

UnionFS 镜像分层

背景 如何复用文件系统,节省存储空间?

传统虚拟机的每个实例都拥有一份完整的文件系统,占用大量存储空间。Docker 引入了UnionFS(联合文件系统),通过分层镜像的方式,让多个容器共享同一份只读镜像层,只有差异部分写入可写层。OverlayFS 是目前最常用的 UnionFS 实现,它通过合并多个目录来提供统一视图。

第一性原理: UnionFS 的本质是「将多个文件系统层叠加成一个统一的文件系统视图」。容器镜像由多个只读层组成,每一层代表文件系统的增量修改。容器运行时,在只读层之上叠加一个可写层(容器层),所有修改写入该层。这种写时复制 (Copy-on-Write) 机制保证了多个容器共享相同的镜像层,只有写入数据时才占用额外空间,极大地提高了存储效率。

原理 镜像层 · OverlayFS · 写时复制

OverlayFS 结构:lowerdir(下层只读目录)和 upperdir(上层可写目录)组成,通过 workdir 临时工作目录,最终合并到 merged 视图。

OverlayFS 镜像分层架构 容器可写层 (upperdir) 容器运行时修改:文件创建、修改、删除 镜像层 (lowerdir 1) - 基础层 镜像层 (lowerdir 2) - 应用层 镜像层 (lowerdir 3) - 配置层 图:OverlayFS 将多层只读镜像叠加,可写层放在最上层
图:OverlayFS 将多层只读镜像叠加,可写层放在最上层
▸ 使用 OverlayFS 挂载镜像
# 创建 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

演进 AUFS → OverlayFS → 镜像分层标准

  • AUFS (早期 Docker): 第一个 UnionFS 实现,但未进入 Linux 内核主线
  • OverlayFS (2014): 进入 Linux 内核 3.18,成为 Docker 默认存储驱动
  • 镜像分层标准 (OCI): 定义了镜像的层结构,使不同实现可以互操作
  • 快照与缓存: 通过缓存层加速容器启动,减少反复拉取镜像
"UnionFS 是容器技术的『存储魔法』。它让镜像可以共享、容器可以快速启动、存储可以高效利用。OverlayFS 进入内核成为标准后,容器存储的性能和稳定性大幅提升。"
—— 存储设计哲学

取舍 设计中的权衡

📦 层数 vs 性能
过多的镜像层会增加文件查找开销,影响文件读取性能。通常建议合并多个变更到一层,控制层数在合理范围内。
⚡ 写时复制 vs 写放大
修改小文件会触发写时复制,可能导致写放大。对于频繁写入的目录,可以使用 --mount 绑定挂载来绕过层存储。
🔧 存储驱动选择
OverlayFS 性能优秀但需要内核支持;devicemapper 在较老内核上可用但性能差。需要根据运行环境选择适合的驱动。
04

OCI 标准

背景 为什么需要容器标准化?

Docker 出现早期,它的镜像格式和运行时是自成一派的。其他容器运行时(如 rkt、LXC)无法直接运行 Docker 镜像,导致生态系统碎片化。2015 年,Docker 贡献了其镜像和运行时规范,成立了 OCI (Open Container Initiative),致力于制定容器行业的开放标准。

第一性原理: OCI 标准的本质是「容器格式和运行时的通用接口」。它定义了镜像规范 (Image Specification)——描述容器镜像的文件系统层结构、配置和元数据;运行时规范 (Runtime Specification)——描述如何根据镜像配置运行容器。这种标准化的结果使得任何遵循 OCI 标准的运行时都可以运行任何 OCI 镜像,实现了容器生态的互通。

原理 镜像规范 · 运行时规范 · 分发规范

OCI 镜像规范: 定义镜像由 manifestconfiglayers 组成,使用 digest 确保内容完整性。

OCI 运行时规范: 定义运行时配置文件 config.json,包含 rootfs、mounts、process、hooks、annotations 等字段。

OCI 标准构成 镜像规范 • 镜像索引 (index.json) • 清单 (manifest) • 配置 (config.json) • 层 (layers) • 摘要 (digest) 运行时规范 • config.json • rootfs (文件系统) • mounts (挂载点) • process (启动命令) • hooks (钩子) 分发规范 • 仓库 API • 镜像拉取/推送 • 引用 (reference) • 认证 (Auth) 图:OCI 三大规范,覆盖镜像构建、分发和运行全流程
图:OCI 三大规范,覆盖镜像构建、分发和运行全流程
▸ OCI 运行时 config.json 示例
// 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 专属 → OCI 标准 → 广泛支持

  • Docker 专属 (2013): Docker 镜像格式不开放,其他运行时无法兼容
  • OCI 成立 (2015): Docker 捐赠镜像和运行时规范,形成开放标准
  • OCI v1.0 (2017): 发布正式规范,成为行业标准
  • 广泛支持 (2018+): 所有主流容器运行时(runc、containerd、CRI-O)支持 OCI 镜像
"OCI 标准是容器生态的『通用语言』。它打破了 Docker 的垄断,使得 docker build 的镜像可以用 runc 运行,甚至可以在 podmancontainerd 中无缝工作。标准化的力量让整个容器生态系统繁荣发展。"
—— 标准化设计哲学

取舍 设计中的权衡

📐 标准化 vs 创新
标准化的代价是限制了实现的特有创新。但 OCI 标准足够精简,为上层创新留出了空间(如 CRI 扩展)。
🔧 兼容性 vs 演进
OCI 版本升级需要保持向后兼容,确保老镜像仍可运行。这导致了规范的演进速度较慢。
⚡ 同一镜像多运行时
OCI 镜像可以在不同运行时上运行,但不同运行时的性能和行为可能存在差异,需要充分测试。
05

容器网络模型

背景 容器之间如何通信?

每个容器拥有独立的网络命名空间,意味着它看不到其他容器的网络设备。为了让容器之间能够相互通信,并且能够访问外部网络,容器运行时必须建立网络桥接路由。Docker 默认采用 bridge 网络模式,通过 docker0 网桥连接容器。

第一性原理: 容器网络的核心是「将多个隔离的网络命名空间连接起来」。最常见的方案是使用 veth pair(虚拟网卡对)一端放入容器,另一端连接到宿主机的网桥上。网桥实现了二层转发,使得所有容器处于同一个子网。再通过 NAT路由实现容器访问外部网络。这种设计实现了隔离连通的平衡。

原理 veth pair · 网桥 · 端口映射 · 网络模式

Docker 默认 bridge 网络模式:

  • 每个容器有一对 veth:一端在容器内(eth0),一端在宿主机上(vethX)
  • 宿主机上的 veth 连接到 docker0 网桥(默认 172.17.0.0/16)
  • 容器之间通过网桥二层通信,出外网通过 iptables NAT 转换
Docker bridge 网络模型 宿主机 (Host OS) docker0 网桥 (172.17.0.1) veth1 veth2 容器 1 eth0: 172.17.0.2 容器 2 eth0: 172.17.0.3 端口映射:宿主机 8080 → 容器 80 图:bridge 网络模型,veth 对连接容器和 docker0 网桥
图:bridge 网络模型,veth 对连接容器和 docker0 网桥
▸ 容器网络操作 (Docker)
# 创建自定义网络 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

演进 bridge → host → overlay → CNI

  • bridge (默认): 网桥模式,简单易用,适用于单机
  • host: 容器直接使用宿主机网络,性能最好但无隔离
  • overlay (Swarm/Kubernetes): 跨主机通信,使用 VXLAN 隧道
  • CNI (Container Network Interface): Kubernetes 标准网络接口,支持多种插件
"容器网络的演进是从『单机』『集群』的转变。bridge 网络解决了单机容器互联,overlay 网络解决了跨主机通信,CNI 则提供了标准化的接口,使得网络插件可以自由替换。"
—— 网络设计哲学

取舍 设计中的权衡

⚡ 隔离 vs 性能
host 网络性能最好但无隔离;bridge 网络有隔离但增加一层转发;overlay 网络引入隧道开销。需要根据场景选择。
🔧 NAT vs 路由
默认 NAT 模式允许容器访问外网,但端口映射复杂且限制多。使用路由模式需要配置宿主机路由,增加了运维复杂度。
📦 CNI 插件选择
CNI 提供了多种插件(Flannel、Calico、Weave 等),各有优劣。Calico 性能好但依赖 BGP;Flannel 简单但隧道开销大。
06

容器与虚拟机的区别

背景 为什么容器比虚拟机更轻量?

虚拟机 (VM) 通过 Hypervisor 模拟完整的硬件,每个 VM 运行一个独立的操作系统内核。容器则共享宿主机的内核,只隔离进程和资源。这种根本性的设计差异,导致容器在启动速度、资源利用率、性能等方面都优于虚拟机。

第一性原理: 容器和虚拟机的根本区别在于「共享内核 vs 独立内核」。虚拟机通过硬件虚拟化实现全隔离,每个 VM 有独立的内核、硬件抽象层,安全边界更强。容器通过操作系统级虚拟化实现进程隔离,共享宿主机内核,启动快、占用小,但隔离性较弱。这种差异不是谁优谁劣,而是不同场景下的最佳选择

原理 架构对比 · 隔离机制 · 资源利用率

特性容器虚拟机
隔离级别进程级 (namespace + cgroup)硬件级 (Hypervisor)
内核共享宿主机内核独立内核
启动时间秒级 (毫秒级可能)分钟级 (通常几十秒)
内存占用MB 级别GB 级别
存储占用MB ~ GB (共享层)GB ~ TB (完整镜像)
性能损耗接近原生 (≈ 0-5%)5-20% (硬件虚拟化开销)
可迁移性镜像标准化 (OCI)依赖 Hypervisor 格式
安全性较弱 (内核共享)较强 (硬件隔离)
容器 vs 虚拟机架构对比 容器 应用 A 应用 B 应用 C 宿主机内核 (共享) 虚拟机 VM 1 (内核 + 应用) 独立内核 VM 2 (内核 + 应用) 独立内核 Hypervisor (硬件虚拟化) 图:容器共享内核,虚拟机独立内核 + Hypervisor
图:容器共享内核,虚拟机独立内核 + 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 起步

演进 硬件虚拟化 → 容器 → 混合部署

  • 硬件虚拟化 (1990s-2000s): VMware、Xen、KVM 提供全虚拟化,隔离性强但开销大
  • 操作系统级虚拟化 (2000s): FreeBSD jail、Solaris Zones、Linux 容器(LXC)
  • Docker 容器 (2013): 标准化容器格式,掀起容器化浪潮
  • 混合部署 (2015+): 虚拟机运行容器(如 Kata Containers),结合两者优点
"容器和虚拟机不是竞争关系,而是互补关系。虚拟机提供更强的隔离,容器提供更高的效率。在云原生时代,虚拟机 + 容器混合部署成为主流,既保证了安全性,又获得了容器的高效与敏捷。"
—— 虚拟化设计哲学

取舍 设计中的权衡

🔒 安全 vs 效率
虚拟机安全性更高但效率低,容器效率高但安全性稍弱。对于多租户不可信环境,虚拟机更合适;对于可信环境或私有云,容器更优。
⚡ 启动速度 vs 资源消耗
容器秒级启动,适合弹性伸缩;虚拟机分钟级启动,适合长期稳定服务。容器资源占用小,适合微服务架构。
📦 标准化 vs 生态
容器依托 OCI 标准,生态丰富,工具链成熟;虚拟机格式多样(VMDK、QCOW2),管理工具各异。