在您的CI或测试环境中使用Docker-in-Docker?三思而后行

2022-12-28 13:59:42 浏览数 (1)

转译于~jpetazzo/Using Docker-in-Docker for your CI or testing environment? Think twice.

Docker-in-Docker的主要目的是帮助开发Docker本身。许多人使用它来运行CI(例如使用Jenkins),这看起来很好,但它们会遇到许多“有趣”的问题,可以通过将Docker套接字绑定到Jenkins容器来避免。

让我们看看这意味着什么。如果您想要没有详细信息的简短解决方案,只需滚动到本文的底部即可。☺

Docker-in-Docker:好的

两年多以前,我在Docker中贡献了-privileged标志 并编写了第一版dind。目标是帮助核心团队更快地开发Docker。在Docker-in-Docker之前,典型的开发周期是:

  • hackity hack
  • 建立
  • 停止当前运行的Docker守护程序
  • 运行新的Docker守护进程
  • 测试
  • 重复

如果你想要一个漂亮的,可重现的构建(即在一个容器中),它会有点复杂:

  • hackity hack
  • 确保可运行的Docker版本正在运行
  • 使用旧Docker构建新的Docker
  • 停止Docker守护进程
  • 运行新的Docker守护进程
  • 测试
  • 停止新的Docker守护进程
  • 重复

随着Docker-in-Docker的出现,这被简化为:

  • hackity hack
  • 构建 一步完成
  • 重复

好多了,对吧?

Docker-in-Docker:糟糕的

然而,与流行的看法相反,Docker-in-Docker并非100%由闪光,小马和独角兽制成。我的意思是,有一些问题需要注意。

一个是关于像AppArmor和SELinux这样的LSM(Linux安全模块):当启动容器时,“内部Docker”可能会尝试应用会使“外部Docker”发生冲突或混淆的安全配置文件。这实际上是最难解决的问题。试图合并-privileged 标志的原始实现。我的更改在我的Debian机器和Ubuntu测试虚拟机上工作(并且所有测试都会通过),但它会在迈克尔克罗斯比的机器上崩溃并烧毁 (如果我记得很好的话,它就是Fedora)。我不记得问题的确切原因,但可能是因为迈克是一个聪明的人SELINUX=enforce (我使用的是AppArmor)并且我的更改没有将SELinux配置文件考虑在内。

Docker-in-Docker:丑陋的

第二个问题与存储驱动程序相关联。在Docker中运行Docker时,外部Docker运行在普通文件系统(EXT4,BTRFS,你有什么)之上,但内部Docker运行在写时复制系统(AUFS,BTRFS,Device Mapper等)之上。 。,取决于外部Docker设置使用的内容)。有许多组合不起作用。例如,您无法在AUFS之上运行AUFS。如果在BTRFS之上运行BTRFS,它应该首先工作,但是一旦嵌套子卷,删除父子卷将失败。Device Mapper不是命名空间,因此如果Docker的多个实例在同一台机器上使用它们,它们将能够看到(并影响)彼此的图像和容器支持设备。没有bueno。

许多问题都有解决方法; 例如,如果你想在内部Docker中使用AUFS,只需 /var/lib/docker将其升级为一个卷,你就可以了。Docker为Device Mapper目标名称添加了一些基本的命名空间,因此如果Docker的多次调用在同一台机器上运行,它们就不会互相踩踏。

然而,设置并不完全是直截了当的,正如您可以从 GitHub 上的存储库中的那些问题中看到的 那样 。 dind

Docker-in-Docker:它变得更糟

那么构建缓存呢?那个人也会变得非常棘手。人们常常问我:“我正在运行Docker-in-Docker; 我如何使用位于主机上的图像,而不是在内部Docker中再次拉动所有图像?“

一些喜欢冒险的人试图/var/lib/docker 从主机绑定到Docker-in-Docker容器。有时它们/var/lib/docker与多个容器共享。

Sterling Archer建议你不要共享/ var / lib / docker,thx

Docker守护程序明确设计为具有独占访问权限/var/lib/docker。没有别的东西可以触摸,戳或隐藏任何隐藏在那里的Docker文件。

这是为什么?这是dotCloud时代的经验教训之一。dotCloud容器引擎通过让多个进程/var/lib/dotcloud同时访问来工作。聪明的技巧,如原子文件替换(而不是就地编辑),通过咨询和强制锁定来编写代码,以及像SQLite和BDB这样的安全系统的其他实验只能让我们到目前为止; 当我们重构我们的容器引擎(最终成为Docker)时,一个重大的设计决策就是在一个守护进程下收集所有容器操作,并完成所有并发访问的废话。

(不要误解我的意思:完全有可能做一些好的,可靠的,快速的,涉及多个进程和最先进的并发管理;但我们认为它更简单,更容易编写和维护,与Docker的单一演员模型一起使用。)

这意味着如果您/var/lib/docker在多个Docker实例之间共享目录,那么您将度过一段美好时光。当然,它可能会起作用,特别是在早期测试期间。“看哪,我可以docker run ubuntu!”但是尝试做更多的事情(从两个不同的实例中拉出相同的图像......)并观察世界燃烧。

这意味着,如果您的CI系统进行构建和重建,每次重新启动Docker-in-Docker容器时,您可能正在调整其缓存。这真的不酷。

解决方案

我们在这里退一步吧。你真的想要Docker-in-Docker吗?或者你只是希望能够从CI系统运行Docker(特别是:构建,运行,有时推送容器和图像),而这个CI系统本身就在容器中?

我敢打赌,大多数人都想要后者。您想要的只是一个解决方案,以便像Jenkins这样的CI系统可以启动容器。

最简单的方法是将Docker套接字暴露给CI容器,方法是将其与-v标志绑定。

简单地说,当您启动CI容器(Jenkins或其他)时,不要与Docker-in-Docker一起攻击某些东西,而是启动它:

代码语言:javascript复制
docker run -v /var/run/docker.sock:/var/run/docker.sock ...

现在这个容器可以访问Docker套接字,因此可以启动容器。除了不启动“子”容器,它将启动“兄弟”容器。

尝试使用docker官方图像(包含Docker二进制文件):

代码语言:javascript复制
docker run -v /var/run/docker.sock:/var/run/docker.sock 
           -ti docker

这看起来像Docker-in-Docker,感觉就像Docker-in-Docker,但它不是Docker-in-Docker:当这个容器创建更多容器时,这些容器将在顶级Docker中创建。您将不会遇到嵌套副作用,并且将在多个调用之间共享构建缓存。

⚠️这篇文章的旧版本建议将docker二进制文件从主机绑定到容器。这不再可靠,因为Docker Engine不再作为(几乎)静态库分发。

如果您想使用Jenkins CI系统中的Docker,您有多种选择:

  • 使用基本映像的打包系统安装Docker CLI(即如果您的映像基于Debian,请使用.deb包),
  • 使用Docker API。

译者总结

与其在容器里创建容器,不如在容器里挂载容器

0 人点赞