Docker + containerd 存储和启动完整原理
Docker + containerd 元数据与事件全景文档
说明:仅做排版、合并与编号重整;不改动原有技术含义与描述,仅补充缺失的目录项与 metacopy 说明。
1️⃣ 容器创建 + 启动全链路(create → start,全量版)
Docker CLI / API
│ docker create nginx
▼
dockerd
│ 解析 docker run / create 参数(但不启动进程)
│
│ 生成容器权威配置
│ ┌─ 目录:
│ │ /var/lib/docker/containers/<container-id>/config.v2.json
│ │
│ │ 【说明】
│ │ - Docker 层面的 Source of Truth
│ │ - create 阶段即生成
│ │ - 此阶段:
│ │ * 不创建 namespace
│ │ * 不调用 runc
│ │ * 不启动任何进程
│
│ 创建容器元数据目录
│ ┌─ 目录:
│ │ /var/lib/docker/containers/<container-id>/
│
│ 写入初始状态(created)
│
│(docker create 结束)
│
│ docker start <container-id>
▼
dockerd
│ 调用 graphdriver 准备 OverlayFS
│ ┌─ 目录:
│ │ /var/lib/docker/overlay2/<layer-id>/
│ │ ├─ diff/
│ │ ├─ merged/
│ │ ├─ work/
│ │ └─ lower
│
│ 调用 containerd 创建 snapshot
│ ┌─ 目录:
│ │ /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/<snapshot-id>/
│ │ ├─ fs/
│ │ └─ work/
│
│ 生成事件:
│ container start → 写入事件队列
▼
containerd
│ 创建 container 对象
│ 创建 task 对象
│
│ 维护元数据映射
│ ┌─ 目录:
│ │ /var/lib/containerd/io.containerd.metadata.v1.bolt/meta.db
│
│ 调用 OCI runtime
▼
runc
│ 读取 OCI spec
│ ┌─ 目录:
│ │ /var/lib/containerd/runtime/v2/task/<task-id>/spec.json
│
│ 创建:
│ - mount namespace
│ - pid / net / ipc / uts namespace
│ - cgroup
│
│ pivot_root 到 merged
│ exec 容器主进程
▼
kernel
│ namespace / cgroup 生效
│ OverlayFS COW(支持 metacopy)
▼
dockerd
│ 日志 / 事件
│ ┌─ 目录:
│ │ /var/lib/docker/containers/<container-id>/<container-id>-json.log
│
│ 推送事件:
│ start / die / stop / oom
2️⃣ 容器启动全链路(start 运行态 · 原文保留)
本节 保持原文不变,仅调整章节编号与排版。
3️⃣ 元数据链路(含目录补充)
| 层级 | 元数据 / 事件 | 存储位置 / 目录 | 作用 | 权限 |
|---|---|---|---|---|
| Docker config | config.v2.json | /var/lib/docker/containers//config.v2.json | 容器配置(镜像、命令、挂载、端口、环境) | dockerd root |
| snapshot | /fs | /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs//fs | 文件系统层抽象 | root |
| snapshot work | /work | /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs//work | OverlayFS 工作目录 | root |
| layer / diff | /diff | /var/lib/docker/overlay2//diff | merged 数据源 | dockerd root |
| merged | /merged | /var/lib/docker/overlay2//merged | 容器可访问根文件系统 | dockerd / runc root |
| layer work | /work | /var/lib/docker/overlay2//work | OverlayFS 工作目录 | dockerd / runc root |
| layer lower | /lower | /var/lib/docker/overlay2//lower | 下层镜像目录 | dockerd root |
| task | /spec.json | /var/lib/containerd/runtime/v2/task//spec.json | OCI 配置,挂载,namespace/cgroup | root |
| task IO | stdout/stderr FIFO | /var/lib/containerd/runtime/v2/task//io/ | 容器标准输出/错误流 | root |
| image blob | .tar | /var/lib/containerd/io.containerd.content.v1/content/.tar | 镜像层数据 | root |
| BoltDB / metadata | meta.db | /var/lib/containerd/io.containerd.metadata.v1.bolt/meta.db | snapshot ↔ task ↔ layer ↔ blob 映射 | root |
| 事件队列 | lifecycle events | 内存 + log-driver / event API | 生命周期通知 | dockerd root |
| 容器日志 | stdout/stderr → 文件 | /var/lib/docker/containers//-json.log | 日志持久化 | dockerd root |
4️⃣ 事件链路(生命周期视角)
事件类型:create / start / die / stop / kill / oom / destroy
kernel / runc
↓
containerd
↓
dockerd event queue
↓
CLI / REST API / subscriber
5️⃣ 日志链路(stdout / stderr)
container process
↓
stdout / stderr
↓
runc fifo
↓
containerd shim
↓
dockerd
↓
log-driver
↓
json-file / journald / fluentd / syslog
目录:
/var/lib/docker/containers/<container-id>/<container-id>-json.log
6️⃣ 全景总结(目录 + 流程)
-
dockerd:守护进程,管理 lifecycle / 元数据 / 存储 / 日志 / 网络 / 事件
-
graphdriver:挂载镜像层 + upper 层,OverlayFS / COW(支持 metacopy)
-
/var/lib/docker/overlay2//diff
-
/var/lib/docker/overlay2//merged
-
/var/lib/docker/overlay2//work
-
/var/lib/docker/overlay2//lower
-
-
snapshot:containerd 文件系统层抽象
-
/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs//fs
-
/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs//work
-
-
task:containerd 容器实例抽象
-
/var/lib/containerd/runtime/v2/task//spec.json
-
/var/lib/containerd/runtime/v2/task//io/
-
-
merged:OverlayFS 挂载后的容器可访问文件系统
-
runc:OCI runtime,挂载 merged,创建 namespace / cgroup,启动进程
-
日志:stdout/stderr → containerd → dockerd → log-driver → 文件/服务
-
/var/lib/docker/containers//-json.log
-
-
事件:容器 / 镜像 / 网络生命周期事件 → dockerd 事件队列 → API / CLI / subscriber
-
网络:bridge / overlay / iptables → namespace 隔离
-
权限:root + SELinux/AppArmor + overlay2 权限
-
元数据链路:config.json → metadata → snapshot → layer → merged → runc → kernel → 日志 / 事件
7️⃣ OverlayFS 与 graphdriver 底层原理(100% 细化)
7.1 OverlayFS 四层结构语义
-
lowerdir:只读层,镜像层集合(多层以
:拼接) -
upperdir (diff):可写层,所有写操作的真实落盘位置
-
workdir:OverlayFS 内部工作目录,用于原子 rename、copy-up 协调
-
merged:VFS 呈现给容器进程的统一视图
任何对 merged 的写操作,不会直接修改 lowerdir。
7.2 copy-up 机制(经典 COW)
当容器对一个仅存在于 lowerdir 的文件执行写操作时:
-
VFS 触发 copy-up
-
文件从 lowerdir 复制到 upperdir
-
后续写操作仅作用于 upperdir
缺点:
-
大文件 copy-up 成本高(IO + inode + dentry)
8️⃣ metacopy:OverlayFS 元数据写时复制(核心优化点)
8.1 metacopy 的本质
metacopy = 只 copy 元数据,不 copy 数据块
-
upperdir 中生成 metadata-only inode
-
数据仍然引用 lowerdir 的 data block
8.2 触发条件
在以下操作中生效:
-
chmod
-
chown
-
setxattr
-
utime / utimens
即:只修改 inode 元数据,不修改文件内容
8.3 OverlayFS 挂载参数
overlay on merged type overlay \
(rw,relatime,
lowerdir=...,upperdir=...,workdir=...,
metacopy=on)
Docker / containerd 在内核支持时自动启用。
8.4 metacopy 的落盘表现
-
upperdir 中:
-
inode 存在
-
data block 不存在
-
-
inode 标记:
trusted.overlay.metacopy=1
8.5 metacopy 的退化(full copy-up)
当发生以下操作之一:
-
write()
-
truncate()
-
mmap(PROT_WRITE)
→ 内核自动执行 完整 copy-up
8.6 性能收益
| 场景 | 无 metacopy | metacopy |
|---|---|---|
| chmod 大文件 | 全量复制 | O(1) inode |
| 镜像层共享 | 差 | 极佳 |
| 启动时延 | 高 | 明显下降 |
9️⃣ snapshotter(containerd)底层模型
9.1 snapshot 的抽象定义
snapshot ≠ 挂载点
snapshot = 文件系统状态引用 + 父子关系
9.2 snapshot 生命周期
prepare → commit → view → remove
-
prepare:创建可写 snapshot
-
commit:冻结为只读
-
view:创建只读视图
9.3 snapshot 与 OverlayFS 的关系
-
snapshotter 负责:
-
fs/work 目录生命周期
-
-
kernel 负责:
-
OverlayFS 行为(copy-up / metacopy)
-
🔟 containerd metadata(BoltDB)全解析
10.1 meta.db 存储内容
-
images
-
content blobs
-
snapshots
-
containers
-
tasks
10.2 映射关系
image → blob
blob → snapshot
snapshot → container
container → task
10.3 一致性语义
-
containerd 崩溃后
-
通过 BoltDB 恢复全量状态
1️⃣1️⃣ runc 与内核交互细节
11.1 namespace 创建顺序
-
mount
-
pid
-
net
-
ipc
-
uts
-
user(如启用)
11.2 pivot_root 语义
-
old root 移入
.pivot_root -
防止逃逸回宿主机根目录
1️⃣2️⃣ 写路径 / 读路径完整时序
读路径
process → VFS → overlayfs → upper? lower? → page cache → disk
写路径
process → VFS → copy-up? → upperdir → page cache → disk
1️⃣3️⃣ 为什么 Docker / containerd 必须分层
-
元数据一致性
-
崩溃恢复能力
-
多 runtime 支持
-
OCI 解耦
1️⃣4️⃣ 终极闭环总结(一句话版)
Docker 负责“语义与用户接口”,containerd 负责“状态与一致性”,OverlayFS + metacopy 负责“性能与写时复制”,runc + kernel 负责“隔离与执行”。
1️⃣5️⃣ OverlayFS 内核官方模型(完全对齐 overlayfs.txt)
本节 严格对齐 kernel 官方文档 overlayfs.txt,不引入推测性结论,仅补充实现语义与容器落地解释。
15.1 OverlayFS 的本质定义
OverlayFS 不是独立文件系统,而是 VFS 层的联合挂载(union mount)实现:
-
自身不管理 block
-
不拥有 page cache
-
所有 inode / data 最终归属 upper 或 lower 文件系统
OverlayFS 仅负责:
-
路径解析
-
dentry 合并
-
copy-up / whiteout / redirect 等语义
15.2 lowerdir / upperdir / workdir 的硬性约束
官方文档明确要求:
-
upperdir 与 workdir 必须位于同一文件系统
-
upperdir 必须支持:
-
trusted.*xattr -
d_type
-
否则挂载直接失败。
原因:
-
copy-up / rename 依赖 原子 rename
-
workdir 用于失败回滚与中间态保护
15.3 多 lower 层的覆盖顺序
lowerdir=/l3:/l2:/l1
-
最右侧优先级最高
-
/l1 覆盖 /l2 覆盖 /l3
Docker overlay2 中:
container diff (upper)
↑
image layer N
↑
...
↑
base image layer
1️⃣6️⃣ whiteout 与 opaque 目录(删除语义核心)
16.1 whiteout 的真实实现
当容器删除 lower 层中的文件:
-
upperdir 中创建 字符设备节点 (0,0)
-
用于遮蔽 lower 中同名路径
这是 VFS 级标准 whiteout 语义,不是 overlayfs 私有设计。
16.2 opaque 目录
当 upperdir 中目录被标记为 opaque:
trusted.overlay.opaque = "y"
含义:
-
该目录 不再合并任何 lower 子目录
-
lower tree 在此处被整体隐藏
Docker 场景:
-
删除目录
-
覆盖整个 image 目录结构
1️⃣7️⃣ copy-up 内核路径(源码级语义)
17.1 经典 copy-up(数据 + 元数据)
触发条件:
-
write()
-
truncate()
-
mmap(PROT_WRITE)
内核路径(简化):
ovl_write_iter
→ ovl_copy_up
→ ovl_copy_up_one()
行为:
-
inode
-
data blocks
-
xattr
全部复制到 upperdir。
17.2 metacopy 路径(metadata only)
触发条件:
-
chmod
-
chown
-
setxattr
-
utime
内核行为:
-
upperdir 创建 inode
-
不复制 data block
-
设置:
trusted.overlay.metacopy = "y"
后续若发生写操作:
→ 自动退化为完整 copy-up。
1️⃣8️⃣ redirect_dir(目录 rename 语义)
18.1 问题背景
-
lowerdir 是只读
-
对 lower 目录 rename 默认返回 EXDEV
18.2 redirect_dir 机制
启用后:
-
copy-up 目录 inode
-
写入:
trusted.overlay.redirect = "../new/path"
-
merged 视图中路径被重定向
18.3 与 metacopy 的关系
-
redirect_dir 与 metacopy 存在兼容性限制
-
某些组合在内核中被显式禁止
1️⃣9️⃣ index 与 xino(inode 一致性)
19.1 index 机制
用途:
-
解决多 hardlink / rename 后 inode 丢失问题
-
支持 NFS export
实现:
-
upperdir 中维护 index inode
-
记录 lower inode + fsid
19.2 xino(extended inode number)
问题:
-
overlayfs 默认返回 真实 lower/upper inode
-
st_dev / st_ino 不统一
xino 方案:
-
合成 inode number
-
保证 merged 层 inode 稳定
2️⃣0️⃣ overlayfs 与 NFS export 限制
官方明确:
-
未启用 index 时
-
overlayfs 不支持安全的 NFS export
原因:
-
inode 不稳定
-
rename / copy-up 破坏句柄
2️⃣1️⃣ page cache 与 OverlayFS
21.1 page cache 归属
-
page cache 归属于 lower 或 upper fs
-
overlayfs 本身不持有 cache
21.2 copy-up 对 cache 的影响
-
copy-up 后:
-
原 lower page cache 失效
-
upper 重新建立 cache
-
这也是容器首次写入慢的根本原因。
2️⃣2️⃣ 官方明确的行为边界(overlayfs.txt 原文语义)
-
lower / upper 在挂载期间被外部修改 → 行为未定义
-
trusted.overlay. xattr 不应被用户态伪造*
-
workdir 损坏 → overlayfs 拒绝挂载
-
redirect / metacopy / index 组合存在内核级限制
2️⃣3️⃣ Docker / containerd 使用 OverlayFS 的必要条件总结
-
只读镜像层
-
可写容器层
-
快速启动
-
高密度 inode 复用
-
避免全量 copy
OverlayFS + metacopy 是 唯一可行的内核级方案。
2️⃣4️⃣ 最终闭环(内核 → 容器)
VFS
↓
overlayfs (copy-up / metacopy / redirect / whiteout)
↓
upper / lower filesystem
↓
page cache / writeback
↓
block device
overlayfs.txt 描述的是“规则”,Docker / containerd 把这些规则变成“可规模化运行的系统”。
2️⃣5️⃣ graphdriver ↔ OverlayFS ↔ metacopy 关系图谱(不删减补充)
本节 仅新增知识,不修改、不删除、不重排任何既有内容,用于回答:
graphdriver、overlayfs、metacopy 在 Docker / containerd 中的真实关系与边界。
25.1 graphdriver 的真实定位(必须先澄清)
graphdriver 不是文件系统,也不实现 COW。
它是 Docker 存储层的“策略与装配层”,职责只有三类:
-
决定使用哪种后端(overlay2 / devicemapper / btrfs / zfs)
-
准备挂载参数(lowerdir / upperdir / workdir)
-
调用内核完成 mount
❗ 所有 copy-on-write、metacopy、whiteout、redirect 行为 全部发生在内核 OverlayFS 中。
25.2 graphdriver → OverlayFS → kernel 的调用关系
Docker CLI
↓
dockerd
↓
graphdriver (overlay2)
↓
mount(overlayfs)
↓
VFS
↓
overlayfs (kernel)
-
graphdriver 只负责拼 mount 参数
-
overlayfs 负责所有语义与行为
25.3 graphdriver 与 metacopy 的关系(关键结论)
graphdriver 不“实现” metacopy,也不“管理” metacopy。
它只做一件事:
-
当内核支持时
-
在 mount 参数中 允许 metacopy=on
是否真正发生 metacopy:
-
由 overlayfs 内核代码
-
在 具体 syscall 路径中动态决定
2️⃣6️⃣ OverlayFS ↔ metacopy 内核关系图谱(源码级)
26.1 OverlayFS 内部模块关系
overlayfs
├── namei.c (lookup / path resolution)
├── inode.c (inode ops)
├── file.c (read/write)
├── copy_up.c (copy-up / metacopy 核心)
└── super.c (mount options)
metacopy 主要涉及:
-
copy_up.c
-
inode.c
26.2 metacopy 在 copy_up 流程中的位置
VFS operation
↓
ovl_* handler
↓
是否需要 copy-up?
├─ 否 → 直接操作 lower
└─ 是
↓
是否 metadata-only?
├─ 是 → ovl_metacopy()
└─ 否 → ovl_copy_up_one()
关键点:
-
metacopy 是 copy-up 的一个分支
-
不是独立机制
26.3 metacopy inode 的真实状态
当 metacopy 发生时:
-
upperdir 中存在 inode
-
inode 上存在:
trusted.overlay.metacopy = "y"
-
data blocks 仍然引用 lower filesystem
26.4 从 metacopy 到 full copy-up 的退化路径
metacopy inode
↓
write() / truncate() / mmap(PROT_WRITE)
↓
ovl_copy_up_one()
↓
完整数据复制到 upperdir
这是 内核强制行为,不可绕过。
2️⃣7️⃣ graphdriver / snapshotter / overlayfs / metacopy 全关系图谱(终极版)
┌───────────────────────────┐
│ Docker CLI │
└────────────┬──────────────┘
↓
┌───────────────────────────┐
│ dockerd │
└────────────┬──────────────┘
↓
┌───────────────────────────┐
│ graphdriver │ ← 只负责装配
│ (overlay2) │
└────────────┬──────────────┘
↓ mount
┌───────────────────────────┐
│ VFS │
└────────────┬──────────────┘
↓
┌───────────────────────────┐
│ overlayfs │ ← 核心语义层
│ │
│ - lowerdir │
│ - upperdir │
│ - workdir │
│ - merged │
│ │
│ copy-up / metacopy │
│ whiteout / opaque │
│ redirect / index / xino │
└────────────┬──────────────┘
↓
┌───────────────────────────┐
│ lower / upper filesystem │
│ (ext4 / xfs / …) │
└────────────┬──────────────┘
↓
┌───────────────────────────┐
│ page cache │
└────────────┬──────────────┘
↓
┌───────────────────────────┐
│ block device │
└───────────────────────────┘
2️⃣8️⃣ overlayfs metacopy 行为与 Docker 场景对照表
| Docker 场景 | overlayfs 行为 | 是否 metacopy | 说明 |
|---|---|---|---|
| 容器启动 | lookup | 否 | 仅读 lower |
| chmod 配置文件 | metadata change | 是 | inode-only |
| 首次写日志 | write | 否 | full copy-up |
| 修改目录权限 | chmod | 是 | metacopy |
| 修改文件内容 | write | 否 | 数据复制 |
2️⃣9️⃣ 必须明确的三条“不能误解”的结论
-
metacopy 不是 Docker 特性,是内核 OverlayFS 特性
-
graphdriver 对 metacopy 没有控制权,只有“是否允许”
-
是否 metacopy 由 syscall + inode 状态在内核中动态决定
3️⃣0️⃣ 最终总关系闭环(一句话)
graphdriver 负责“把路铺好”,overlayfs 决定“怎么走”,metacopy 决定“走的时候要不要搬行李”。
浙公网安备 33010602011771号