Docker 完全指南 / 03 - 架构原理
03 - 架构原理
深入理解 Docker 的分层架构:daemon、containerd、runc,以及存储驱动与 cgroup/namespace。
3.1 Docker 整体架构
Docker 采用经典的 客户端-服务器(Client-Server) 架构:
┌─────────────────────────────────────────────────────────┐
│ Docker Client │
│ docker CLI │ Docker Compose │ Docker SDK │ API │
└───────┬────────────────┬────────────────┬───────────────┘
│ REST API │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Docker Engine (dockerd) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ 镜像管理 │ │ 容器管理 │ │ 网络管理 │ │ 卷管理 │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Docker API Server │ │
│ └─────────────────────────────────────────────────────┘ │
└───────┬─────────────────────────────────────────────────┘
│ gRPC
▼
┌─────────────────────────────────────────────────────────┐
│ containerd │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ 镜像服务 │ │ 内容存储 │ │ 容器运行时管理 │ │
│ └──────────┘ └──────────┘ └──────────────────────┘ │
└───────┬─────────────────────────────────────────────────┘
│ OCI Runtime Spec
▼
┌─────────────────────────────────────────────────────────┐
│ runc │
│ (OCI 标准容器运行时) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Linux Kernel: namespaces + cgroups + seccomp │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
组件职责表
| 组件 | 角色 | 说明 |
|---|---|---|
| docker CLI | 客户端 | 用户交互界面,发送 REST API 请求 |
| dockerd | Docker 守护进程 | 镜像管理、构建、网络、卷、API 服务 |
| containerd | 高级容器运行时 | 容器生命周期管理、镜像分发 |
| containerd-shim | 容器垫片进程 | 每个容器一个,使容器独立于 containerd |
| runc | OCI 运行时 | 实际创建和运行容器 |
| Linux Kernel | 内核 | namespaces、cgroups、seccomp 等 |
3.2 Docker daemon (dockerd)
职责
dockerd 职责:
├── API 服务: 暴露 REST API (默认 unix:///var/run/docker.sock)
├── 镜像管理: 构建、拉取、推送镜像
├── 容器管理: 创建、启动、停止容器(委托给 containerd)
├── 网络管理: 创建和管理 Docker 网络
├── 卷管理: 创建和管理数据卷
├── 日志管理: 容器日志收集和驱动
└── 事件系统: 发布容器、镜像等事件
配置文件
# Docker daemon 配置文件
cat /etc/docker/daemon.json
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://docker.1ms.run"],
"default-address-pools": [
{ "base": "172.17.0.0/12", "size": 24 }
],
"live-restore": true,
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 5
}
常用 daemon 参数
| 参数 | 默认值 | 说明 |
|---|---|---|
storage-driver | overlay2 | 存储驱动 |
log-driver | json-file | 默认日志驱动 |
exec-opts | [] | 执行选项,如 cgroup 驱动 |
live-restore | false | daemon 重启时保持容器运行 |
max-concurrent-downloads | 3 | 并发下载层数 |
default-runtime | runc | 默认 OCI 运行时 |
iptables | true | 是否管理 iptables 规则 |
ipv6 | false | 是否启用 IPv6 |
3.3 containerd
containerd 是一个行业标准的高级容器运行时,由 Docker 捐赠给 CNCF,现为毕业项目。
containerd 架构
┌──────────────────────────────────────────┐
│ containerd │
│ │
│ ┌──────────┐ ┌───────────────────────┐ │
│ │ Content │ │ Snapshot Service │ │
│ │ Store │ │ (快照管理) │ │
│ │ (内容存储)│ │ │ │
│ └──────────┘ └───────────────────────┘ │
│ │
│ ┌──────────┐ ┌───────────────────────┐ │
│ │ Image │ │ Container Service │ │
│ │ Service │ │ (容器生命周期) │ │
│ │ (镜像服务)│ │ │ │
│ └──────────┘ └───────────────────────┘ │
│ │
│ ┌──────────┐ ┌───────────────────────┐ │
│ │ Namespace │ │ Task Service │ │
│ │ Service │ │ (进程管理) │ │
│ │ (命名空间)│ │ │ │
│ └──────────┘ └───────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Runtime (runc/crun/kata) │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────┘
直接使用 containerd
# 使用 ctr 工具(containerd 的 CLI)
sudo ctr images pull docker.io/library/nginx:alpine
sudo ctr containers create docker.io/library/nginx:alpine my-nginx
sudo ctr tasks start my-nginx
sudo ctr tasks ls
使用 nerdctl(containerd 的现代 CLI)
# 安装 nerdctl(兼容 Docker CLI 语法)
# https://github.com/containerd/nerdctl
nerdctl run -d --name my-nginx -p 8080:80 nginx:alpine
nerdctl ps
nerdctl images
3.4 containerd-shim
每个容器运行时都有一个 shim 进程,它的作用是:
containerd ──启动──> containerd-shim ──启动──> runc ──创建──> 容器进程
│ │
│ runc 退出
│
└─── 保持运行,管理容器进程
| 功能 | 说明 |
|---|---|
| 解耦容器与 containerd | containerd 重启不影响运行中的容器 |
| 保持 stdin 开启 | 容器的输入输出流不中断 |
| 退出状态报告 | 将容器退出码报告给 containerd |
| 资源监控 | 收集容器资源使用数据 |
# 查看 shim 进程
ps aux | grep containerd-shim
# containerd-shim-runc-v2 -namespace moby -id <container_id> ...
3.5 runc — OCI 标准运行时
runc 是最底层的容器运行时,直接调用 Linux 内核功能创建容器。
runc 的工作原理
runc 创建容器的过程:
1. 创建容器目录结构
2. 设置 rootfs(根文件系统)
3. 挂载 /proc, /sys, /dev 等
4. 配置 Linux namespaces:
- PID namespace: 进程隔离
- Network namespace: 网络隔离
- Mount namespace: 文件系统隔离
- UTS namespace: 主机名隔离
- IPC namespace: 进程间通信隔离
- User namespace: 用户隔离
5. 配置 cgroups:
- CPU 限制
- 内存限制
- I/O 限制
6. 设置 seccomp / AppArmor / SELinux 安全策略
7. 切换到容器的 rootfs
8. 执行用户指定的进程
直接使用 runc
# 创建 OCI bundle
mkdir -p /mycontainer/rootfs
# 导出 Ubuntu 镜像的文件系统
docker export $(docker create ubuntu:22.04) | tar -C /mycontainer/rootfs -xf -
# 生成默认配置
cd /mycontainer
runc spec
# 运行容器
sudo runc run mycontainer
# 清理
sudo runc delete mycontainer
rm -rf /mycontainer
其他 OCI 运行时
| 运行时 | 特点 | 适用场景 |
|---|---|---|
| runc | 参考实现,最广泛 | 通用场景 |
| crun | C 语言实现,更快 | 性能敏感场景 |
| kata-runtime | 轻量 VM 级隔离 | 强安全隔离 |
| gVisor (runsc) | 用户态内核 | 安全沙箱 |
3.6 Linux Namespaces(命名空间)
Namespace 是 Linux 内核实现容器隔离的核心机制。
六种 Namespace
| Namespace | 隔离内容 | 标志 | 说明 |
|---|---|---|---|
| PID | 进程 ID | CLONE_NEWPID | 容器内 PID 从 1 开始 |
| Network | 网络栈 | CLONE_NEWNET | 独立的网卡、IP、端口 |
| Mount | 文件系统挂载 | CLONE_NEWNS | 独立的挂载点 |
| UTS | 主机名 | CLONE_NEWUTS | 独立的 hostname |
| IPC | 进程间通信 | CLONE_NEWIPC | 独立的信号量、消息队列 |
| User | 用户/组 ID | CLONE_NEWUSER | UID 映射 |
验证 Namespace
# 查看容器的 namespace
PID=$(docker inspect --format '{{.State.Pid}}' my-nginx)
ls -la /proc/$PID/ns/
# 输出:
# lrwxrwxrwx 1 root root 0 ... cgroup -> 'cgroup:[4026532583]'
# lrwxrwxrwx 1 root root 0 ... ipc -> 'ipc:[4026532582]'
# lrwxrwxrwx 1 root root 0 ... mnt -> 'mnt:[4026532580]'
# lrwxrwxrwx 1 root root 0 ... net -> 'net:[4026532584]'
# lrwxrwxrwx 1 root root 0 ... pid -> 'pid:[4026532581]'
# lrwxrwxrwx 1 root root 0 ... uts -> 'uts:[4026532579]'
Namespace 操作示例
# 使用 unshare 创建新的 namespace
sudo unshare --pid --mount --net --uts --ipc --fork bash
# 在新 namespace 中
hostname my-container
echo "当前 hostname: $(hostname)"
ps aux # 只能看到当前 namespace 中的进程
3.7 Cgroups(控制组)
Cgroups 用于限制、控制和统计进程组的资源使用。
资源控制类型
| 控制器 | 功能 | 示例 |
|---|---|---|
| cpu | CPU 时间分配 | --cpus=1.5 |
| memory | 内存使用限制 | --memory=512m |
| blkio | 块设备 I/O | --blkio-weight=500 |
| pids | 进程数限制 | --pids-limit=100 |
| cpuset | CPU 核心绑定 | --cpuset-cpus=0,1 |
| devices | 设备访问控制 | --device-read-bps |
容器资源限制示例
# CPU 限制
docker run -d --name cpu-test --cpus=1.5 nginx:alpine
# 内存限制
docker run -d --name mem-test --memory=256m --memory-swap=512m nginx:alpine
# 进程数限制
docker run -d --name pid-test --pids-limit=50 nginx:alpine
# 查看容器的 cgroup 信息
docker stats cpu-test mem-test pid-test --no-stream
cgroup v1 vs v2
| 特性 | cgroup v1 | cgroup v2 |
|---|---|---|
| 层级 | 多层级(每个控制器独立) | 统一层级 |
| 挂载点 | /sys/fs/cgroup/<controller>/ | /sys/fs/cgroup/ |
| 资源压力通知 | 不支持 | 支持 PSI |
| 线程级控制 | 不支持 | 支持 |
| 安全性 | 较弱 | 更强 |
| Docker 默认 | 旧版本默认 | Docker 20.10+ 默认 |
# 检查当前 cgroup 版本
stat -fc %T /sys/fs/cgroup/
# cgroup2fs = v2, tmpfs = v1
# 查看容器的 cgroup 路径
docker inspect --format '{{.HostConfig.CgroupParent}}' <container>
3.8 存储驱动(Storage Driver)
Docker 使用分层文件系统来存储镜像和容器数据。
分层原理
镜像层 (只读):
┌────────────────────────────────┐
│ Layer 3: COPY app.py /app/ │ ← 应用代码 (10KB)
├────────────────────────────────┤
│ Layer 2: RUN pip install flask │ ← 依赖安装 (50MB)
├────────────────────────────────┤
│ Layer 1: ubuntu:22.04 base │ ← 基础镜像 (77MB)
└────────────────────────────────┘
容器层 (可写):
┌────────────────────────────────┐
│ Writable Layer │ ← 容器运行时写入
│ (修改的文件、日志、临时文件) │
├────────────────────────────────┤
│ 镜像层 (只读,通过 CoW 复制) │
└────────────────────────────────┘
Copy-on-Write (CoW) 机制
读取文件:
容器层 → 未找到 → 向下查找镜像层 → 找到并读取
容器层 → 找到 → 直接读取
写入文件:
首次写入 → 从镜像层复制到容器层 (Copy-on-Write) → 在容器层修改
后续写入 → 直接在容器层修改
存储驱动对比
| 驱动 | 性能 | 稳定性 | 适用场景 | 说明 |
|---|---|---|---|---|
| overlay2 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 推荐默认 | Linux 4.0+ 内核 |
| fuse-overlayfs | ⭐⭐⭐ | ⭐⭐⭐⭐ | Rootless 模式 | 需要 FUSE 支持 |
| btrfs | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Btrfs 文件系统 | 原生快照支持 |
| zfs | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ZFS 文件系统 | 适合大容量存储 |
| vfs | ⭐ | ⭐⭐⭐⭐⭐ | 测试/不支持 CoW | 无 CoW,全量复制 |
# 查看当前存储驱动
docker info | grep "Storage Driver"
# 查看镜像层信息
docker inspect nginx:alpine | jq '.[0].RootFS.Layers'
# 查看存储占用
docker system df -v
3.9 容器创建的完整流程
用户执行: docker run -d --name web -p 8080:80 nginx:alpine
完整流程:
1. Docker CLI → dockerd (REST API)
2. dockerd 解析参数,检查镜像是否存在
3. 如果镜像不存在 → 从 Registry 拉取
4. dockerd 调用 containerd 创建容器
5. containerd 创建容器配置
6. containerd 启动 containerd-shim
7. containerd-shim 调用 runc
8. runc 创建 namespaces、cgroups
9. runc 配置 rootfs、挂载点
10. runc 执行容器进程 (nginx)
11. runc 退出,containerd-shim 接管
12. 容器进程独立运行
13. dockerd 配置网络(bridge + port mapping)
14. 返回容器 ID 给 CLI
# 使用 nsenter 进入容器的 namespace 查看
CONTAINER_PID=$(docker inspect --format '{{.State.Pid}}' web)
sudo nsenter -t $CONTAINER_PID -p -n -m ps aux
3.10 Docker 数据目录结构
/var/lib/docker/
├── overlay2/ # 镜像和容器的文件系统层
│ ├── <layer-id>/ # 每层的目录
│ │ ├── diff/ # 该层的文件差异
│ │ ├── link # 短标识符链接
│ │ └── merged/ # 联合挂载后的完整文件系统
│ └── l/ # 符号链接目录
├── containers/ # 容器元数据和日志
│ └── <container-id>/
│ ├── config.v2.json # 容器配置
│ ├── hostconfig.json # 主机配置
│ └── <id>-json.log # 容器日志
├── image/ # 镜像元数据
│ └── overlay2/
│ ├── distribution/ # 分发元数据
│ ├── imagedb/ # 镜像数据库
│ ├── layerdb/ # 层数据库
│ └── repositories.json # 仓库索引
├── volumes/ # 数据卷
│ └── <volume-name>/
│ └── _data/ # 卷数据
├── network/ # 网络配置
├── tmp/ # 临时文件
└── swarm/ # Swarm 数据
# 查看 Docker 数据目录大小
sudo du -sh /var/lib/docker/
sudo du -sh /var/lib/docker/overlay2/
sudo du -sh /var/lib/docker/containers/
# 查看容器配置
sudo cat /var/lib/docker/containers/<container-id>/config.v2.json | jq .
要点回顾
| 要点 | 核心内容 |
|---|---|
| 分层架构 | CLI → dockerd → containerd → runc → Kernel |
| containerd | 高级容器运行时,管理容器生命周期 |
| runc | OCI 标准运行时,实际创建容器 |
| Namespace | 6 种命名空间实现进程、网络、文件系统隔离 |
| Cgroups | 限制 CPU、内存、I/O 等资源使用 |
| 存储驱动 | overlay2 是推荐的默认存储驱动 |
注意事项
不要手动修改
/var/lib/docker: Docker 数据目录由 Docker 管理,手动修改可能导致数据损坏。
cgroup 版本一致性: Kubernetes 要求 kubelet 和容器运行时使用相同的 cgroup 版本。Docker 20.10+ 默认 cgroup v2。
overlay2 内核要求: overlay2 需要 Linux 4.0+ 内核,且需要
overlay文件系统支持。使用modprobe overlay确认模块已加载。
下一步
→ 04 - 镜像管理:学习 Docker 镜像的拉取、标记、多架构构建等操作。