跳到主要内容

9 篇博文 含有标签「dragonfly」

查看所有标签

· 22 分钟阅读

Nydus 镜像加速之内核演进之路

多年来容器化实践告诉我们,优化后的容器镜像搭配 P2P 网络等技术可以有效降低容器部署启动的时间,并可保障容器持续稳定运行。为此我们开发了 Nydus 项目,作为 Dragonfly 下的镜像服务。

而除了启动速度,镜像分层、按需加载等核心特性在容器镜像领域也尤为重要。但是由于没有原生的文件系统支持,大多数都选择了用户态方案,Nydus 最初亦如此。随着方案和需求的不断演进,用户态方案遇到了越来越多的挑战,如性能与原生文件系统相比有较大差距、高密场景下资源开销较大等等。

为此我们设计并实现了兼容内核原生 EROFS 文件系统的 RAFS v6 格式,希望将容器镜像方案下沉到内核态。 同时随着 erofs over fscache 按需加载技术合入 5.19 内核, Nydus 镜像服务的下一代容器镜像分发方案逐渐清晰起来。 同时这也是 Linux 主线内核 首个原生支持、开箱即用的容器镜像分发方案,为容器镜像终态的高密、高性能、高可用和易用性提供坚实保障。

本文将从 Nydus 架构回顾、RAFS v6 镜像格式和 EROFS over Fscache 按需加载技术三个角度来介绍该方案的演变历程。

为了解更多详情,请关注Nydus。 请参考用户手册 以使用 Nydus 的上述特性.

Nydus 架构回顾

一句话总结一下,Nydus 镜像加速服务是一种优化了现有的 OCIv1 容器镜像架构,设计了 RAFS (Registry Acceleration File System) 磁盘格式,最终呈现为一种 文件系统 的容器镜像格式的镜像加速实现。

容器镜像的根本需求,本质上是为了提供容器的根目录 (rootfs),这可以通过文件系统 (file system) 或者是归档格式 (archive format) 来承载,当然也可以在文件系统的基础上二次套娃 (例如通过自定义的块格式来承载), 但本质载体是一个 目录树,体现为文件接口。

先看一下 OCIv1 标准镜像,OCIv1 格式是一种基于 Docker Image Manifest Version 2 Schema 2 格式的镜像格式规范,由 manifest、镜像索引 (optional)、一系列容器镜像层及配置文件组成,细节可以参见相关文档,本文不再赘述。 本质上说 OCI 镜像是一个以层为基本单位的镜像格式,每个层存储了文件级别的 diff data,以 tgz 归档格式存储,如下所示: ociv1

由于 tgz 的限制,OCIv1 存在一些固有问题,例如无法按需加载、较粗的层级的去重粒度、每层 hash 值易变等等。

而一些“二次套娃”方案 (例如基于自定义块格式的容器镜像方案),也存在一些原理性的设计缺陷。例如:

  • 容器镜像最终要体现为一棵目录树,那么就需要相应的文件系统来承载 (例如 ext4),这样整个链路为“自定义块格式 + 用户态块设备 + 文件系统”,相对于文件系统方案其链路更长更复杂,端到端稳定性不可控;
  • 由于块格式对上层的文件系统不感知,无法区分文件系统的元数据和数据并分别处理 (例如压缩);
  • 无法实现基于文件的镜像分析特性例如安全扫描、热点分析和运行时拦截等;
  • 对于多个“二次套娃”容器镜像,无法做到不修改 blob 内容直接 merge 成一个大镜像,而这是文件系统方案的天然能力。

而我们实现的 Nydus 则是一种基于文件系统的容器镜像存储方案。其中将容器镜像文件系统的数据 (blobs) 和元数据 (bootstrap) 分离,让原来的镜像层只存储文件的数据部分。 并且把文件以 chunk 为粒度分割,每层 blob 存储对应的 chunk 数据;因为采用了 chunk 粒度,这细化了去重粒度,chunk 级去重让层与层之间,镜像与镜像之间共享数据更容易,也更容易实现按需加载。 由于元数据被单独分离出来合为一处,因此对于元数据的访问不需拉取对应的 blob 数据,需要拉取的数据量要小很多,I/O 效率更高。Nydus RAFS 镜像格式如下图所示: nydus_rafs

RAFS v6 镜像格式

RAFS 镜像格式演变

在 RAFS v6 格式引入之前,Nydus 使用的是一个完全用户态实现的镜像格式,通过 FUSE 或 virtiofs 接口提供服务。但用户态文件系统方案在设计上存在以下缺陷:

  • 大量系统调用开销不可忽略,例如深度为 1 的随机小 I/O 访问;
  • 当容器镜像中存在大量文件时,频繁的文件操作会产生大量的 fuse 请求,造成内核态/用户态上下文的频繁切换,造成性能瓶颈;
  • 非 FSDAX 场景下,用户态到内核态的 buffer copy 会消耗 CPU 占用;
  • 在 FSDAX (virtiofs 作为接口) 场景下,大量小文件会大量占用 DAX window 资源,存在潜在的性能抖动;频繁切换访问小文件也会产生大量 DAX mapping setup 开销。

这些问题是用户态文件系统方案的天然限制带来的,而如果将容器镜像格式的实现下沉到内核态,就可以从原理上根治上述问题。因而我们引入了 RAFS v6 镜像格式,一个依托于内核 EROFS 文件系统,实现于内核态的容器镜像格式。

EROFS 文件系统介绍

EROFS 文件系统自 Linux 4.19 内核开始存在于 Linux 主线中,过去主要用于嵌入式和移动终端领域,存在于当前各大流行发行版中 (例如 Fedora,Ubuntu,Archlinux,Debian,Gentoo 等等)。 用户态工具 erofs-utils 也已经存在于这些发行版和 OIN Linux system definition 列表中,社区较活跃。

EROFS 文件系统具备如下特征:

  • 适用于多种场景的原生本地只读块文件系统,磁盘格式具备最小 I/O 单位定义;
  • page-sized 块对齐的不压缩元数据;
  • 通过 Tail-packing 内联技术有效节省空间,同时维持高访问性能;
  • 数据均以块为单位寻址 (mmap I/O 友好,不需 I/O 后处理);
  • 随机访问友好的磁盘目录格式;
  • 核心磁盘格式非常简单,且易于增加 payload,扩展性更好;
  • 支持 DIRECT I/O 访问,支持块设备、FSDAX 等多种后端;
  • 同时 EROFS 预留了 boot sector,可支持 bootloader 自启动等需求。

RAFS v6 镜像格式介绍

过去一年,阿里云内核团队对 EROFS 文件系统进行了一系列的改进与增强,拓展其在云原生下的使用场景,使其适应容器镜像存储系统的需求,最终呈现为一个实现于内核态的容器镜像格式,RAFS v6。而除了将镜像格式下沉到内核态,RAFS v6 还在镜像格式上进行了一系列优化,例如块对齐、更加精简的元数据等等。

新的 RAFS v6 镜像格式如下: rafsv6

改进后的 Nydus 镜像服务架构如下图所示,增加了对 (EROFS based) RAFS v6 镜像格式的支持: rafsv6_arch

EROFS over Fscache

erofs over fscache 是阿里云内核团队为 Nydus 开发的下一代容器镜像按需加载技术,同时也是 Linux 内核原生的镜像按需加载特性,于 5.19 版本合入 Linux 内核主线erofs_over_fscache_merge

并于 LWN.net 作为 5.19 合并窗口的 高亮特性erofs_over_fscache_lwn

在此之前业界已有的按需加载几乎都是用户态方案。用户态方案会涉及频繁的内核态/用户态上下文切换,以及内核态/用户态之间的内存拷贝,从而造成性能瓶颈。这一问题在容器镜像已经全部下载到本地的时候尤其突出,此时容器运行过程中涉及的文件访问,都还是会陷出到用户态的服务进程。

事实上我们可以将按需加载的 1) 缓存管理和 2) 缓存未命中的时候,通过各种途径 (例如网络) 获取数据,这两个操作解耦开。缓存管理可以下沉到内核态执行,这样当镜像在本地 ready 的时候,就可以避免内核态/用户态上下文的切换。 而这也正是 erofs over fscache 技术的价值所在。

方案原理

fscache/cachefiles (以下统称 fscache) 是 Linux 系统中相对成熟的文件缓存方案,广泛应用于网络文件系统 (例如 NFS、Ceph 等)。我们的主要工作是,使其支持本地文件系统 (例如 erofs) 的按需加载特性。

此时容器在访问容器镜像的时候,fscache 会检查当前请求的数据是否已经缓存,如果缓存命中 (cache hit),那么直接从缓存文件读取数据。这一过程全程处于内核态之中,并不会陷出到用户态。 erofs_over_fscache_cache_hit

否则 (cache miss) 需要通知用户态的 Nydusd 进程以处理这一访问请求,此时容器进程会陷入睡眠等待状态;Nydusd 通过网络从远端获取数据,通过 fscache 将这些数据写入对应的缓存文件,之后通知之前陷入睡眠等待状态的进程该请求已经处理完成;之后容器进程即可从缓存文件读取到数据。 erofs_over_fscache_cache_miss

方案优势

正如之前所描述的,在镜像数据已经全部下载到本地的情况下,用户态方案会导致访问文件的进程频繁陷出到用户态,并涉及内核态/用户态之间的内存拷贝。 而 erofs over fscache 下则不会再陷出到用户态,让按需加载真的“按需”,从而在提前下载容器镜像的场景下实现几乎无损的性能和稳定性, 最终获得 1) 按需加载与 2) 提前下载容器镜像 这两种场景下真正 统一无损 的方案。

具体来说 erofs over fscache 相对于用户态方案具有以下优势。

1. 异步预取

容器创建之后,当容器进程尚未触发按需加载 (cache miss) 的时候,用户态的 Nydusd 就可以开始从网络下载数据并写入缓存文件,之后当容器访问的文件位置恰好处于预取范围内的时候,就会触发 cache hit 直接从缓存文件读取数据,而不会再陷出到用户态。用户态方案则无法实现该优化。 erofs_over_fscache_prefetch

2. 网络 IO 优化

当触发按需加载 (cache miss) 的时候,Nydusd 可以一次性从网络下载比当前实际请求的数据量更多的数据,并将下载的数据写入缓存文件。 例如容器访问 4K 数据触发的 cache miss,而 Nydusd 实际一次性下载 1MB 数据,以减小单位文件大小的网络传输延时。之后容器访问接下来的这 1MB 数据的时候,就不必再陷出到用户态。 用户态方案则无法实现该优化,因为即使触发 cache miss 的时候,用户态的服务进程同样实现了该优化,下一次容器访问位于读放大范围内的文件数据的时候,同样会陷出到用户态。 erofs_over_fscache_readahead

3. 更佳的性能表现

当镜像数据已经全部下载到本地的时候 (即不考虑按需加载的影响),erofs over fscache 的性能表现显著优于用户态方案,同时与原生文件系统的性能相近,从而实现与原生容器镜像方案 (未实现按需加载) 相近的性能表现。 以下是几个工作负载下的性能测试数据 [1]

read/randread IO

以下是文件 read/randread buffer IO [2] 的性能对比

readIOPSBW性能
native ext4267K1093MB/s1
loop240K982MB/s0.90
fscache227K931MB/s0.85
fuse191K764MB/s0.70
randreadIOPSBW性能
native ext410.1K41.2MB/s1
loop8.7K34.8MB/s0.84
fscache9.5K38.2MB/s0.93
fuse7.6K31.2MB/s0.76
  • "native" 表示测试文件直接位于本地的 ext4 文件系统中
  • "loop" 表示测试文件位于 erofs 镜像内,通过 loop 设备的 DIRECT IO 模式挂载 erofs 镜像
  • "fscache" 表示测试文件位于 erofs 镜像内,通过 erofs over fscache 方案挂载 erofs 镜像
  • "fuse" 表示挂载测试文件位于 fuse 文件系统 [3]
  • "性能" 一栏对各个模式下的性能进行归一化处理,以原生 ext4 文件系统的性能为基准,比较其他模式下的性能

可以看到,fscache 模式下的 read/randread 性能与 loop 模式下的性能基本持平,同时要优于 fuse 模式;但是与原生 ext4 文件系统的性能仍存在一定差距,我们正在进一步分析和优化,理论上该方案可以达到原生文件系统的水平。

文件元数据操作测试

通过对大量小文件执行 tar 操作 [4] 测试文件元数据操作的性能。

Time性能
native ext41.04s1
loop0.550s1.89
fscache0.570s1.82
fuse3.2s0.33

可以看到 erofs 格式的容器镜像的元数据性能甚至优于原生 ext4 文件系统,这是 erofs 特殊的文件系统格式导致的。 由于 erofs 是一个只读 (read-only) 文件系统,因而其所有元数据可以紧密排布在一起,而 ext4 作为可写文件系统,其元数据则分散排布在多个 BG (block group) 中。

典型工作负载测试

测试 linux 源码编译 [5] 这一典型工作负载下的性能表现。

Linux CompilingTime性能
native ext4156s1
loop154s1.0
fscache156s1.0
fuse200s0.78

可以看到,fscache 模式下的 Linux 编译负载性能与 loop 模式、原生 ext4 文件系统的性能基本持平,同时要优于 fuse 模式。

4. 高密部署

由于 erofs over fscache 方案基于文件实现,即每个容器镜像都表现为 fscache 下的一个缓存文件,因而其天然支持高密部署的场景。例如一个典型的 node.js 容器镜像在该方案下对应 ~20 个缓存文件,那么在一个部署有上百个容器的机器中,只需要维护上千个缓存文件。

5. 故障恢复与热升级

当镜像文件全部下载到本地的时候,镜像中文件的访问不再需要用户态服务进程的介入,因而用户态服务进程存在更加充裕的时间窗口来实现故障恢复与热升级功能。这种场景下甚至不再需要用户态进程,从而实现与原生容器镜像方案 (未实现按需加载) 相近的稳定性表现。

6. 统一的容器镜像方案

有了 RAFS v6 镜像格式和 erofs over fscache 按需加载技术,Nydus 同时适用于 runc 与 rund,作为这两种容器场景下的统一的容器镜像分发方案。

另外更重要的,erofs over fscache 是 1) 按需加载与 2) 提前下载容器镜像 这两种场景下真正 统一无损 的方案。一方面,它实现了按需加载特性,在容器启动的时候不需要容器镜像全部下载到本地,从而助力极致的容器启动速度。 另一方面,它又完美兼容容器镜像已经下载到本地的这一场景,在文件访问过程中不再频繁陷出到用户态,从而实现与原生容器镜像方案 (未实现按需加载) 近乎无损的性能和稳定性表现。

展望与感谢

之后我们会对 erofs over fscache 方案进行持续迭代与完善,例如不同容器之间的镜像复用,stargz 和 FSDAX 支持,以及性能优化等。

最后感谢方案开发过程中支持和帮助过我们的所有个人与团队,感谢快手与字节跳动的同学对该方案的大力支持,让我们携手一起构建一个更好的容器镜像生态 :)

  1. 测试环境 ECS ecs.i2ne.4xlarge (16 vCPU, 128 GiB Mem),本地 NVMe 盘
  2. 测试命令 "fio -ioengine=psync -bs=4k -direct=0 -rw=[read|randread] -numjobs=1"
  3. 使用 passthrough_hp 作为 fuse daemon
  4. 测试 "tar -cf /dev/null linux_src_dir" 命令的执行时间
  5. 测试 "time make -j16" 命令的执行时间

· 8 分钟阅读
anjia0532

简单介绍下

  • google jib :支持 gradle 和 maven,用于构建 java 应用镜像时,将基础镜像(jdk),
  • 依赖(jar lib),资源文件(resources),class 文件等进行分层(layer),这样依赖,在拉取和推送镜像时,起到加速和节省带宽的目的。
  • Dragonfly2:是阿里开源的一款基于 P2P 协议的,镜像和文件分发加速工具,
  • 与 dragonfly1 相比,dragonfly2 用 golang 重构了,运行时占用资源更少。理论上可以基于 dragonfly 做一个局域网 CDN,及局域网镜像加速器,
  • 文件通过 dragonfly 下载后,缓存到局域网内,再次请求时,如果局域网节点内有,且未过期,则通过 p2p 协议从局域网内拉取,防止占用公网带宽及某个节点过 载被打死的情况。

简单总结下,jib 解决的是 java 应用动不动 100M+甚至 1G+的情况(变成了 80M JDK(基本不变)+200M jar(基本不变)+300M resource(基本不变)+ 1M class(每次发版会变)), 而 dragonfly2 解决的是节省公网带宽,减少内部 registry 节点过热的情况,加起来就是,容器 push&pull 的过程更快了

其实三年前写过阿里 Dragonfly+google jib 的文章,但是时间比较久远,有些内容已经过时,所以准备重新整理下。 之前文章如下:

Google Jib

jib 支持 maven: jib-maven-plugingradle: jib-gradle-plugin , 以及常见的 多模块场景

翻了翻新版的 jib-maven-plugin 文档, 主要部分跟我之前的 加速和简化构建 Docker(基于 Google jib) 差不多,不再CV

但是加了不少新特性,比如

特别的说一下,jib 的很多配置,除了改 pom.xml 外,还支持无侵入的通过命令行指定,并且,命令行传入优先级更高, 比如 mvn compile com.google.cloud.tools:jib-maven-plugin:3.2.0:build -Dimage=<MY IMAGE>中的-Dimage= 对应的是 pom.xml 里的<configuration><to><image></image></to></configuration>

K8S(kubernetes)以 rke2 为例

安装 k8s 集群,如果只是简单测试一下,可以用 k3s 或者 本地分布式开发环境搭建使用 Vagrant 和 Virtualbox 或者 rke2 (如果会用 ansible 也可以用 ansible playbook rke2 )

如果想可视化看下 k8s 集群,可以用kube-explorer, k9s 也可以用rancher2.6(注意如果用 rancher 的话 ,注意 rke2 安装的版本,以最新稳定版本 2.6.3为例,默认支持的是 k8s v1.21.7,也就是得用rke2 v1.21.7+rke2r2)

Dragonfly2

详细内容可参考官网 d8y.io

helm 安装

官方文档 https://github.com/dragonflyoss/helm-charts/blob/main/charts/dragonfly/README.md

考虑到国内特殊国情,可能会访问 github 失败,可以用 https://gitee.com/projects/import/url 中转(也可以用我的 https://gitee.com/anjia/dragonflyoss-helm-charts)

git clone https://gitee.com/anjia/dragonflyoss-helm-charts.git

cd ./dragonflyoss-helm-charts/charts/dragonfly

helm dependency update

helm install --create-namespace --namespace dragonfly-system dragonfly . -f values.yml

如果要自定义参数,通过 -f values.yml 来指定,如果默认则移除 -f values.yml, 支持的配置有 https://github.com/dragonflyoss/helm-charts/tree/main/charts/dragonfly#values

注意点

  1. dragonfly 的 helm 支持 docker 和 containerd 两种引擎,官方推荐使用 containerd(因为支持 fallback,docker 不支持),如果是加速多镜像库官方推荐使 用 containerd1.5.x+, 因为 /etc/containerd/config.toml 是 version2 版本,支持多个注册中心的加速,否则只支持一个,当然也有办法解决,后边再说。
  2. rke2 是通过 /etc/rancher/rke2/registries.yaml 来生成 /var/lib/rancher/rke2/agent/etc/containerd/config.toml的, 而目前版本的 helm 不支持自定义/etc/containerd/config.toml就会导致 daemon 启动失败, 提了个 pr 还没过 https://github.com/dragonflyoss/helm-charts/pull/51,可以先手动修改
  3. 通过 d7y 的 helm 修改的 config.toml 一重启 rke2-server/agent 就会被覆盖,所以,最终要修改 /etc/rancher/rke2/registries.yaml , 而这个改动需要重启 rke2-server/agent 才生效,所以注意测试是否对业务有影响,尽量一次改完
  4. 注意污点(taints)对于 d7y daemon 的影响,如果确定要不走 d7y 的,注意别改 /etc/rancher/rke2/registries.yaml, 虽然 containerd 有 fallback,但是多少影响点时间不是么,如果有污点也有用 d7y 记得在 values 里加上对应的容忍(tolerations)
  5. 注意 d7y 的磁盘规划,以及缓存时间的设置
  6. 可以通过 多次运行 time sudo /var/lib/rancher/rke2/bin/crictl --config=/var/lib/rancher/rke2/agent/etc/crictl.yaml pull xxx:latest 镜像来评估 d7y 对于镜像的加速作用(如果是在一台执行,记得执行 sudo /var/lib/rancher/rke2/bin/crictl --config=/var/lib/rancher/rke2/agent/etc/crictl.yaml rmi --prune来清理无用镜像)
  7. containerd1.4.x 支持多注册中心的办法:1. 等 d7y 官方支持,参见 PRchore: enable range feature gate in e2e, 2,等 rancher 官方支持 containerd1.5.9 且你的集群升得动,3,改 hosts 劫持(但是不支持 fallback),4,只加速最常用的一个注册中心, 5,将其他不常用的注册中心的镜像 pull&push 到加速的注册中心里(注意别有镜像冲突)6,起两套 daemon 分别监听 65001 65002
  8. d7y 支持预热功能,但是 consoleui 版本的,暂时没测通,api 版本可以,
  9. 参见文档 Preheat API
  10. Harbor p2p 预热支持 d7y
  11. containerd 如果要配置私有镜像库加速,需要配置127.0.0.1:65001的 auth,详见 issues dragonflyoss/Dragonfly2/#1065

附赠:docker hub 转移镜像到阿里私服 bash 脚本

注意将 xxxx 替换成实际值 用法 /path/to/pull_push.sh nginx:alpine

#!/usr/bin/env bash

sudo service docker start

sudo docker login -uxxxx -pxxxxx registry.cn-zhangjiakou.aliyuncs.com

sudo docker pull $1

sudo docker tag $1 registry.cn-zhangjiakou.aliyuncs.com/xxxx/${1##*/}

sudo docker push registry.cn-zhangjiakou.aliyuncs.com/xxxx/${1##*/}

招聘小广告

山东济南的小伙伴欢迎投简历啊 加入我们 , 一起搞事情。 长期招聘,Java 程序员,大数据工程师,运维工程师,前端工程师。

参考资料

· 12 分钟阅读

Dragonfly 发布 Nydus 容器镜像加速服务

镜像对容器部署的挑战

在容器的生产实践中,偏小的容器镜像能够很快的部署启动。当应用的镜像达到几个GB以上的时候,在节点上下载镜像通常会消耗大量的时间。Dragonfly 通过引入 P2P 网络有效的提升了容器镜像大规模分发的效率。然而,用户还是必须等待镜像数据完整下载到本地,然后才能创建自己的容器。我们希望进一步缩减镜像下载的时 间,让用户能够更快地部署容器应用。同时,如何更好的保护用户的数据,也是容器行业近年来的重要关注点。

为此,我们为Dragonfly项目引入了一个容器镜像加速服务 nydus 。nydus 能够极大缩短镜像 下载时间,并提供端到端的镜像数据一致性校验,从而让用户能够更安全快捷地管理容器应用。nydus 由阿里云和蚂蚁集团的工程师合作开发,并大规模部署在内部的 生产环境中。 作为云原生生态的一部分, nydus 在生产环境的优秀表现,让我们有信心现在将项目开源,让更多的容器用户能够体验到容器快速启动和安全加载方面 的能力。

Nydus: Dragonfly 的容器镜像服务

nydus 项目优化了现有的 OCI 镜像标准格式,并以此设计了一个用户态的文件系统。通过这些优化,nydus 能够提供这些特性:

  • 容器镜像按需下载,用户不再需要下载完整镜像就能启动容器
  • 块级别的镜像数据去重,最大限度为用户节省存储资源
  • 镜像只有最终可用的数据,不需要保存和下载过期数据
  • 端到端的数据一致性校验,为用户提供更好的数据保护
  • 兼容 OCI 分发标准和 artifacts 标准,开箱即可用
  • 支持不同的镜像存储后端,镜像数据不只可以存放在镜像仓库,还可以放到 NAS 或者类似 S3 的对象存储上
  • 与 Dragonfly 的良好集成

架构上, nydus 主要包含一个新的镜像格式,和一个负责解析容器镜像的 FUSE 用户态文件系统进程。

nydus-architecture| center | 768x356

nydus 能够解析FUSE, 或者virtiofs协议来支持传统的 runc 容器, 或者Kata容器。容器仓库,OSS 对象存储 ,NAS,以及 Dragonfly 的超级节点和 peer 节点都可以作为 nydus 的镜像数据源。同时, nydus 还可以配置一个本地缓存,从而避免每次启动都从远端数据源拉取数据。

镜像格式方面, nydus 把一个容器镜像分成元数据和数据两层。其中元数据层是一颗自校验的哈希树。 每个文件和目录都是哈希树中的一个附带哈希值的节点。一个文件节点的哈希值是由文件的数据确定,一个目录节点的哈希值则是由该目录下所有文件和目录的哈希值 确定。每个文件的数据被按照固定大小切片并保存到数据层中。数据切片可以在不同文件以及不同镜像中的不同文件共享。

nydus-format| center | 768x356

Nydus 能为用户带来什么?

用户如果部署了 nydus 镜像服务,最直观的一个感受就是,容器启动变快了,从以前的明显时间消耗,变成了几乎瞬间就能启动起来。在我们的测试中, nydus 能够把常见镜像的启动时间,从数分钟缩短到数秒钟。

nydus-performance| center | 768x356

另外一个不那么明显但也很重要的改进,是 nydus 能够为用户提供容器运行时数据一致性校验。在传统的镜像中,镜像数据会先被解压到本地文件系统,再由容器应 用去访问使用。解压前,镜像数据是完整校验的。但是解压之后,镜像数据不再能够被校验。这带来的一个问题就是,如果解压后的镜像数据被无意或者恶意地修改, 用户是无法感知的。而 nydus 镜像不会被解压到本地,同时可以对每一次数据访问进行校验,如果数据被篡改,则可以从远端数据源重新拉取。

nydus-integraty| center | 768x356

未来规划

前面我们介绍了 nydus 的架构和优点。在过去的一年里,我们和内部的产品团队一起致力于让 nydus 项目更稳定,安全和易用。在把 nydus 项目开源之后,我们 将会更关注广泛的云原生容器生态。我们的愿景是,当用户在集群中部署 Dragonfly 和 nydus 服务的时候,无论镜像大小,用户都能够方便快捷地运行他们的容器 应用,同时不需要为容器镜像的数据安全性担忧。

OCI 社区容器镜像标准

虽然我们已经在内部生产环境中大规模部署 nydus,我们坚信对 OCI 镜像标准的改进需要广泛的社区合作。为此,我们积极地参与了 OCI 社区关于下一代镜像标准 的讨论,并发现 nydus 能够广泛地符合 OCI 社区对下一代镜像格式的要求。所以我们提议把 nydus 作为 OCI 社区下一代镜像格式的示例实现,并期待和更多的 云原生行业领导者们一起推进下一代镜像标准的制定和实现。

FAQ

Q: 现有的 OCI 镜像标准有什么问题?

  • SUSE 的 Aleksa Sarai 写过一个 blog (The Road to OCIv2 Images: What's Wrong with Tar?) ,详细地描述了现有 OCI 镜像标准的一系列问题,简单总结就是 OCI 镜像标准使用的 tar 格式太古老并不适合作为容器镜像格式。

Q: nydus 和 CRFS 有什么区别?

  • CRFS 是 GO build team 设计的一个镜像格式。二者在主要设计思想上非常相似。细节上, nydus 支持块级别的数据去重和端到端的数据一致性校验,可以说 是在 CRFS 的 stargz 格式上的进一步改进。

Q: nydus 和 Azure 的 Teleport 有什么区别?

  • Azure Teleport 更像是现有 OCI 镜像标准在基于 SMB 文件共享协议的 snapshotter 上的一个部署实现,能够支持容器镜像数据按需下载,但保留了所有 目前 OCI 镜像 tar 格式的缺陷。相对的, nydus 抛弃了过时的 tar 格式,并使用 merkle tree 格式来提供更多的高级特性。

Q: 如果运行基于 nydus 的容器的时候网络断了怎么办?

  • 使用现有 OCI 镜像的时候,如果在容器镜像还没有完整下载的时候网络断了,容器会一开始就无法启动。 nydus 很大程度上改变了容器启动的流程,用户不需要 再等待镜像数据完整下载就能启动容器。而容器运行时如果网络断了,将无法访问没有下载到本地的镜像数据。 nydus 支持在容器启动后在后台下载容器镜像数据, 所以当容器镜像数据完整下载到本地后,基于 nydus 的容器也不会受到网络中断的影响。

附录:OCIv2 镜像标准

从 2020 年 6 月开始,OCI 社区花了一个多月时间密集讨论了当前 OCI 镜像标准的缺陷,以及 OCIv2 镜像格式需要满足哪些要求。OCIv2 在这里只是一个宣传 命名,实际上 OCIv2 是当前 OCI 镜像标准的改进,而不会是一个全新的镜像标准。

这次镜像格式大讨论从一个邮件和一份共享文档开始,并促成了多次在线的 OCI 社区讨论会议。最后的结论也很鼓舞人心,OCIv2 镜像格式需要满足下列要求:

  • 更少的重复数据
  • 可重建的镜像格式
  • 明确的更少的文件系统元数据
  • 可以 mount 的文件系统格式
  • 镜像内容列表
  • 镜像数据按需加载
  • 可扩展性
  • 可校验和/或可修复
  • 更少的上传数据
  • 可以工作在不可信存储上

在这份共享文档中可以找到每一个要求的详细描述。我们全程参与了整个 OCIv2 镜像格式要求的讨论 ,并发现 nydus 很好地满足了全部的这些要求。这进一步促使我们开源 nydus 项目来为社区讨论提供一个工作的代码基础。

· 7 分钟阅读
anjia0532

阿里Dragonfly体验之私有registry下载(基于0.3.0)

书接上篇012-P2P加速Docker镜像分发(阿里Dragonfly) ,讲解了如何快速搭建 Dragonfly,但是访问的是公开镜像,本文主要讲解如何下载私有镜像。

实验环境

主机

类型主机名系统ipdocker version
supernoded7y-1Ubuntu Server 16.04.6 LTS X64192.168.0.7517.06.2ubuntu
clinet1d7y-2Ubuntu Server 16.04.6 LTS X64192.168.0.7617.06.2ubuntu
clinet2d7y-3Ubuntu Server 16.04.6 LTS X64192.168.0.7717.06.2ubuntu

私有registry

本次以阿里云私有镜像库为例,可以自行开通。

文档之坑

官方文档比较简单,甚至带有误导性,下意识的以为应该在dfdaemon节点上配置auth信息,并且配的是真实的私有registry,如果真这么搞了,肯定被坑。 (但是也能解释通,比较绕,dfdaemon本身就是一个伪装成registry,用来加速私有registry,那么登陆信息就应该换成dfdaemon ip,只是示例不太恰当而 已,对初学者相当不友好倒是真的)

image.png

supernode步骤

安装supernode

root@d7y-1:~# docker run --name dragonfly-supernode --restart=always \
-d -p 8001:8001 -p 8002:8002 -v /data/dragonfly/supernode:/home/admin/supernode \
registry.cn-hangzhou.aliyuncs.com/dragonflyoss/supernode:0.3.0 \
-Dsupernode.advertiseIp=192.168.0.75

root@d7y-1:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be7fb931db0b registry.cn-hangzhou.aliyuncs.com/dragonflyoss/supernode:0.3.0 "/bin/sh -c '/root..." About a minute ago Up About a minute 0.0.0.0:8001-8002->8001-8002/tcp dragonfly-supernode

root@d7y-1:/data/dragonfly/supernode/logs# cat app.log
2019-03-30 01:04:40.065 INFO [ main] c.d.d.s.SuperNodeStarter - Starting SuperNodeStarter on be7fb931db0b with PID 9 (/supernode.jar started by root in /)
2019-03-30 01:04:40.069 INFO [ main] c.d.d.s.SuperNodeStarter - No active profile set, falling back to default profiles: default
2019-03-30 01:04:42.151 INFO [ main] c.d.d.s.c.SupernodeProperties - init local ip of supernode, use ip:192.168.0.75
2019-03-30 01:04:42.253 INFO [ main] c.d.d.s.c.SupernodeProperties - cluster members: [{"downloadPort":8001,"ip":"localhost","registerPort":8002}]
2019-03-30 01:04:42.263 INFO [ main] c.d.d.s.c.util.MonitorService - available processors count is 4
2019-03-30 01:04:42.272 ERROR [ Thread-2] c.d.d.s.c.util.MonitorService - process fields:null error
java.io.IOException: Cannot run program "tsar": error=2, No such file or directory
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
at java.lang.Runtime.exec(Runtime.java:620)
at java.lang.Runtime.exec(Runtime.java:450)
at java.lang.Runtime.exec(Runtime.java:347)
at com.dragonflyoss.dragonfly.supernode.common.util.MonitorService$1.run(MonitorService.java:56)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException: error=2, No such file or directory
at java.lang.UNIXProcess.forkAndExec(Native Method)
at java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
at java.lang.ProcessImpl.start(ProcessImpl.java:134)
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
... 5 common frames omitted
2019-03-30 01:04:43.507 INFO [ main] c.d.d.s.SuperNodeStarter - Started SuperNodeStarter in 3.906 seconds (JVM running for 4.59)
2019-03-30 01:04:49.472 INFO [ spring-1] c.d.d.s.s.p.PreheatServiceImpl - deleteExpiresPreheatTask, count:0

2019-03-30 01:04:42.151 INFO [ main] c.d.d.s.c.SupernodeProperties - init local ip of supernode, use ip:192.168.0.75 看,启动ip设置成功.

注意,官方的镜像没改时区,默认是UTC时间,比北京东八区早8小时。

登陆私有registry并推送镜像

root@d7y-1:~# docker login https://registry.cn-qingdao.aliyuncs.com
Username: //你阿里云账号
Password: //你阿里云密码
Login Succeeded
root@d7y-1:~# docker pull nginx:alpine
root@d7y-1:~# docker tag nginx:alpine registry.cn-qingdao.aliyuncs.com/d7y-test/nginx:alpine
root@d7y-1:~# docker push registry.cn-qingdao.aliyuncs.com/d7y-test/nginx:alpine
alpine: digest: sha256:857e6f195df0e9b497be0c7fad0f013126407aaeb71edcef66a24e8b990d94b3 size: 1153

dfdaemon 步骤

安装dfdaemon

在两台client节点分别执行如下命令

root@d7y-2:~# cat <<EOD >/etc/dragonfly.conf
[node]
address=192.168.0.75
EOD
root@d7y-2:~# docker run --name dragonfly-dfclient --restart=always \
-d -p 65001:65001 -v /root/.small-dragonfly:/root/.small-dragonfly \
-v /etc/dragonfly.conf:/etc/dragonfly.conf dragonflyoss/dfclient:v0.3.0 \
--registry=https://registry.cn-qingdao.aliyuncs.com --ratelimit 100M
Unable to find image 'dragonflyoss/dfclient:v0.3.0' locally
v0.3.0: Pulling from dragonflyoss/dfclient
169185f82c45: Pull complete
f58f64214283: Pull complete
bd8f062dc2d2: Pull complete
Digest: sha256:5bcabd5b34f4da0c2d489c8f99a23a401fb9ec57e54d4fa90457a93c5a85371f
Status: Downloaded newer image for dragonflyoss/dfclient:v0.3.0
b491e90489a584119b82ca934cf2ae087abc136f7f9de3542e14fb12bc1c7512

root@d7y-2:~# cat <<EOD >/etc/docker/daemon.json
{
"registry-mirrors": ["http://127.0.0.1:65001"]
}
EOD
root@d7y-2:~# systemctl restart docker

root@d7y-2:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b491e90489a5 dragonflyoss/dfclient:v0.3.0 "/dfclient/dfdaemo..." 28 seconds ago Up 4 seconds 0.0.0.0:65001->65001/tcp dragonfly-dfclient

root@d7y-2:~/.small-dragonfly/logs# cat dfdaemon.log
2019-03-30 01:18:21.331 INFO sign:1 : init...
2019-03-30 01:18:21.331 INFO sign:1 : rotate log routine start...
2019-03-30 01:18:21.338 INFO sign:1 : dfget version:
2019-03-30 01:18:21.338 ERRO sign:1 : init properties failed:open /etc/dragonfly/dfdaemon.yml: no such file or directory
2019-03-30 01:18:21.338 INFO sign:1 : init properties:{"Registries":[{"Schema":"https","Host":"registry.cn-qingdao.aliyuncs.com","Certs":null,"Regx":"(^localhost$)|(^127.0.0.1$)|(^127.0.0.1$)"}]}
2019-03-30 01:18:21.338 INFO sign:1 : init finish
2019-03-30 01:18:21.338 INFO sign:1 : start dfdaemon param: &{DfPath:/dfclient/dfget DFRepo:/root/.small-dragonfly/dfdaemon/data/ RateLimit:100M CallSystem:com_ops_dragonfly URLFilter:Signature&Expires&OSSAccessKeyId Notbs:true MaxProcs:4 Version:false Verbose:false HostIP:127.0.0.1 Port:65001 Registry:https://registry.cn-qingdao.aliyuncs.com DownRule: CertFile: KeyFile: TrustHosts:[] ConfigPath:/etc/dragonfly/dfdaemon.yml}
2019-03-30 01:18:21.338 INFO sign:1 : launch dfdaemon http server on 127.0.0.1:65001

登陆dfdaemon

root@d7y-2:~# docker login http://127.0.0.1:65001
Username: //你阿里云账号
Password: //你阿里云密码
Login Succeeded
root@d7y-2:~# cat ~/.docker/config.json
{
"auths": {
"127.0.0.1:65001": {
"auth": "zzxxxxxx="
}
}
}

pull 私有镜像

root@d7y-2:~# docker pull 127.0.0.1:65001/d7y-test/nginx:alpine
alpine: Pulling from d7y-test/nginx
8e402f1a9c57: Pull complete
56b0d9b69cc9: Pull complete
b66c8bb200cc: Pull complete
4ec77fc9c55f: Pull complete
Digest: sha256:857e6f195df0e9b497be0c7fad0f013126407aaeb71edcef66a24e8b990d94b3
Status: Downloaded newer image for 127.0.0.1:65001/d7y-test/nginx:alpine

可以通过iftop 等命令,观察流量。

其他

排错

如果有遇到其他问题,可以通过查看日志来获取更多信息。

dfdaemon log : /root/.small-dragonfly/logs/{dfclient.log,dfdaemon.log,dfserver.log}
supernode log: /home/admin/supernode/{app.log,data-gc.log,downloader.log,piece-hit.log,space-gc.log}

公开和私有registry混用

如果大量都是私有registry的话,可以在/etc/docker/daemon.json 中配置dfdaemon和加速器,如果是一半一半的话,那就干脆起两个dfdaemon就行了, 一个--registry写私有的,一个--registry写公有的,然后也是配置 /etc/docker/daemon.json

cat /etc/docker/daemon.json
{
"registry-mirrors": ["http://127.0.0.1:65001","https://xxx.mirror.aliyuncs.com"],
"dns": ["223.5.5.5"]
}

吐槽

再次吐槽一下d7y的产品很好,解决了很大问题。但是这文档,真心不是给新手看的。从未见过如此坑多且深的文档。没见过哪家quick start 写的这么复杂。

鸣谢

非常感谢钉钉群内的 d7y 的 contributor 太云-lowzj 耐心解答,从开始研究d7y开始, 遇到的很多坑都是在 太云-lowzj 帮助下蹚过去的。但是还是觉得,如果文档足够友好,肯定会减少群内被打扰的次数, 进而节省自己时间的。

image.png

招聘小广告

山东济南的小伙伴欢迎投简历啊 加入我们 , 一起搞事情。

长期招聘,Java程序员,大数据工程师,运维工程师,前端工程师。

· 7 分钟阅读
anjia0532

使用 Dragonfly 加速 Docker 镜像分发(基于 0.3.0)

介绍

如果说,微服务和容器是最佳拍档,那么模块多实例是肯定少不了。 假如没有使用类似 Google jib 等手段进行镜像分层(利用镜像缓存),势必会造成:

  • 带宽浪费:尤其是公网带宽,如果是自建 harbor,那么会容易导致单节点网卡被打满,如果用了 harbor 联邦,又会导致数据同步等运维问题。
  • 集群拉起慢:镜像下载慢,必然会导致服务拉起慢。

关于 Google jib 可以参见我另外一篇 加速和简化构建 Docker(基于 Google jib), 本文只介绍 Dragonfly + dfdaemon

Dragonfly 是阿里巴巴自研并开源的一款基于 P2P 协议的文件分发系统。除了使用 dfget 进行文件下载外,还支持 dfdaemon 进行 docker 镜像下载。

关于 Dragonfly 的镜像分发的原理性说明,可参见直击阿里双 11 神秘技术:PB 级大规模文件分发系统“蜻蜓” ,文中介绍很详细,此处不多说明。

实验环境

类型系统ipdocker version
supernodeUbuntu Server 16.04.6 LTS X64192.168.0.4417.06.2~ce-0~ubuntu
clinet1Ubuntu Server 16.04.6 LTS X64192.168.0.4017.06.2~ce-0~ubuntu
clinet2Ubuntu Server 16.04.6 LTS X64192.168.0.4517.06.2~ce-0~ubuntu
注意

如果是实验目的,建议用 Vmware,并且在关键操作时备份快照(比如,刚装完环境,升级完 PS 和.Net 后),这样能够及时,干净的还原现场, 节省每次重装系统导致的时间浪费安装吐槽一下 Dragonfly 的文档,简直让人不知所以。结合 issues + 钉钉群内请教,遂整理出最简使用文档。

supernode

可选:给 supernode 增加 docker 加速器,可以参考 https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors ,如果不需要,可以去掉。

$ cat << EOD >/etc/docker/daemon.json
{
"registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"]
}
EOD
$ systemctl restart docker
$ docker run --name dragonfly-supernode --restart=always -d -p 8001:8001 -p 8002:8002 \
-v /data/dragonfly/supernode:/home/admin/supernode registry.cn-hangzhou.aliyuncs.com/dragonflyoss/supernode:0.3.0 \
-Dsupernode.advertiseIp=172.60.20.44
说明
  • --restart=always 在容器退出时,自动重启容器,防止异常 kill 或者 oom 导致的异常退出
  • registry.cn-hangzhou.aliyuncs.com/dragonflyoss/supernode:0.3.0 dragonfly 的 supernode 目前没有 docker hub 镜像,只能用阿里云的 -v /data/dragonfly/supernode:/home/admin/supernode 将 supernode 的 data dir 挂载到宿主机上
  • -Dsupernode.advertiseIp=172.60.20.44 设置 clinet 可以访问的 supernode ip,这是一个大坑。如果不设置,有可能会导致 client 无法连接 supernode,届时,docker pull 会走 clinet 的网络,从真实的 registry 直接下载镜像

image.png

dfdaemon

$ cat <<EOD >/etc/dragonfly.conf
[node]
address=192.168.0.44
EOD
$ docker run --name dragonfly-dfclient --restart=always -d -p 65001:65001 \
-v /root/.small-dragonfly:/root/.small-dragonfly \
-v /etc/dragonfly.conf:/etc/dragonfly.conf dragonflyoss/dfclient:v0.3.0 \
--registry=https://xxx.mirror.aliyuncs.com --ratelimit 100M

$ cat <<EOD >/etc/docker/daemon.json
{
"registry-mirrors": ["http://127.0.0.1:65001"]
}
EOD

$ systemctl restart docker
解释
  • 在 /etc/dragonfly.conf 中配置 client 可以访问的 supernode 的 ip 地址,但是,目前官方没有做 HA,supernode 没法组集群,撑死算是联邦, 不能共享文件信息,而且最坑的是,快速开始里,中英文均未提供需要配置此文件, 等有所提及(我都是被坑完后,用关键词在 d7y 的 org 里搜索,类似知道答案后,找出处 手动[捂脸]
  • -v /root/.small-dragonfly:/root/.small-dragonfly ,将容器中的关键目录挂载到宿主机上,防止重启或者镜像升级时,数据丢失
  • --registry=https://xxx.mirror.aliyuncs.com 从何处下载镜像,可以写 harbor 地址,也可以写加速器地址。默认是 https://index.docker.io ,但是,因为国内网络原因,会导致大概率性失败。很灵异。 而官方文档是写的 --registry https://xxx.xx.x 不能算是坑,但是,对于 docker 不熟悉的,往往会不知能不能用加速器。
  • --ratelimit 100M 是限速,默认是 20M ,这肯定不算坑哈,这是正常特性,但是,文档是有误的 -ratelimit 而实际是 --ratelimit ,如果不改此参数,会发现,下载很慢。
  • 修改/etc/docker/daemon.json 是为了让 docker engine 走 dfdaemon
  • systemctl restart docker 是为了让 daemon 生效

测试

大文件测试

docker pull anjia0532/kubeflow-images-public.tensorflow-1.6.0-notebook-gpu:v20180604-b3733835

可以通过 iftop 等软件,查看带宽使用情况判断是否生效,也可以通过查看日志来判断。 image.png

但是会经常性的出现 error pulling image configuration: received unexpected HTTP status: 502 Bad Gateway

最后

需要结合实际情况,配置相关参数,比如,文件失效时间,用来平衡文件有效期及磁盘使用量。

参考资料

招聘小广告

山东济南的小伙伴欢迎投简历啊 加入我们 , 一起搞事情。

长期招聘,Java 程序员,大数据工程师,运维工程师,前端工程师。

· 2 分钟阅读

611.jpg | center | 827x347

睿云智合基于 Dragonfly 支持docker proxy https

1、部署https harbor

https://github.com/goharbor/harbor/blob/master/docs/configure_https.md

2、部署docker_proxy

pull images

pull_images.sh

#!/bin/sh
docker_registry_proxy="dockerhubwp/docker_proxy_nginx:latest"
supernode="registry.cn-hangzhou.aliyuncs.com/alidragonfly/supernode:0.2.0"
dfclient="dockerhubwp/dfclient:latest"

images="${docker_registry_proxy} ${supernode} ${dfclient}"

function pullImage(){
for image in ${images}; do
echo -e "pull image ======>${image}"
docker pull ${image}
done
}

pullImage

docker_proxy.sh

#! /bin/sh

# Separate deployment docker_proxy
# dfdaemon and docker registry map
# example x.x.x
registry="harbor域名"
containername=docker_registry_proxy

# 你需要配置的dns 服务器 (如:dnsmasq)
DNS_SERVER="dns-server"
docker_registry_proxy="dockerhubwp/docker_proxy_nginx:latest"

# get localhost ip
ipaddr=$(ip addr | awk '/^[0-9]+: / {}; /inet.*global/ {print gensub(/(.*)\/(.*)/, "\\1", "g", $2)}')
localhostIp=$(echo ${ipaddr} | cut -d " " -f 1)

function changeDockerProxy() {
mkdir -p /etc/systemd/system/docker.service.d
cat <<EOD >/etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:3128/"
Environment="HTTPS_PROXY=http://127.0.0.1:3128/"
EOD
}

function dockerDockerProxyRun() {
if [[ 0 != $(docker ps -a | grep ${containername} | wc -l) ]]; then
docker rm -f ${containername}
fi
docker run --restart=always --privileged=true --name ${containername} -d -p 0.0.0.0:3128:3128 \
-v /etc/docker_proxy_nginx/docker_mirror_certs:/ca -v /var/log/docker_proxy_nginx:/var/log/nginx/ \
-e DRAGONFLY_REGISTRIES="${registry},http://${localhostIp}:65001" -e REGISTRIES="${registry}" \
-e DNS_SERVER=${DNS_SERVER} ${docker_registry_proxy}
}

changeDockerProxy

systemctl daemon-reload
systemctl restart docker

dockerDockerProxyRun

3、部署dragonfly

部署Supernode

supernode.sh
#!/bin/sh

# Separate deployment supernode

supernode="registry.cn-hangzhou.aliyuncs.com/alidragonfly/supernode:0.2.0"
containername=supernode

function superNode() {
if [[ 0 != $(docker ps -a | grep ${containername} | wc -l) ]]; then
docker rm -f ${containername}
fi
docker run --name ${containername} --restart=always -d -p 8001:8001 -p 8002:8002 ${supernode}
}
superNode

部署dfclient

dfclient.sh
#!/bin/sh

# Separate deployment docker_proxy

dfclient="dockerhubwp/dfclient:latest"

#harbor 地址
dfdaemon_registry="https://x.x.x"
containername=dfclient

# supernode ips example (10.0.0.160,10.0.0.162)
supernodes="supernodeip"


ipaddr=$(ip addr | awk '/^[0-9]+: / {}; /inet.*global/ {print gensub(/(.*)\/(.*)/, "\\1", "g", $2)}')
localhostIp=$(echo ${ipaddr} | cut -d " " -f 1)

cat <<EOD >/etc/dragonfly.conf
[node]
address=${supernodes}
EOD

function startDfClient() {
if [[ 0 != $(docker ps -a | grep ${containername} | wc -l) ]]; then
docker rm -f ${containername}
fi
docker run --name ${containername} --restart=always -d -p 65001:65001 \
-v /root/.small-dragonfly:/root/.small-dragonfly -v /etc/dragonfly.conf:/etc/dragonfly.conf \
-e dfdaemon_registry=${dfdaemon_registry} -e localhostIp=${localhostIp} ${dfclient}
}

startDfClient

最后

trust.sh
#!/bin/sh

# trust ca
curl http://127.0.0.1:3128/ca.crt >/etc/pki/ca-trust/source/anchors/docker_proxy_nginx.crt

update-ca-trust

测试: 拉取镜像

docker pull x.x.x/library/nginx:latest

原文链接:https://mp.weixin.qq.com/s/95mX8cDox5bmgQ2xGHLPqQ

参考:

· 6 分钟阅读

在Kubernetes上部署Dragonfly

1 说明

  • supernode 组件至少需要两节点部署,hostnetwork,一般部署在管理节点,比如k8s master上。
  • supernode 需要使用8001、8002端口。如果跟kube-apiserver部署在一个节点上,需要确保不要有端口冲突。
  • supernode 所在节点对网络带宽、磁盘空间、磁盘IO要求较高。
  • dfdaemon 组件使用daemonset部署在所有的node节点上,hostnetwork,占用65001端口
  • 需要修改所有node的docker启动参数 --registry-mirror http://127.0.0.1:65001

2 部署

其中 supernode 部署在管理节点,dfgetdfdaemon 部署在所有的node上面。

2.1 supernode

三种部署方式:

  • 直接跑docker 容器
  • kubelet管理静态pod
  • deployment部署

NOTE: 官方提供的镜像是0.2.0,继委自己打了个0.3.0的镜像,这个镜像是基于master分支的。问了一下已经部署上线蜻蜓的去哪儿网和美团的工程师, 他们用的是官方的0.2.0镜像。

2.1.1 docker容器
docker run -d -p 8001:8001 -p 8002:8002 -v /data/log/supernode/:/home/admin/supernode/logs/ \
-v /data/supernode/repo/:/home/admin/supernode/repo/ hub.c.163.com/hzlilanqing/supernode:0.3.0
2.1.2 静态pod
apiVersion: v1
kind: Pod
metadata:
name: supernode
namespace: kube-system
spec:
containers:
- image: hub.c.163.com/hzlilanqing/supernode:0.3.0
name: supernode
ports:
- containerPort: 8080
name: tomcat
- containerPort: 8001
name: register
- containerPort: 8002
name: download
resources:
requests:
cpu: "2"
memory: 4Gi
volumeMounts:
- mountPath: /etc/localtime
name: ltime
- mountPath: /home/admin/supernode/logs/
name: log
- mountPath: /home/admin/supernode/repo/
name: data
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
restartPolicy: Always
tolerations:
- effect: NoExecute
operator: Exists
- effect: NoSchedule
operator: Exists
nodeSelector:
node-role.kubernetes.io/master: ""
volumes:
- hostPath:
path: /etc/localtime
name: ltime
- hostPath:
path: /data/log/supernode
name: log
- hostPath:
path: /data/supernode/repo/
name: data
2.1.3 deployment
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: supernode
name: supernode
namespace: kube-system
spec:
replicas: 2
selector:
matchLabels:
app: supernode
template:
metadata:
labels:
app: supernode
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
spec:
containers:
- image: hub.c.163.com/hzlilanqing/supernode:0.3.0
name: supernode
ports:
- containerPort: 8080
hostPort: 8080
name: tomcat
protocol: TCP
- containerPort: 8001
hostPort: 8001
name: register
protocol: TCP
- containerPort: 8002
hostPort: 8002
name: download
protocol: TCP
resources:
requests:
cpu: "2"
memory: 4Gi
volumeMounts:
- mountPath: /etc/localtime
name: ltime
- mountPath: /home/admin/supernode/logs/
name: log
- mountPath: /home/admin/supernode/repo/
name: data
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
restartPolicy: Always
tolerations:
- effect: NoExecute
operator: Exists
- effect: NoSchedule
operator: Exists
nodeSelector:
node-role.kubernetes.io/master: ""
volumes:
- hostPath:
path: /etc/localtime
type: ""
name: ltime
- hostPath:
path: /data/log/supernode
type: ""
name: log
- hostPath:
path: /data/supernode/repo/
type: ""
name: data

---

kind: Service
apiVersion: v1
metadata:
name: supernode
namespace: kube-system
spec:
selector:
app: supernode
ports:
- name: register
protocol: TCP
port: 8001
targetPort: 8001
- name: download
protocol: TCP
port: 8002
targetPort: 8002

2.2 dfget & dfdaemon
  • Dockerfile
FROM debian:7

MAINTAINER "hzlilanqing@corp.netease.com"

RUN apt-get update && apt-get install -y python wget && \
wget http://dragonfly-os.oss-cn-beijing.aliyuncs.com/df-client_0.2.0_linux_amd64.tar.gz && \
tar -zxf df-client_0.2.0_linux_amd64.tar.gz && \
cp -R df-client/- /usr/local/bin/ && \
rm -rf df-client_0.2.0_linux_amd64.tar.gz df-client

COPY ./dragonfly.conf /etc/

EXPOSE 65001

CMD ["/bin/sh","-c","/usr/local/bin/dfdaemon --registry https://hub.c.163.com"]
  • dragonfly.conf
    [node]
address=<supernode_address>
  • 构建镜像
docker build -t dfdaemon:0.2.0 .

使用daemonset部署,需要使用hostnetwork,占用端口65001,并且需要准备一个configmap将dragonflg.conf挂载到容器的/etc/目录下面:

## 这个配置需要是`supernode` 的域名
## cat dragonfly.conf
[node]
address=<supernode_address>

## kubectl -n kube-system create configmap dragonfly-conf --from-file=dragonfly.conf
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: dfdaemon
namespace: kube-system
spec:
selector:
matchLabels:
app: dfdaemon
template:
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
labels:
app: dfdaemon
spec:
containers:
- image: hub.c.163.com/hzlilanqing/dfdaemon:0.2.0
name: dfdaemon
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 250m
volumeMounts:
- mountPath: /etc/dragonfly.conf
subPath: dragonfly.conf
name: dragonconf
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
restartPolicy: Always
tolerations:
- effect: NoExecute
operator: Exists
- effect: NoSchedule
operator: Exists
volumes:
- name: dragonconf
configMap:
name: dragonfly-conf
items:
- key: dragonfly.conf
path: dragonfly.conf
2.3 docker启动参数设置

设置启动参数 registry-mirror,其中65001dfdaemon的服务端口:

  • 方法1: 修改/etc/systemd/system/multi-user.target.wants/docker.service

    ExecStart=/usr/bin/dockerd -H fd:// --registry-mirror http://127.0.0.1:65001
  • 方法2: 修改/etc/docker/daemon.json

    {
    "registry-mirrors": ["http://127.0.0.1:65001"]
    }

附录

完整的部署yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: supernode
name: supernode
namespace: kube-system
spec:
replicas: 2
selector:
matchLabels:
app: supernode
template:
metadata:
labels:
app: supernode
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
spec:
containers:
- image: hub.c.163.com/hzlilanqing/supernode:0.3.0
name: supernode
ports:
- containerPort: 8080
hostPort: 8080
name: tomcat
protocol: TCP
- containerPort: 8001
hostPort: 8001
name: register
protocol: TCP
- containerPort: 8002
hostPort: 8002
name: download
protocol: TCP
resources:
requests:
cpu: "2"
memory: 4Gi
volumeMounts:
- mountPath: /etc/localtime
name: ltime
- mountPath: /home/admin/supernode/logs/
name: log
- mountPath: /home/admin/supernode/repo/
name: data
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
restartPolicy: Always
tolerations:
- effect: NoExecute
operator: Exists
- effect: NoSchedule
operator: Exists
nodeSelector:
node-role.kubernetes.io/master: ""
volumes:
- hostPath:
path: /etc/localtime
type: ""
name: ltime
- hostPath:
path: /data/log/supernode
type: ""
name: log
- hostPath:
path: /data/supernode/repo/
type: ""
name: data

---

kind: Service
apiVersion: v1
metadata:
name: supernode
namespace: kube-system
spec:
selector:
app: supernode
ports:
- name: register
protocol: TCP
port: 8001
targetPort: 8001
- name: download
protocol: TCP
port: 8002
targetPort: 8002

---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: dfdaemon
namespace: kube-system
spec:
selector:
matchLabels:
app: dfdaemon
template:
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
labels:
app: dfdaemon
spec:
containers:
- image: hub.c.163.com/hzlilanqing/dfdaemon:0.2.0
name: dfdaemon
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 250m
volumeMounts:
- mountPath: /etc/dragonfly.conf
subPath: dragonfly.conf
name: dragonconf
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
restartPolicy: Always
tolerations:
- effect: NoExecute
operator: Exists
- effect: NoSchedule
operator: Exists
volumes:
- name: dragonconf
configMap:
name: dragonfly-conf
items:
- key: dragonfly.conf
path: dragonfly.conf

---
apiVersion: v1
data:
dragonfly.conf: |
[node]
address=supernode
kind: ConfigMap
metadata:
name: dragonfly-conf
namespace: kube-system

参考文档

  1. https://d7y.io/

· 20 分钟阅读

浙江移动容器云基于 Dragonfly 的统一文件分发平台生产实践

611.jpg | center | 827x347

2018 年 11 月, 源于阿里巴巴的云原生镜像分发系统—— Dragonfly 在 KubeCon 上海现场亮相,成为 CNCF 沙箱级别项目(Sandbox Level Project)。

  • Dragonfly 主要解决以 Kubernetes 为核心的分布式应用编排系统的镜像分发难题。2017 年开源即成为阿里巴巴最为核心的基础设施技术之一。 开源一年以来,Dragonfly 已在诸多行业落地。
  • DCOS 是浙江移动容器云平台,目前在平台式运行的应用系统已有 185 套,包括手机营业厅、CRM 应用等核心系统。本文将主要介绍浙江移动容器云(DCOS) 平台以 Dragonfly 为改革“利器”,成功解决了运营商大规模集群场景下分发效率低、成功率低以及网络带宽控制难等问题;并反哺社区,在 Dragonfly 界面功能、生产高可用部署层面对 Dragonfly 进行了升级。

DCOS 容器云在生产环境中遇到的挑战

  • 随着浙江移动容器云(DCOS)平台的持续完善,承载应用不断增加,在运行容器数量接近 10000 个。采用传统 C/S(Client-Server)结构的分发服务体系 已经越来越无法适应大规模分布式应用在代码包发布、文件传输时的场景:
  • 计算结点因网络异常等原因,导致代码包下载失败,对应用代码包的完整性和一致性构成影响。
  • 在多用户高并发情况下,可能会出现 TB 级的文件传输,单点性能瓶颈增加了应用发布时长。

Dragonfly 简介

P2P(Peer-To-Peer)是一种点对点网络技术,通过各结点互联,将网络中的资源和服务分散在各个结点上。信息的传输和服务实现直接在结点之间进行, 避免了传统 C/S 结构可能的单点瓶颈。

612.jpg | center | 612x264

CNCF 开源文件分发服务解决方案 Dragonfly,是一种基于 P2P 与 CDN 技术,构建了适用于容器镜像和文件的分发系统,有效解决了企业大规模集群场景下文件 和镜像分发效率、成功率低以及网络带宽控制的问题。

Dragonfly 的核心组件:

  • SuperNode:超级结点,以被动 CDN 方式从文件源下载文件并生产种子数据块,在 P2P 网络中,充当网络控制器,调度结点之间的分块数据传输;
  • dfget proxy:部署在计算结点的代理,负责 P2P 结点的数据块下载和结点间的数据共享。

Dragonfly 分发工作原理(以镜像分发为例): 容器镜像不同于普通文件,由多层存储构成,下载也是分层下载,非单一文件。每层的镜像文件都会被分割为 block 数据块并作为种子。下载结束后,通过每层镜像 唯一的 ID 和 sha256 算法,重新组合成完整的镜像。确保下载过程的一致性。

613.jpg | center | 643x229

Dragonfly 镜像下载模式的过程如下图所示:

614.jpg | center | 622x379

  1. dfget-proxy 拦截客户端 docker 发起的镜像下载请求(docker pull)并转换为向 SuperNode 的dfget 下载请求;
  2. SuperNode 从镜像源仓库下载镜像并将镜像分割成多个 block 种子数据块;
  3. dfget 下载数据块并对外共享已下载的数据块,SuperNode 记录数据块下载情况,并指引后续下载请求在结点之间以 P2P 方式进行数据块下载;
  4. Dokcer daemon 的镜像 pull 机制将最终将镜像文件组成完整的镜像。

根据 Dragonfly 的上述特性,浙江移动容器云平台结合生产实际决定引入 Dragonfly 技术对现行代码包发布模式进行改造,通过 P2P 网络分摊发布时产生的 单一文件服务器传输带宽瓶颈,并利用 Docker 本身的镜像 pull 机制来保证整个发布过程镜像文件的一致性。

解决方案:统一分发平台

架构设计与实现

功能架构设计

在 Dragonfly 技术的基础上,结合浙江移动容器云生产实践,统一分发平台的总体设计目标如下:

  • 利用 Dragonfly 技术和文件下载校验功能,解决目前生产发布过程中应用代码包发布不一致、发布时间过长的问题;
  • 支持客户端界面化,屏蔽后台命令行细节,简化操作流程,效率更高;
  • 支持 Mesos、K8s、Host、VM 等多种云环境下的分发,并实现集群的自主发现,支持用户通过统一分发平台对目标集群进行统一化管理;
  • 增加用户权限控制和任务带宽限制,支持多租户多任务的分发;
  • 优化 P2P Agent 部署方式,支持更快速的计算结点 P2P 组网。

基于上述目标,统一分发平台的总体架构设计如下:

615.jpg | center | 677x418

  • P2P 网络层是支持不同异构集群接入(主机集群、K8s 集群、Mesos 集群)的由多个计算结点构成的分发网络;
  • 分发服务层由功能模块和存储模块构成,是整个通用分发系统的核心架构。其中,用户接入鉴权模块提供系统登录审核功能;分发控制模块基于 Dragonfly, 实现 P2P 方式的任务分发;流量控制模块提供租户对不同任务的带宽设置功能;配置信息数据库负责记录网络层目标集群、任务状态等基本信息; 用户通过状态查询模块可实现对分发任务执行进度的透明掌控;
  • 用户操作层由任意数量的界面化用户客户端构成。

技术架构实现

根据上述平台设计目标与总体架构分析,容器云团队在开源组件的基础上进行了平台功能的二次开发,具体包括:

  • 开发界面化用户客户端 Client;
  • 引入 Harbor 开源镜像仓库进行镜像存储,Minio 对象存储服务进行文件存储;
  • 使用 MySQL、Redis 作为 CMDB,由 MySQL 负责管理集群状态、用户信息等,为面向集群的“一键式”任务创建提供支撑。通过 Redis 保存分发任务状态信 息,提供高并发、低延迟的状态查询服务;
  • 平台核心服务层(Docktrans)和 API 服务网关层(Edgetrans)都是是无状态、面向集群的、可动态横向扩展的核心组:
    • API 网关封装了系统内部架构,主要负责接收并转发 Client 发起的任务请求以及实现用户对各功能模块的接入鉴权,并对外可提供定制化的 API 调用 服务;
    • 核心服务层是平台各功能模块业务逻辑处理的引擎。在分发过程中,核心服务层将通过统一的远程调用向 P2P 代理结点同时发起下载请求,完成 客户端——任务集群“一对多”的分发过程。
  • df-master 与 df-client 均为 Dragonfly 组件,df-master 即 Dragonfly 中的超级节点SuperNode,df-client 即 P2P 网络中的对等节 点代理 dfget proxy。

616.jpg | center | 629x527

技术特色

  • df-client 实现容器镜像化。通过轻量级的容器部署,加快了组网效率。新接入网络层的集群 host 结点可通过镜像下载、启动的方式,秒级完成 P2P Agent 结点启动;
  • 核心接口层(Docktrans)屏蔽了dfget 底层命令行细节,提供了界面化的功能,简化了用户操作。通过统一远程调用方式下发至多个 P2P 任务结点, 解决了用户需要逐台进行 dfget 等下载操作的问题,简化了“一对多”的任务发起模式。

核心功能模块|分发控制接口交互流程

如下图所示,统一分发平台的核心模块在进行任务分发时的工作流程具体如下:

  1. 用户通过 Client 建立镜像或文件分发任务;
  2. 分发模块首先通过平台 API 服务网关(Edgetrans)的鉴权功能判断用户是否具有分发功能的权限;
  3. 用户通过鉴权后,设置分发任务参数,提供集群ID,平台从 MySQL 数据库读取集群配置信息实现集群结点的自主发现。用户也可以指定多个结点 IP 作为自定义 集群参数;
  4. 根据分发类型,核心服务层(Docktrans)分发功能模块将不同的前端分发请求转换为 dfget(文件)或者 Docker pull(镜像)命令,通过统一远程调用 Docker Service 服务将命令下发至多个结点 df-client 进行相应的处理;
  5. 在任务进行过程中,任务进度与任务事件日志分别写入 Redis 与 MySQL 数据库提供用户对任务状态的查询能力。

617.jpg | center | 650x750

生产环境改造成果

截至目前,生产共运行 200 余个业务系统 1700 多个应用模块,已全部优化为镜像发布模式。发布耗时和发布成功率得到明显改善:

采用 P2P 镜像发布后,业务多应用一次上线的月均发布成功率稳定在 98%。

618.png | center | 750x452

4 月后容器云平台开始用 P2P 镜像发布方式代替传统分发系统的代码包发布方式,多应用一次集中上线发布耗时相较与改造之前大幅降低,平均下降 67%。

619.jpg | center | 806x437

同时,容器云平台选取了多个应用集群进行单应用的 P2P 镜像发布改造效果测试。可以看出,单个应用发布耗时相较于改造前大幅降低,平均下降 81.5%。

620.jpg | center | 756x418

后续推广

统一文件分发平台已有效解决了浙江移动容器云应用在代码发布过程中的效率和一致性问题,成为平台的重要组成部分之一。同时,也支持更多大规模集群中进行高效 文件分发的场景。可陆续推广至:集群批量安装介质分发以及集群批量配置文件更新。

社区共建|界面功能展示

直接引入 Dragonfly 后诞生的社区诉求

  • 缺少图形化界面,用户使用成本高,操作效率低;
  • 缺少用户权限管理和分发审计功能,无分发管控能力;
  • 不支持用户“一对多”的集群操作模式。云环境下,用户通常需要向自己所管理的集群同时进行分发,但现有模式仅支持用户在单结点进行分发操作;
  • 传统 Agent 应用软件包部署方式效率低,不利于大规模集群的快速伸缩扩展。作为系统软件,增加了对宿主系统的入侵性。

目前,客户端界面化开发工作基本完成,已进入生产测试和部署中。分发平台总体规划 4 大核心功能:任务管理,目标管理,权限管理和系统分析,现已开放前三项功 能。

权限管理界面

权限管理,即用户管理,为不同用户提供个性化的权限管理功能,具体如下:

  • 支持不同角色(超级管理员、任务集群管理员、任务管理员)用户创建、删除、修改;
  • 支持不同权限集合的定制化组合(角色创建),用户权限赋权;
  • 支持外部系统用户接入与权限授权(暂未开放)。

621.jpg | center | 775x356

622.jpg | center | 777x291

目标管理界面

目标管理,即用户进行任务分发的目标集群结点管理,为用户提供管理集群的 P2P 组网和集群结点状态信息健康功能,具体如下:

  • 支持不同用户集群的创建和删除;
  • 支持在用户所管理集群下,容器自动化 Agent 部署快速新增、删除 P2P 网络结点,并对结点状态进行监控;
  • 支持不同类型,如 host(虚拟机、物理机)集群、K8s 集群、Mesos 集群的接入,同时,支持直接读取 K8s、Mesos 集群结点信息,批量接入 P2P 网络层。

623.jpg | center | 768x352

624.jpg | center | 770x375

任务管理

任务管理提供文件或镜像分发任务的创建、删除、停止、信息查看等功能,具体如下:

  • 支持镜像预热模式(可设置计划分发任务,提前发布镜像或文件分发至各结点);
  • 支持容器镜像等多格式文件的分发;
  • 支持指定任务集群多结点“一键式”任务创建、执行、删除、终止和已执行任务的“一键复制”;
  • 支持对发布文件版本的创建和删除管理;
  • 支持对分发任务状态与任务日志的查看。

625.jpg | center | 768x356

626.jpg | center | 777x358

系统分析(计划开放)

预计后续将开放系统分析功能,为平台管理员、用户提供任务分发耗时、成功率等数据和任务执行效率统计图表,通过数据统计与预测,有效支撑平台向智能化方向 演进。

社区共建|生产高可用部署

镜像库主备容灾部署,主备之间通过镜像同步保持数据一致性。

  • P2P 发布由 df-master 和 df-client 构成(蓝色部分),df-master 从镜像库拉取镜像形成 P2P 种子,每个机房配置两个 df-master 形成高可用;
  • P2P 分发只在本机房分发,避免跨机房流量;
  • 每个机房配置两个 mirror(备用镜像库),当 P2P 分发方式异常无法工作时计算结点会自动到 mirror 上下载镜像, mirror 通过负载均衡实现高可用。

626.jpg | center | 777x358

目前,我们计划把界面功能展示贡献给 CNCF Dragonfly 社区,进一步丰富 CNCF Dragonfly 社区周边生态。未来,我们希望更多人参与进来,一起为社区繁 荣贡献力量。

本文作者:

陈远峥 :浙江移动云计算架构师

王淼鑫 :浙江移动云计算架构师

Dragonfly 社区分享

Dragonfly 社区贡献者太云在 Dragonfly Meetup 分享到:

目前,Dragonfly 已经成为 CNCF Sandbox 项目,Star 数 2800+,有很多企业用户正在使用 Dragonfly 来解决他们在镜像或者文件分发方面遇到的各种 问题。未来,我们将不断完善和改进 Dragonfly,为云原生应用提供更加丰富强大且简便的分发工具。 期待与大家共同努力, 让 Dragonfly 早日成为 CNCF 毕业项目。”

项目地址

https://github.com/dragonflyoss/Dragonfly

Dragonfly Roadmap

628.jpg | center | 827x951