background

早期的镜像分发 是直接从 registry 拉取, 后来有了p2p 模式的大规模网络镜像拉取的优化, 但是论文指出 大规模的容器集群(container cluster) 这种方式还是不够用, 因此有了 论文中 提到的 DADI(Data Acceleration for Disaggregated Infrastructure): 分层基础设施的数据加速 (本人直译).

DADI 的本质在于将 container 的每一层 image 都映射了一个块设备上 (我也是很惊讶), 读取container 本质上就是 读取相应的 块设备, 并行化程度高了很多, 同时还能避免 原来镜像分发中 重复的内容, 去重又进一步降低了需要传输的数据量(后面有数据). 除此之外, 为了平衡各个主机的网络流量, 还使用了 p2p 架构, 可以直接从其他节点的缓存获取数据(后续在 data load 流程中展开), 延迟进一步降低. 按照论文的说法, 4s内可以拉取 总结 1000 个主机上 总计 10000 个container.

不过从论文整理上来看, dadi 只是通过 network block device(nbd) 技术将写追加到了 blob 类型的存储, 并做索引, 后面contianer 通过合并多层 image, 定位数据所在的blob 进行读写而已. 因此, 每个 image 并没有占据完整的 lba, 只用不断追加就可以. 和传统的块存储还是差异很大的

design

dadi

分两部分看设计. DADI image server 基于 block interface 处理 image, 底层存储系统无感知(hdfs、nfs、cifs),

一个image layer 的 delta change blocks 是一个 block device. 每个 container 对应对应一个 vnbd. overlay nbd 对应一个 server. 典型的场景, 用户在container 中读取一个文件, 会经历 fs->kernel->nbd protocol->vnbd->dadi module/OverlayBD->multi layer random read (一个文件可能被多层image修改), 个人理解会读取 多个 block device.

(个人想法)需要注意的是, 每个block 多大呢? 按照 image layer 写完就不会修改的特性 (在修改就是另一个image了), 后续应该可以 compact block address.

data model

single image layer

dadi 使用 var-length segments + index 维护数据空间. 看论文的数据结构.

  • moffset 和 pos 是什么意思 ? image 的 blob offset 和 lba 不是一一对应的, 因此需要映射. 读取 需要根据 moffset 读取. 如果只是一层, moffset 没有意义, 但是多层重叠修改的时候, lba 的一段地址空间 可能对应多个 segment的 一部分, 所以需要 moffset 记录实际读取的位置.

  • blob offset 和 lba 怎么理解? blob offset 是 实际存储的位置.

index 是一组offset排序的数组. QEMU 因为 block size 太大 反而导致 index 更大, 不是很理解!! 读取直接根据index 读取. (个人想法) 如果用的是 bytedrive, 因为是不用 记录的, 因为我们本身记录了映射. 感觉他们底层存到了 blob 存储里面了, 所以需要额外记录, 而且这样也不需要处理 vbd 的 size 问题了. 因为是append模型的, 所以 hdfs 这种存储层 无感知模型 也就可替换了

multi image layer (主要是合并算法)

contianer 包含了多层 image layer, 因此 需要聚合多层为 统一的视图(数据采用最新的), 论文统称 “Merged View of Layers”. 在index 加载的时候, 提前算法 lba 每一段range 对应的 index (figure 5挺清晰的), 算法描述在 algorithm 2, 就是从上(最新的layer)向下定位, 用最新 non-hole的 segment 信息, hole segment 可能在下面的层被修改过, 因此需要递归向下定位

查询一段range的blob信息的 伪代码是 algorithm 1, 语言描述如下:

  1. 通过 index 的 end 二叉搜索 找到第一个不小于 offset 的 index (因为offset可能在index表示的lba中间)
  2. 如果 offset 刚好在中间, 那么记录 offset->index.end 作为一段lba; 不在则略过.
  3. 从上面的index 遍历定位 length 范围的数据. 需要注意 中间 以及 end 存在 hole 的情况 (中间的空洞通过比较 待查的offset和 index 的offset 就知道; 尾部通过比较 最终待查的 offset 和 end)

algorithm 3 是对应的读取操作, 先通过调用 algorithm 2 知道 (offset, length) 对应的 segment 信息, 然后读取 segment 指定的位置  (论文应该有问题, pread 应该用 moffset).

算法中的 merged_index 记录的是 一段lba 对应在哪个 blob[i], 用 segment 描述. lookup(offset, length) 返回这段lba 覆盖的 blob, 可能是多个 blob 位于不同的layer.

zfile

避免传统压缩 需要解压缩 offset 前面的地址空间, 提出了 zfile 的设计. 从论文看, 就是将文件分成 chunk 进行压缩, 这样对于 segment 的读取 也就是 解压缩相应的 chunk. 实现上 支持各种 压缩算法 + 字典加速. index 也是压缩的 (感觉没有太大收益, 毕竟数据量少). 无论是 nvme ssd/hdd 收益都很大, 因为 zfile的 读IO的时间收益 > 解压缩的消耗. 另外, 通过 lz4 实现了在线解压缩.

writable layer

这个比较有意思, 通过 log-structed 实现了 Writable Layer, 并且解耦了存储层类型. 不断写入过程中, 存在重叠的现象, 会启动后台gc线程清理, 除此之外, writable layer 写入后 也会进行 compact.

p2p

考虑到 大contianer的服务压力, 会在 host 本机磁盘上缓存 最近使用的数据块. 也支持从 其他host 拉取. 为了提高效率, 这里使用了 tree-structed overlay topology, host role 分为了 dadi agent 和 dadi root, 每个集群都有几个 dadi root 互为灾备(论文说有复制流程). agent 读取数据 必须从parent 读取, 允许层层递归. dadi root 维护topo, 并且分配 parent, 以及从 register 读取 data block 到本地缓存.

dadi root 会为 新的layer 执行 warm-up. 使用 crc32 保护 image block integration.

问题:

  • 这个host是专门的dadi cache 还是说 也是 container host ?看 IO Path 应该就是 container host.

IO Path

请求还是 走 nbd 协议, overlayBD 对于本地没有缓存的 image layer 会请求 p2p agent 的 持久化缓存, 然后持久化本地缓存中(lru缓存最近). qemu 走 dadi block driver, 行为和 nbd 模式一致.

dadi 也通过 graph driver(docker plugin) 和 docker 集成. dadi 实现了 snapshotter(类似graph driver) 和 container 集成. 这样就有 .tgz 已有的镜像格式 以及 dadi 格式. 目前是双格式兼容的. 测试下来, dadi 格式的 download 和 build 速度都有很大的提升 (避免拉取完整的镜像: 存在很多冗余).

ps: 上面提到的 log-structed writable layer, 也就实现了 随机写 转换成顺序写, 加快构建速度. 但是一个 image 的构建有很多 layer, 每个layer 都需要 启停一次 nbd, 太费了. 这里用 stack-commit 模式进行了优化: 直接启动一个新的layer, 后台 commit. 也支持格式转换

历史方案回顾

以前的解决方式

  1. p2p
  2. 按需下载

两种方式都是从 文件系统接口入口, 但是 文件操作粒度大 (新增的 image layer 更新前先要copy整个文件, 硬链接也要copy, 文件属性在多平台之间不兼容;即使一个平台, 不同文件系统之间也没全部支持 hard links/sparse file)。 如果基于文件系统搞: 安全 性能 健壮性 都比 block 难度大

Overlay Block Device (OverlayBD) 的设计:

provides a merged view of a sequence of block-based layers. 支持 multi-layer container flatten 来提升性能, 好处: (1) fine-grained on-demand data transfer of remote images; (2) online decom- pression with efficient codecs; (3) trace-based prefetching; (4) peer-to-peer transfer to handle burst workload; (5) flexible choice of guest file systems and host systems; (6) efficient modification of large files (cross layer block references); (7) easy integration with the container ecosystem.

知识补充

  1. docker container image 和 layer 的关系是什么? container layer 强调共享, 多人共享. 写文件操作是 cow. 快照 和 disk 绑定, 生命周期 从属于 disk

  2. qcow2 image format of QEMU,

  3. exo-clones?

  4. blktrace prefetch 不懂, 为什么用 fio ?

问题

  1. 每个layer一个 virtual block device, 那么load 一个 container 岂不是要读取很多次 block? bm的qps 也就变大了, 这样需要支持多点挂在 只读设备

  2. Startup in our Production Environment 为什么只算一个 metadata 的拉取时间呢? 应该是切分成了 pull metadata + launch app 两部分, dadi 只有在 launch 的时候加载数据, metadata 部分只 load index.

  3. 有意思的是, launch 也很快, 因为 overlaybd 比 overlayfs 更快, 以及 p2p 走cache

  4. DADI realizes each layered image as a separate virtual block device 会不会太浪费, 为什么不是都放在一个盘上? 因为采用了 log-structed 的模式, 将新增部分 追加到了 存储上, 因此表面上 每个 layer 一个 block device 占据了 很大的 lba, 实际上只占用了 很小的空间

  5. overlayfs 还能共享 page cache, 比较惊讶

参考