上一篇介绍了Linux ublk:内核提供标准块设备接口,用户态程序负责具体的存储处理。

本文接着看一种具体的镜像后端——OverlayBD:如何把“共享的基础镜像 + 各自的改动”,变成一块可以正常读写的磁盘?

为便于理解,下面以两个虚拟机共用一个只读基础镜像为例,只讨论分层、按需读取和增量保存,不展开部署命令、内部算法和性能测试。

Architecture

What

先把最容易混淆的一点说清楚:

1
2
3
每个虚拟机拥有自己的磁盘视图
≠
每个虚拟机都要完整复制一份磁盘镜像

OverlayBD(Overlay Block Device,叠加块设备)是一种基于块的分层镜像技术,可以将多层磁盘数据组合成一个统一的磁盘视图,并支持按需读取远端镜像。

结合上一篇,两者的分工可以这样理解:

1
2
3
ublk:提供标准块设备接口,把请求交给用户态处理

OverlayBD:决定读取哪一层的数据,以及把改动保存在哪里

OverlayBD 关注的是“磁盘哪个位置存放什么数据”,不是“哪个路径下有什么文件”。ext4 等文件系统仍然负责组织文件和目录。

所以:

ublk 解决“如何提供一块磁盘”,OverlayBD 解决“这块磁盘的数据如何分层存放和读取”。

OverlayBD与之前介绍的OverlayFS,区别在于叠加的对象不同:

1
2
3
OverlayFS:叠加目录树,提供合并后的文件视图

OverlayBD:叠加磁盘块,提供合并后的磁盘视图

在 OverlayBD 提供的虚拟磁盘上,仍然可以使用 ext4 等普通文件系统。

所以:

OverlayBD 不是另一种 ext4,也不是把多个目录直接合起来;它工作在文件系统下面,负责磁盘块。

Why

假设多个虚拟机使用相同的基础环境,但各自只读取部分内容、修改少量数据。

如果启动前先下载整个镜像,又为每个虚拟机分别复制一份完整磁盘,就会付出两类开销:

1
2
3
完整下载:为暂时用不到的数据,支付传输和等待成本

完整复制:为没有变化的基础内容,重复占用存储空间

OverlayBD 将它们分别处理:

1
2
3
4
5
共享只读层:不必为每个实例重复保存全部基础数据

按需加载:不必等完整镜像下载完,才能开始使用

可写增量层:各自的改动单独保存

因此,它的目的可以概括为:

不必先搬来整块磁盘,也不必为每个实例重新复制整块磁盘。

基础 Example

假设 VM1 和 VM2 使用同一个只读基础镜像,镜像简化为四个块:

1
2
块号:        0    1    2    3
基础层内容: A B C D

字母表示块中的内容,不是四个文件。下面的数量仅用于说明原理,不是论文测试数据。

读取:需要哪些块,再获取哪些块

假设数据尚未缓存在本地,VM1 先读取块 0、1,之后才读取块 3。暂不考虑预读和后台下载:

1
2
3
需要块 0、1 → 获取 A、B → 开始使用

后来需要块 3 → 再获取 D

不需要为了读取 A、B,先把 C、D 也全部下载下来。

修改:只改变自己的磁盘视图

现在,VM1 将块 1 写成新内容 B’,VM2 不做修改:

1
2
3
4
5
6
7
块号:          0    1    2    3

VM1 可写层: — B' — —
共享只读层: A B C D
---------------------------------
VM1 看到: A B' C D
VM2 看到: A B C D

这里的 — 表示该层没有覆盖这个位置,不表示内容为零。

VM1 再读取块 1 时,使用自己的 B’;读取其他块时,继续使用基础层中的内容。

共享基础层的 B 没有被改掉,因此 VM2 仍然读到 B。

为什么这样可以省空间?

只计算这个例子中的块内容,不计索引、缓存等额外开销:

1
2
3
4
5
两份完整磁盘:
VM1 的 4 个块 + VM2 的 4 个块 = 8 个块

共享基础层 + 各自增量:
基础层的 4 个块 + VM1 新写入的 B' = 5 个块

省掉的是未修改内容的独立副本,不是让新增的数据不再占空间。

How

只看三个动作。

1. 通过索引,找到应该使用哪一层的数据

OverlayBD 用索引记录磁盘位置与实际数据位置的关系:

1
2
3
4
5
要读取的磁盘位置
↓
应该使用哪一层?
↓
数据位于这一层的什么位置?

对于本例,上层覆盖的位置读取上层,其他位置继续读取下层。

组合的是磁盘访问视图,不是先把各层复制、合并成一个完整的新镜像。

2. 数据不在本地时,再从远端获取

定位数据后,已经缓存的内容可以直接复用;缺失的部分再从远端读取,并按缓存策略保留在本地。

使用支持按需解压的压缩层时,也不必先解压整个镜像,只需要读取、解压相关数据块。

不过,按需加载不等于没有等待:第一次读取尚未缓存的数据,仍然可能等待远端 I/O。

3. 将改动保存为新的增量层

运行时写入保存在自己的可写层中,不修改共享的只读层。

需要保存这次环境改动时,可以将可写层提交为一个新的只读增量层。之后继续使用这个环境,再在上面增加新的可写层:

1
2
3
4
5
6
7
8
保存之前:
Base(只读)+ 本次可写层

保存之后:
Base(只读)+ Diff-v1(只读增量层)

继续使用:
Base(只读)+ Diff-v1(只读)+ 新的可写层

这样,保存的是相对已有层的变化,不需要重新保存所有未修改的基础数据。

Diff-v1 仍然依赖下面的基础层,它不是一份可以独立使用的完整磁盘镜像。

DSec 中怎么使用?

DSec 将 OverlayBD 用于 microVM 的可写 ext4 磁盘,包括在虚拟机内运行 Docker 时使用的独立数据磁盘,并通过 ublk 将这些磁盘提供给Firecracker。

论文中的这条路径可以概括为:

1
2
3
4
5
只读镜像数据:保存在 3FS,按需读取,并在本地缓存

运行时写入:保存在节点本地的可写层

保存环境:支持增量磁盘快照,不必重新打包成 EROFS

DSec 的只读基础镜像和工具层仍然使用 EROFS,不是所有镜像都换成了 OverlayBD。

这里的按需读取、镜像分层和增量保存,来自 OverlayBD 及其存储实现;ublk 负责提供块设备接口,并不会自动实现这些功能。

记忆图

以 DSec 的这条可写磁盘路径为例,省略缓存及虚拟机内部的设备细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Guest:microVM 内部

应用 + ext4 文件系统
│
│ 磁盘读写
▼
──────────────────────────────────────
Host:宿主机

Firecracker
│
▼
ublk 块设备
│
│ 请求与完成结果
▼
用户态 OverlayBD 实现
│
┌──────────┴──────────┐
▼ ▼
本地可写层 3FS 中的只读层
保存各自改动 按需获取基础数据

可以简单记成:

1
2
3
ublk:提供磁盘接口

OverlayBD:共享基础数据,按需读取,单独保存改动

一句话概括:

每个实例都能使用自己的磁盘,但不必各存一份完整镜像:基础内容复用,需要的数据按需取,各自的改动另存一层。


参考资料:

  1. Linux Kernel Documentation: Userspace Block Device Driver(ublk)
  2. OverlayBD 官方项目
  3. OverlayBD Documentation: Standalone Usage — Writable Layer
  4. DADI: Block-Level Image Service for Agile and Elastic Application Deployment — USENIX ATC 2020
  5. DeepSeek Elastic Compute (DSec): A Sandbox Infrastructure for Effective Agentic Training at Scale — §5.3、§7