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️⃣ 全景总结(目录 + 流程)

  1. dockerd:守护进程,管理 lifecycle / 元数据 / 存储 / 日志 / 网络 / 事件

  2. 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

  3. snapshot:containerd 文件系统层抽象

    • /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs//fs

    • /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs//work

  4. task:containerd 容器实例抽象

    • /var/lib/containerd/runtime/v2/task//spec.json

    • /var/lib/containerd/runtime/v2/task//io/

  5. merged:OverlayFS 挂载后的容器可访问文件系统

  6. runc:OCI runtime,挂载 merged,创建 namespace / cgroup,启动进程

  7. 日志:stdout/stderr → containerd → dockerd → log-driver → 文件/服务

    • /var/lib/docker/containers//-json.log

  8. 事件:容器 / 镜像 / 网络生命周期事件 → dockerd 事件队列 → API / CLI / subscriber

  9. 网络:bridge / overlay / iptables → namespace 隔离

  10. 权限:root + SELinux/AppArmor + overlay2 权限

  11. 元数据链路: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 的文件执行写操作时:

  1. VFS 触发 copy-up

  2. 文件从 lowerdir 复制到 upperdir

  3. 后续写操作仅作用于 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 性能收益

场景无 metacopymetacopy
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 创建顺序

  1. mount

  2. pid

  3. net

  4. ipc

  5. uts

  6. 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 原文语义)

  1. lower / upper 在挂载期间被外部修改 → 行为未定义

  2. trusted.overlay. xattr 不应被用户态伪造*

  3. workdir 损坏 → overlayfs 拒绝挂载

  4. 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 存储层的“策略与装配层”,职责只有三类:

  1. 决定使用哪种后端(overlay2 / devicemapper / btrfs / zfs)

  2. 准备挂载参数(lowerdir / upperdir / workdir)

  3. 调用内核完成 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️⃣ 必须明确的三条“不能误解”的结论

  1. metacopy 不是 Docker 特性,是内核 OverlayFS 特性

  2. graphdriver 对 metacopy 没有控制权,只有“是否允许”

  3. 是否 metacopy 由 syscall + inode 状态在内核中动态决定


3️⃣0️⃣ 最终总关系闭环(一句话)

graphdriver 负责“把路铺好”,overlayfs 决定“怎么走”,metacopy 决定“走的时候要不要搬行李”。

posted on 2026-01-05 20:42  吃草的青蛙  阅读(1)  评论(0)    收藏  举报

导航