多主机共享Docker镜像

512 阅读3分钟

场景:在 K8S 框架下,通常管理着多台节点,这几台节点可能运行着多个相同的容器,比如多份 replica、deamon容器等,这些容器会使用相同的镜像文件。但是,随着机器数量的增多,这些镜像占用的空间也会随之线性增加,能不能将这些镜像的储存位置共享出来让所有的节点使用呢?

性能比较:共享目录使用 NFS 管理,因此每次读取的时候走网络而非本地磁盘。此假设的前提应该是网络读取速度快于磁盘读取(比如机房环境 + 比较差的硬盘),这样的话可以保证储存空间的节省。

结论:暂不可行,docker 官方不支持,需要侵入式修改 docker 代码

/var/lib/docker 共享实验

环境配置:启用两台 Host A、B,分别安装 docker,使用NFS 共享Host A中的目录,并挂载到 Host B 中。

期望效果:在 Host A 中使用 docker pull XXX,Host B 中 调用 docker images 可以显示拉取的镜像

实验一:共享 /docker/var/lib,这个目录下存放着 docker 的所有信息

结果:Host B 的 docker 无法启动,这个目录的范围太大,影响范围太广,直接导致没法启动

实验二:共享 /docker/var/lib/images,这个目录应该存放了 docker 的镜像相关的信息

结果:同上

实验三:共享 /docker/var/lib/images 和 /docker/var/lib/overlays

结果:Host B 中的docker daemon 可以正常启动,Host A 中 docker pull XXX 之后,虽然 Host B 不能立即显示 image 的更新,但是 reload docker 之后可以显示,表示实验是基本完成的。

但是,共享的 docker 镜像无法 run 起来,具体什么原因未知。

开源社区讨论

对于这个问题其实在 docker 的 issue 中已经存在讨论:主要有两个帖子

github.com/moby/moby/i…

github.com/moby/moby/i…

核心思想总结:

开发者正方:支持做这个东西,有如下几个优点/场景

  1. 下载镜像只需要下一次,不用所有机器都下载,所以更快
  2. 省储存空间,每个镜像只有 1 份而不是 N 份(N个机器每个机器一份)
  3. 我们有场景:一个提问者说她的 cluster 有 630 个节点,我们需要一种方式来防止磁盘空间的浪费

docker 维护者反方:不支持这个东西,原因如下:

  1. /var/lib/docker 本身就是给一个 docker daemon 用的,给多个 daemon 同时用就是 ask for trouble,Docker Engine is very much single-host focused (today).
  2. 并行问题,比如 Host A 中的容器正在用 image,Host B 给这个 image 删掉了,Host A 中的容器就直接 crash 了

一些可行的解决方法:

  1. docker 开发者说可以作一个外部的图驱动器(external graph drivers),应该指的是储存镜像的储存系统

    1. 他们自己的尝试,没细看:github.com/moby/moby/p…
    2. 一些外部的开源尝试:github.com/hustcat/doc…
  2. 没太看懂的提问:

    Now that Linux 4.5 allows overlayfs to work cleanly with nfs mounts (www.spinics.net/lists/linux… ), there could be some way that Docker running on modern Linux (with overlay2) could leverage this for a shared image store. What options should I set on the dockerd  runtime to make this work?

  3. SoCC(CCF B) 的一种实现方式:dl.acm.org/doi/abs/10.…

    这篇文章使用一个额外的机制来共享镜像文件,读写层放在本地保证效率,只读层使用共享文件系统减少储存空间的损耗,并且构造了一份元数据仓库用于解决并行问题。

    在实现上,使用侵入式修改 docker 代码的方式来实现了这一功能。因此,如果想达到我们上述的效果,非侵入式的使用 docker 是不可行的,不符合 docker 原本的设计理念,会在各种层面报错。

结论:要想实现这个方式需要侵入式的修改 docker 源码,需要重新编译 docker,在实际情况中是否值得需要加以考虑。