DADI: Block-Level Image Service for Agile and Elastic Application Deployment

发表时间: 2020-07 · USENIX ATC 2020 (Alibaba Group)

原文: https://www.usenix.org/system/files/atc20-li-huiba.pdf

A1 主要贡献

本文针对容器因镜像下载和解包过程而导致的启动缓慢问题,提出了一种名为 DADI 的块级镜像服务,旨在实现应用的敏捷和弹性部署。


图 1: 分层的容器镜像。镜像层(L1, L2)是只读的,由多个容器(C1, C2)共享,而容器层(LC)是私有的可写层。

A3 背景知识/关键Observation/设计原则

2.1 容器镜像

2.2 远程镜像

2.3 基于文件系统的远程镜像

2.4 基于块快照的远程镜像

2.5 其他相关工作

A2 方法细节

3 DADI 镜像服务

DADI 的设计目标是成为容器生态系统的一部分的通用解决方案。其核心(3.1-3.4节)是一个继承了容器镜像分层模型的远程镜像设计,并通过遵循 OCI-Artifacts【31. OCI Artifacts, OCI】标准与镜像仓库保持兼容。DADI 独立于传输协议,因此可以插入一个可选的 P2P 传输模块(3.5节)来应对大规模应用。DADI 也独立于底层存储系统,用户可以选择合适的存储系统(如 HDFS、NFS、CIFS 等)来构建一个完全网络化的解决方案。DADI 采用块级接口,这最小化了攻击面,对于虚拟化安全容器而言,这是一个尤为重要的设计点。


图 2: DADI 镜像。DADI 镜像层(L1, L2)由修改过的数据块组成。DADI 使用一个覆盖块设备为每个容器(C1, C2)提供其层的合并视图。

3.1 DADI 镜像


图 4: 读取 DADI 镜像时的索引查找。查找操作是在一组有序、非重叠的可变长度段上进行范围查询,每个段指向其原始数据在层 blob 中的位置。

3.2 层的合并视图

输入: 要查找的范围 (offset, length)
end ← offset + length;
i ← index.binary_search_first_not_less(offset);
if i < index.size() then
    delta ← offset - index[i].offset;
    if delta > 0 then // 裁剪并产生第一个段
        s ← index[i];
        s.offset ← offset;
        s.moffset += delta;
        s.length -= delta;
        yield s;
        offset ← s.end();
        i++;
    end
end
while i < index.size() and index[i].offset < end do
    len ← index[i].offset - offset;
    if len > 0 then // 产生一个空洞
        yield Hole(offset, len);
        offset ← index[i].offset;
    end
    s ← index[i]; // 产生下一个段
    s.length ← min(s.length, end - offset);
    yield s;
    offset ← s.end();
    i++;
end
if offset < end then // 产生最后一个空洞
    yield Hole(offset, end - offset); // 修正:原文为 end - begin
end

算法 1: 索引查找。在指定的范围(offset, length)内生成一系列段,其中 i 初始化为索引中不小于 offset 的第一个元素,Hole 是一种特殊的段类型,表示从未被写入的范围。

输入: 索引数组 indices[1..n]; 本次递归的索引数组下标 i; 要合并的范围 (offset, length)
for s in indices[i].lookup(offset, length) do
    if s is NOT a Hole then
        s.pos ← i;
        yield s;
    else if i > 0 then // 忽略一个真正的空洞
        indices_merge(indices, i-1, s.offset, s.length);
    end
end

算法 2: 递归方式的索引合并。

输入: 文件对象数组 blobs[0..n]; 要预读的范围 (offset, length)
for s in merged_index.lookup(offset, length) do
    // s.pos == 0 对应 Hole 段
    // blobs[0] 是一个特殊的虚拟文件对象
    // 在预读时产生全零内容
    blobs[s.pos].pread(s.moffset, s.length); // 修正:原文为 pread(s.offset, s.length),根据图4应为moffset
end

算法 3: 基于合并索引的读取。


图 5: 索引合并。


图 6: 生产环境中应用的索引大小。


图 7: 单个 CPU 核心上的索引性能。

3.3 压缩与在线解压


图 8: ZFile 格式。


图 9: 相对层 Blob 大小。

3.4 DADI 容器层


图 10: DADI 的可写层。

3.5 P2P 数据传输


图 11: DADI 的树状 P2P 数据传输。

4 实现与部署

本节讨论 DADI 如何与应用程序和容器引擎接口,以及 DADI 如何在不同的用户场景中部署。

4.1 数据路径


图 12: cgroups 运行时的 I/O 路径。


图 13: 虚拟化运行时(QEMU 等)的 I/O 路径。

4.2 容器引擎集成

4.3 镜像构建

4.4 部署选项

A4 实验环境

A4 实验结果

5.2 启动延迟


图 14: 冷启动延迟。


图 15: 温启动延迟。


图 16: 使用基于追踪的预取时的启动延迟。


图 17: 批量冷启动延迟。


图 18: 生产环境中拉取镜像的时间。条形图表示10%和90%分位数。


图 19: 生产环境中启动应用的时间。

5.3 可伸缩性


图 20: 使用 DADI 的启动延迟(大规模启动)。


图 21: 使用 DADI 的预计启动延迟(超大规模启动)。

5.4 I/O 性能


图 22: 未缓存随机读性能。


图 23: du 所有文件的时间。


图 24: tar 所有文件的时间。

5.5 镜像构建速度


图 25: 构建镜像的时间。

A7 补充细节

6 讨论与未来工作

A5 结论

本文设计并实现了 DADI,一个用于容器的块级远程镜像服务。DADI 的核心思想是,增量镜像可以通过基于块的层来实现,其中每个层对应一组文件变更,但在物理上是给定文件系统下的块级变更集合。这种设计使得镜像服务与文件系统和平台无关,从而能够在不同环境中弹性部署应用。块级层的相对简单性进一步促进了一系列优化,以提高敏捷性,包括:细粒度的按需数据传输、使用高效编解码器的在线解压、基于追踪的预取、处理突发工作负载的 P2P 传输,以及与容器生态系统的轻松集成。DADI 在全球最大电子商务平台之一的生产环境中的应用经验表明,它在提高应用部署的敏捷性和弹性方面非常有效。