containerd 2.1.8 超深度分析 — Metadata存储(BoltDB) + Content(CAS) + Snapshot + Diff + Mount

源码:core/metadata/ (6217行) + core/content/ (617行) + core/snapshots/ (397行) + core/diff/ (684行) + core/mount/ (2155行)


一、Metadata — BoltDB 元数据存储

1.1 BoltDB 架构

containerd 使用 BoltDB (现在叫 bbolt) 作为元数据存储引擎。所有容器、镜像、快照等元数据都存储在 BoltDB 中。

Bucket 层次结构

Root Bucket

version
(数据库版本号)

namespace/
(按 namespace 分桶)

namespace/{ns}/
├── containers
├── images
├── content
│ ├── ingest
│ └── blob
├── snapshots
│ └── {snapshotter}
├── leases
└── volumes

1.2 DB 结构体

type DB struct {
    db     *bolt.DB                   // BoltDB 实例
    co     *containerOpts              // 容器选项
    wlock  *kmutex.KeyedMutex         // 写锁 (per-key)
    store  map[string]content.Store    // Content Store 集合
    ss     map[string]snapshots.Snapshotter  // Snapshotter 集合
}

DB.View / DB.Update — 事务封装

func (m *db) View(fn func(*bolt.Tx) error) error {
    return m.db.View(fn)  // 只读事务
}

func (m *db) Update(fn func(*bolt.Tx) error) error {
    m.wlock.Lock(string(tx.WriteTo(nil)))  // 按 namespace 加写锁
    defer m.wlock.Unlock(string(tx.WriteTo(nil)))
    return m.db.Update(fn)  // 读写事务
}

设计kmutex.KeyedMutex 提供按 namespace 的细粒度锁,避免不同 namespace 的事务互相阻塞。

1.3 Container 存储

func (m *containerStore) Create(ctx context.Context, container containers.Container) (containers.Container, error) {
    return m.db.Update(ctx, func(tx *bolt.Tx) error {
        bucket := tx.Bucket([]byte(namespace)).Bucket([]byte("containers"))

        // 1. 检查是否已存在
        existing := bucket.Get([]byte(container.ID))
        if existing != nil {
            return errdefs.ErrAlreadyExists
        }

        // 2. 序列化
        data, _ := proto.Marshal(containerToProto(&container))

        // 3. 写入 BoltDB
        return bucket.Put([]byte(container.ID), data)
    })
}

1.4 GC — 垃圾收集

func (m *db) GarbageCollect(ctx context.Context) (gc.Stats, error) {
    // ─── 标记阶段 ───
    // 1. 从所有 leases 开始标记
    // 2. 标记所有引用的 content blobs
    // 3. 标记所有引用的 snapshots
    // 4. 标记所有 images 引用的 manifests/blobs

    // ─── 清除阶段 ───
    // 5. 删除未被标记的 content blobs
    // 6. 删除未被标记的 snapshots
    // 7. 删除未被标记的 ingest 临时数据
}

GC 标记-清除流程

Sweep (清除)

Mark (标记)

从 Leases 开始

标记 Images → manifests

标记 Manifest → config blob + layer blobs

标记 Containers → snapshots

标记 Snapshot → parent snapshots

删除未标记的 content blobs

删除未标记的 snapshots

删除未标记的 ingest


二、Content — 内容寻址存储 (CAS)

2.1 Content Store 接口

type Store interface {
    Info(ctx context.Context, dgst digest.Digest) (Info, error)        // 查询 blob 信息
    Walk(ctx context.Context, fn WalkFunc, filters ...string) error     // 遍历所有 blob
    Delete(ctx context.Context, dgst digest.Digest) error               // 删除 blob
    Update(ctx context.Context, info Info, fieldpaths ...string) (Info, error) // 更新元数据
    Writer(ctx context.Context, opts ...WriterOpt) (Writer, error)      // 获取写入器
    ReaderAt(ctx context.Context, dgst digest.Digest) (io.ReaderAt, error) // 读取 blob
    Status(ctx context.Context, ref string) (Status, error)             // 查询上传状态
    Abort(ctx context.Context, ref string) error                        // 中止上传
}

2.2 Content 存储路径

/var/lib/containerd/io.containerd.content.v1.content/
├── blobs/
│   └── sha256/
│       ├── a1b2c3d4...   ← blob 文件 (以 digest 命名)
│       └── e5f6g7h8...
└── ingest/
    ├── ref-123/          ← 正在上传的临时数据
    └── ref-456/

Content 写入流程

blobs/sha256/ ingest 目录 Content Store 调用者 blobs/sha256/ ingest 目录 Content Store 调用者 计算 digest + 校验 Writer(ctx, ref="pull-nginx") 创建 ingest/ref-nginx/data Writer Write(p) 追加数据 Commit(dgst) rename(ingest → blobs/sha256/abcd) 删除 ingest/ref-nginx/ Info{Digest: "sha256:abcd"}

三、Snapshot — 快照管理

3.1 Snapshotter 接口

type Snapshotter interface {
    Stat(ctx context.Context, key string) (Info, error)               // 查询快照信息
    Update(ctx context.Context, info Info, fieldpaths ...string) (Info, error)
    Usage(ctx context.Context, key string) (Usage, error)             // 查询磁盘使用
    Mounts(ctx context.Context, key string) ([]mount.Mount, error)    // 获取 mount 参数
    Prepare(ctx context.Context, key, parent string, opts ...Opt) ([]mount.Mount, error)  // 准备可写快照
    View(ctx context.Context, key, parent string, opts ...Opt) ([]mount.Mount, error)     // 准备只读快照
    Commit(ctx context.Context, name, key string, opts ...Opt) error  // 提交快照
    Remove(ctx context.Context, key string) error                     // 删除快照
    Walk(ctx context.Context, fn WalkFunc, filters ...string) error   // 遍历
    Close() error                                                      // 关闭
}

3.2 快照类型与转换

类型 Kind 描述
Active KindActive 可写快照 (Prepare 创建)
Committed KindCommitted 只读快照 (Commit 创建)
View KindView 只读视图 (View 创建)

快照父子关系

Prepare

Commit

Commit

Prepare

View

(empty)

layer-0
(Committed)

layer-1
(Committed)

layer-2
(Committed)

container-active
(Active)

container-view
(View)

3.3 overlayfs snapshotter 实现原理

Prepare("container-1", "layer-2") → 返回 mount 参数:
  type: "overlay"
  options: lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/2,
           upperdir=/var/lib/containerd/.../snapshots/3/fs,
           workdir=/var/lib/containerd/.../snapshots/3/work

Commit("nginx-rootfs", "container-1"):
  1. 将 upperdir 的内容变为只读
  2. 删除 workdir
  3. 标记 snapshot 3 为 Committed

四、Diff — 差异计算

4.1 Differ 接口

type Differ interface {
    Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount) (ocispec.Descriptor, error)
    DiffMounts(ctx context.Context, lower, upper []mount.Mount, opts ...Opt) (ocispec.Descriptor, error)
    Compare(ctx context.Context, a, b ocispec.Descriptor, opts ...Opt) (ocispec.Descriptor, error)
}

4.2 Walking Diff 实现

func (d *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount, opts ...Opt) (ocispec.Descriptor, error) {
    // 1. Mount lower 和 upper 到临时目录
    // 2. 遍历 upper 目录,与 lower 比较
    // 3. 新增/修改/删除的文件 → 写入 tar 流
    // 4. 计算 digest
    // 5. 存储 content blob
    // 6. 返回 descriptor
}

Diff 类型

场景 方法 描述
Layer diff DiffMounts(lower, upper) 两个 mount 间的差异
Layer apply Apply(desc, mounts) 将 diff 应用到 mount
Manifest compare Compare(a, b) 两个镜像的差异

五、Mount — 挂载操作封装

5.1 Mount 结构体

type Mount struct {
    Type    string            // 文件系统类型 (overlay, bind, proc, ...)
    Source  string            // 源路径
    Options []string          // 挂载选项 (rw, lowerdir=..., upperdir=...)
}

5.2 关键操作

func (m *Mount) Mount(target string) error {
    // 调用 unix.Mount(m.Source, target, m.Type, flags, data)
}

func All(mounts []Mount, target string) error {
    // 依次挂载所有 mount 点
}

六、核心协作关系

6.1 Pull → Unpack → Snapshot 协作

Metadata (BoltDB) Snapshotter (overlayfs) Diff Service Content Store Registry Transfer Service Metadata (BoltDB) Snapshotter (overlayfs) Diff Service Content Store Registry Transfer Service loop [每个 layer] Unpack 阶段 继续直到所有 layer 解压完成 Fetch manifest WriteBlob(manifest) WriteBlob(config blob) Fetch layer blob (tar+gzip) WriteBlob(layer blob) CreateImage(ref, manifest digest) Prepare("unpack-1", parent="") []Mount Apply(layer-desc, mounts) ReadBlob(layer-desc) mount + 解压 tar 到 mount 点 Commit("layer-0", "unpack-1") Prepare("unpack-2", parent="layer-0") Apply(layer-desc, mounts) Commit("layer-1", "unpack-2")

七、设计模式总结

# 模式 体现
1 CAS (Content Addressable) Content Store 以 digest 为 key
2 Copy-on-Write overlayfs snapshotter
3 标记-清除 GC 从 leases/images 标记,清除未引用资源
4 Namespace 隔离 BoltDB per-namespace bucket
5 Ingest 临时区 Writer → ingest → commit → blobs
6 父子快照链 Prepare → Active → Commit → Committed
7 Walking Diff 逐文件遍历比较
8 Per-key Mutex kmutex 按 namespace 加锁
9 Proxy 模式 content/snapshot proxy (gRPC 代理)
10 双层存储 Metadata (BoltDB) + Content (文件系统)
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐