编程 Docker 底层原理:Namespaces、Cgroups 与分层文件系统解析

2026-09-07 02:10:38

Docker 底层原理:Namespaces、Cgroups 与分层文件系统解析

DEV Community 技术博客发表文章,由 Michael Wolfenberger 撰写,深入解析 Docker 的底层工作原理。文章首先澄清了一个常见的误解:许多工程师认为 Docker 像轻量级虚拟机一样运行,但实际上并非如此。虚拟机通过 hypervisor 虚拟化物理硬件(CPU、内存控制器、网络适配器),在模拟硬件中运行完整的客户操作系统(包括独立的内核、init 系统、设备驱动和系统后台服务);而 Docker 容器直接运行在宿主 Linux 内核上,只是内核通过 Namespaces(命名空间)和 Cgroups(控制组)限制了进程能看到的、能消耗的和能修改的内容。本文基于该技术文章,系统解读 Docker 容器技术的核心原理。

背景:容器与虚拟机的本质区别

虚拟机的架构

虚拟机(Virtual Machine)的架构:

  • Hypervisor 层:虚拟化物理硬件(CPU、内存控制器、网络适配器)
  • 客户操作系统:在模拟硬件中运行完整的操作系统(独立内核、init 系统、设备驱动、后台服务)
  • 应用层:运行在客户操作系统中

虚拟机特点:

  • 每个虚拟机有完整独立的操作系统
  • 内核级隔离
  • 资源开销大(每个虚拟机都需要完整的 OS)
  • 启动速度慢(需要启动完整操作系统)
  • 隔离性强

容器的架构

容器(Container)的架构:

  • 宿主 Linux 内核:容器直接运行在宿主内核上
  • Root 文件系统:容器有自己的根文件系统,但没有独立内核
  • Namespaces + Cgroups:内核通过它们限制容器的可见性、资源使用和文件修改
  • 应用层:运行在根文件系统中

容器特点:

  • 所有容器共享宿主内核
  • 进程级隔离(不是内核级)
  • 资源开销小
  • 启动速度快(秒级)
  • 隔离性弱于虚拟机

容器不是轻量级虚拟机

关键澄清:

  • 当你运行 docker run -d nginx 时,Nginx worker 是宿主机器进程树中一个普通进程
  • 区别在于内核限制了该进程能看到的、能消耗的、能修改的内容
  • 容器没有自己的内核
  • 容器与宿主机共享内核
  • 容器是"受限的进程",不是"轻量的虚拟机"

三大核心技术

1. 限制可见性:Linux Namespaces

什么是 Namespaces

Linux Namespaces 是内核提供的进程隔离机制,让进程拥有独立的系统资源视图。每个 Namespace 提供一种资源的隔离视图。

Namespaces 的类型

Namespace隔离内容容器中的作用
PID Namespace进程 ID容器内 PID 从 1 开始,看不到宿主机其他进程
Network Namespace网络栈每个容器有自己的网络接口、IP、路由表
Mount Namespace文件系统挂载点容器有独立的文件系统视图
UTS Namespace主机名和域名容器有自己的主机名
IPC Namespace进程间通信容器有独立的 IPC 资源
User Namespace用户和组 ID容器内的用户映射到宿主机的非特权用户

PID Namespace

  • 容器内的第一个进程 PID 为 1
  • 容器进程看不到宿主机和其他容器的进程
  • 就像每个容器是一个独立的"系统"
  • 进程隔离的基础

Network Namespace

  • 每个容器有独立的网络接口
  • 容器有自己的 IP 地址
  • 容器有自己的路由表和防火墙规则
  • 容器间网络通过 bridge 等机制互联
  • 网络隔离和端口映射的基础

Mount Namespace

  • 每个容器有独立的文件系统挂载视图
  • 容器看到的根文件系统是镜像层叠加的结果
  • 宿主机的挂载点对容器不可见(除非显式挂载)
  • 文件系统隔离的基础

User Namespace

  • 容器内的 root 用户映射为宿主机的非特权用户
  • 容器内的高权限操作在宿主机上是低权限的
  • 增强安全性
  • 降低容器逃逸的风险

2. 限制资源消耗:Cgroups

什么是 Cgroups

Cgroups(Control Groups)是 Linux 内核提供的资源限制和监控机制,用于限制、记录和隔离进程组的资源使用。

Cgroups 的功能

  • CPU 限制:限制进程组的 CPU 使用率
  • 内存限制:限制进程组的内存使用量
  • 磁盘 I/O 限制:限制进程组的磁盘读写速度
  • 网络带宽限制:限制进程组的网络带宽
  • 进程数限制:限制进程组可以创建的进程数
  • 资源监控:监控进程组的资源使用情况

Cgroups 在容器中的应用

Docker 使用 Cgroups 实现资源限制:

  • --cpus:限制容器的 CPU 使用
  • --memory:限制容器的内存使用
  • --memory-swap:限制容器的交换内存
  • --blkio-weight:限制容器的磁盘 I/O
  • --pids-limit:限制容器的进程数

示例:

# 限制容器最多使用 1.5 个 CPU 核心
docker run --cpus=1.5 myapp

# 限制容器最多使用 512MB 内存
docker run --memory=512m myapp

# 同时限制 CPU 和内存
docker run --cpus=2 --memory=1g myapp

Cgroups 的意义

  • 防止单个容器耗尽所有资源
  • 保证多容器公平分配资源
  • 防止"吵闹的邻居"问题
  • 资源使用可预测
  • 提升整体稳定性

3. 限制文件修改:分层文件系统

什么是分层文件系统

Docker 镜像由多层(Layer)组成,每层代表镜像构建过程中的一个步骤。容器运行时的文件系统是多层叠加的结果。

镜像分层

Dockerfile 的每个指令生成一层:

FROM ubuntu:24.04          # 基础层
RUN apt-get update         # 层1:更新软件源
RUN apt-get install -y nginx  # 层2:安装 nginx
COPY app.conf /etc/nginx/  # 层3:复制配置
EXPOSE 80                  # 元数据(不产生层)
CMD ["nginx", "-g", "daemon off;"]  # 元数据(不产生层)

每层的特点:

  • 只读层:镜像层都是只读的
  • 不可变性:镜像层一旦创建不可修改
  • 共享性:相同层可以跨镜像共享
  • 缓存性:构建时未变化的层可以复用缓存

写时复制(Copy-on-Write)

容器运行时在镜像层之上添加一个可写层:

  • 容器对文件的修改写入可写层
  • 读取文件时按层查找,找到第一个匹配的层
  • 修改文件时先复制到可写层再修改(Copy-on-Write)
  • 删除文件时在可写层添加删除标记(whiteout)
  • 容器删除后可写层销毁,镜像不受影响

OverlayFS

Docker 默认使用 OverlayFS 实现分层:

  • Lowerdir:镜像层(只读)
  • Upperdir:容器可写层
  • Merged:合并后的视图(容器看到的文件系统)

优点:

  • 高效的层合并
  • 按需复制
  • 共享底层数据
  • 快速的容器创建

分层文件系统的意义

  • 镜像可以复用和共享
  • 镜像体积优化(只存差异)
  • 容器启动快速(不需要复制整个文件系统)
  • 容器间隔离(各自的可写层独立)
  • 镜像分发高效(只传输差异层)

容器完整生命周期

容器启动过程

docker run -d nginx
  1. 拉取镜像(如果本地没有):从 registry 拉取镜像层
  2. 创建容器:创建容器的配置和状态
  3. 创建 Namespaces:为容器创建独立的 PID、Network、Mount、UTS、IPC、User Namespaces
  4. 配置 Cgroups:创建容器的 Cgroup,设置资源限制
  5. 准备文件系统:使用 OverlayFS 合并镜像层和可写层
  6. 运行进程:在 Namespaces 和 Cgroups 的约束下启动容器进程
  7. 进程 PID 1:容器内第一个进程成为 PID 1(init 进程)
  8. 网络配置:为容器分配 IP 和网络接口

容器运行状态

容器运行时:

  • 进程运行在宿主内核上
  • 受到 Namespaces 的可见性限制
  • 受到 Cgroups 的资源限制
  • 文件系统是镜像层 + 可写层的叠加
  • 可以通过 docker exec 进入容器
  • 可以通过 docker logs 查看日志

容器停止和清理

docker stop nginx
docker rm nginx
  • 停止容器:发送 SIGTERM/SIGKILL 给容器进程
  • 删除容器:销毁可写层和容器配置
  • 镜像保留:镜像层不受影响
  • 数据持久化:挂载的卷(Volume)保留

安全考量

容器的安全边界

容器提供的是进程级隔离,不是安全边界:

  • 共享内核意味着内核漏洞可能影响所有容器
  • 需要额外安全机制(如 gVisor、Kata Containers)实现更强的隔离
  • User Namespaces 可以降低权限提升风险
  • Seccomp 可以限制系统调用
  • AppArmor/SELinux 可以强制访问控制

安全最佳实践

  • 使用非 root 用户运行容器
  • 最小化镜像体积(减少攻击面)
  • 只暴露必要的端口
  • 使用只读文件系统(--read-only)
  • 使用 User Namespaces 映射
  • 启用 Seccomp 和 AppArmor
  • 定期更新基础镜像
  • 扫描镜像漏洞

总结

Docker 容器技术的核心是三个 Linux 内核机制:Namespaces(命名空间)、Cgroups(控制组)和分层文件系统。Namespaces 限制容器进程能看到的资源,提供 PID、Network、Mount、UTS、IPC、User 等六种隔离视图,让每个容器看起来像独立的系统;Cgroups 限制容器能消耗的资源,提供 CPU、内存、磁盘 I/O、网络带宽、进程数等限制,防止单个容器耗尽所有资源;分层文件系统限制容器能修改的文件,通过镜像分层和写时复制机制,让容器可以共享只读镜像层,在可写层进行修改,容器删除后镜像不受影响。这三者协同工作,实现了容器"轻量、快速、隔离"的特性,与虚拟机(完整客户 OS + Hypervisor)形成鲜明对比。理解这些底层原理对于正确使用 Docker 至关重要:可以帮助排查容器网络问题(Network Namespace)、优化资源分配(Cgroups)、理解镜像体积优化(分层文件系统)、设计安全策略(Namespaces 和内核安全机制)。同时,容器并非安全边界,共享内核的特性决定了需要额外的安全机制来加强隔离。随着云原生技术的普及,深入理解容器底层原理已经成为现代开发者和运维工程师的基本功。

来源:https://dev.to/michaelwolfenberger/docker-under-the-hood-namespaces-cgroups-and-layered-filesystems-explained-1414

推荐文章

程序员茄子在线接单