深度利用 docker snapshotter 机制分析容器块级数据变动,需理解其将文件操作映射到底层存储的原理,结合 containerd 接口抓取快照差分、内核工具(blktrace/io.stat/btrfs/zfs)验证物理 i/o,并用 ebpf 拦截系统调用精确定位块写入。

要深度利用 Docker 架构中的 Snapshotter 机制分析容器运行时的物理块级数据变动,关键不是“看日志”或“查目录”,而是理解它如何把文件系统操作映射到底层存储单元,并借助其接口与工具链主动观测、拦截和量化这些变动。
明确 Snapshotter 的观测边界:它不暴露块设备,但可推导块行为
Snapshotter(如 overlayfs、stargz、nydus)本身是用户态抽象,不直接操作磁盘扇区或 NVMe block,但它管理的“快照”本质是**有明确写入边界的存储单元集合**。例如:
-
overlay2的upperdir每次 copy-up 或新建文件,都会触发底层文件系统(如 ext4/xfs)分配新 inode 和 data block —— 这就是块级变动的起点; -
stargz-snapshotter在按需解压时,会以 1MB~4MB 的压缩 chunk 为单位拉取并解压到内存页,再刷入快照层 —— 这些 chunk 解压后落地的 fs block 可被追踪; - 所有 snapshotter 的
Prepare()和Commit()调用,都对应一次原子性的“快照创建/固化”,期间发生的全部写操作,天然构成一个可观测的数据变更窗口。
用 containerd 的 snapshot 接口抓取变更上下文
Containerd 的 snapshotter 接口提供了精确的生命周期钩子,可在容器启动前后主动获取快照状态,从而对比出真实变动:
- 在容器启动前调用
MakeSnapshot("pre-run", "")创建基线快照; - 让容器运行一段时间(如执行一段写入脚本);
- 调用
MakeSnapshot("post-run", "pre-run")创建差分快照(parent 指向 pre-run); - 用
Stat()查两个快照的Usage字段(单位字节),得到净增数据量; - 调用
Mounts()获取两个快照的挂载路径,再用find /path -xdev -type f -printf "%s %p\n" | sort -n对比文件大小变化,定位高频小文件写入点。
结合内核层工具定位真实块 I/O 模式
Snapshotter 层只能告诉你“哪些文件变了”,要确认是否发生物理块重写(而非仅元数据更新),需下沉一层:
- 用
blktrace -d /dev/sda -o trace.bin+blkparse trace.bin抓取容器进程(通过 cgroup path 关联)触发的块请求类型(WS写、WR读、WM元数据); - 配合
cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/io.stat</container-id>查该容器实际发出的io_service_bytes_recursive,验证 snapshot 差值是否匹配真实写入量; - 若使用
btrfs或zfsbackend,可直接用btrfs filesystem usage或zfs list -o written,used查快照间共享/独占 block 数,获得真正物理块级复用率。
用 eBPF 精准挂钩快照写入路径(进阶)
对于需要实时、低开销监控的场景,可绕过用户态 snapshotter,直接在内核拦截其依赖的系统调用:
- 监控
copy_file_range()、splice()、writev()等调用,过滤目标容器的 pid/ns,统计每次调用的 buffer size 和 target inode; - 用
bpftrace脚本关联ext4_write_beginkprobe,输出每个写操作对应的逻辑块号(lbk)和长度,再与 snapshot 目录下的文件 offset 映射,反推出哪些快照层文件触发了哪些物理块写; - 配合
perf record -e 'syscalls:sys_enter_write*' -p $(pgrep -f "containerd.*snapshot"),捕获 snapshotter 自身的写系统调用栈,判断是 metadata 更新还是 payload 写入。
不复杂但容易忽略:Snapshotter 不是黑盒,它是容器存储可观测性的锚点。从接口调用切入,叠加上下文快照对比,再辅以内核 I/O 和 eBPF 追踪,就能把“容器写了什么”精准还原为“到底改了哪几个物理块”。











