使用 Docker-in-Docker 来运行 CI 或集成测试环境?三思!

英文网址:https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/

中文网址:https://www.jianshu.com/p/2e708cb9af3b

Docker-in-Docker 的主要目的是帮助 Docker 本身的发展。很多人用它来运行 CI 系统(例如 Jenkins ),这初看起来还不错,但会遇到很多“有趣”的问题,这些问题可以通过将 Docker socket 绑定安装进 Jenkins 容器中解决。

让我们来看看这是什么意思。如果你只是想要快速解决方案而不是细节,可以跳到文章末尾。

Docker-in-Docker:优点

两年多前,我为-privileged flag in Docker贡献了代码并写了first version of dind。目的是为了帮助核心团队更快速地进行 Docker 开发。在 Docker-in-Docker 之前,典型的开发流程是:

hackity hack

构建

停止正在运行的 Docker 进程

运行新的 Docker 进程

测试

重复

如果你想要一个良好可复用的构建(例如在一个容器中),稍微有一点复杂:

ackity hack

确认有一个可工作版本的 Docker 在运行

使用旧 Docker 构建新 Docker

停止 Docker 进程

启动新的 Docker 进程

测试

停止新的 Docker 进程

重复

随着 Docker-in-Docker 的到来,这个过程简化为:

hackity hack

一步构建和运行

重复

好多了,不是么?

Docker-in-Docker:缺点

然而,出乎人们的预料,Docker-in-Docker 不是百分之百完美和健壮。我的意思是,有几个问题需要注意。

一是关于 LSM(Linux Security Modules)比如 AppArmor 和 SELinux:当启动一个容器,那个“内部 Docker ”可能尝试应用安全配置文件并导致“外部Docker”产生混乱或者冲突。当你尝试去合并带有-privileged参数的原始实现的时候这可能是最难解决的问题。我的修改在我的 Debinan 机器和 Ubuntu 测试虚拟机上可以运行(并且所有的测试都通过了),但在 Michael Crosby 的机器上(我没记错的话是 Fedora )却功败垂成。我不记得问题的具体原因,但是这可能是由于 Mike 是个聪明的家伙,在SELINUX=enforce(我在使用 AppArmor )的情形下运行而我的修改没有将 SELinux 考虑在内。

Docker-in-Docker:丑陋

第二个问题和存储驱动有关,当你在 Docker 中运行 Docker 时,外部的 Docker 运行在正常的文件系统( EXT4、BTRFS 等等你有的)之上而内部 Docker 运行在写时复制的系统(AUFS、BTRFS、Device Mapper,依赖于外层 Docker 所使用的文件系统)之上。有许多组合不能工作。例如,你不能在 AUFS 之上运行 AUFS。如果你在 BTRFS 之上运行 BTRFS,开始时会正常运行,一旦你嵌入 subvolumes,移除父级 subvolumes 会失败。Device Mapper 没有命名空间,所以如果多个 Docker 实例在一台机器上使用它,它们会看见(和影响)其它的镜像和容器备份装置。这样不好。

问题有很多解决方法;例如,如果你想在内部 Docker 中使用 AUFS,将/var/lib/docker升级为 volume 就会解决问题。 Docker 为 Device Mapper 目标名称添加了一些基础的命名空间,所以在一台机器上运行多个实例,就不会互相干扰。

目前为止,设置并不是完全一步到位,你可以看在 GitHub 的dind仓库中的这几个 issues。

Docker-in-Docker:更加糟糕

关于构建缓存?这同样相当棘手。人们经常问我,“我在运行 Docker-in-Docker,怎么样我才能使用主机上的镜像而不是重新将所有东西拉入内部 Docker?”。

一些胆大的人试图从主机绑定安装/var/lib/dockerDocker-in-Docker容器中。有时候在多容器之间共享/var/lib/docker

Docker 进程明确地被设计为独占/var/lib/docker的访问。没有什么可以触碰隐藏在那里的任何文件。

为什么是这样?这是从使用 dotClound 的时候开始的惨痛教训。 dotCloud 容器引擎工作于多进程同时访问/var/lib/dotcloud。像原子文件替换(代替编辑)这样的小窍门将代码强制锁定,其它 safe-ish 系统的实验就像 SQLite 和 BDB ,当我们重构我们的容器引擎(最终成为 Docker ),一个大的设计决策是收集一个单一进程的所有的容器操作和所有的并发访问。

(不要误会我的意思:完全有可能做一些漂亮的、可靠的和快速的涉及多个进程和先进的并发管理,但我们认为 Docker 的单一角色模型更简单,以及更容易编写和维护)

这意味着,如果你在多 Docker 实例之间共享/var/lib/docker目录,你会碰到许多问题。当然,它可能工作,尤其是在早期的测试。 “看,我可以在 Ubuntu 中运行Docker!”。但是,尝试做一些更复杂(从两个不同的实例拉相同的镜像)的操作,世界就会燃烧起来。

这意味着,如果你的 CI 系统进行构建和重建,每一次你会重新启动 Docker-in-Docker 容器,你可能会摧毁它的缓存。这真的不好。

解决方案

让我们退一步,你真的想要 Docker-in-Docker?或者当 CI 系统本身就是一个容器的时候你只是想要在 CI 系统中运行 Docker(具体为:构建、运行,有时 push 容器和镜像)。

我敢打赌,大多数人只是想要后者。你想要的一个解决方案是使得你的像 Jenkins 一样的 CI 系统能够启动容器。

最简单的方法就是通过使用-v参数帮顶安装它来为你的 CI 系统暴露 Docker 的 socket。

简单的来说,当你启动你的 CI 容器( Jenkins 或者其他),而不是使用 Docker-in-Docker 来 hacking 一些东西,用这条命令启动它:

docker run -v /var/run/docker.sock:/var/run/docker.sock ...

现在这个容器将能够访问 Docker Socket,然后就能启动容器。它将启动“兄弟”容器而不是“子”容器。

如果你的 CI 使用 Docker 二进制脚本,你可以将它包含在你的 CI 镜像中或者从主机 bind-mount。例如:

docker run -v /var/run/docker.sock:/var/run/docker.sock \ -v $(which docker):/bin/docker \

-ti ubuntu

这看起来就像 Docker-in-Docker,感觉起来也像 Docker—in-Docker,但它不是 Docker-in-Docker :当你的CI容器要创建更多容器时,这些容器将会在顶级 Docker 中被创建。你将不会与遇到嵌入的影响,构建缓存也将在多实例中共享。



作者:希云Docker容器管理平台
链接:https://www.jianshu.com/p/2e708cb9af3b
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

猜你喜欢

转载自blog.csdn.net/lzl001/article/details/84775084
今日推荐