在将Docker-in-Docker用于CI或测试环境之前,请仔细考虑。



Docker-in-Docker是在容器本身中运行的虚拟Docker守护程序,用于构建容器映像。创建Docker-in-Docker的主要目标是帮助开发Docker本身。许多人使用它来运行Jenkins CI。乍一看,这似乎很正常,但是通过在Jenkins CI容器中安装Docker可以避免一些问题。本文介绍了如何执行此操作。如果您对没有详细信息的最终解决方案感兴趣,只需阅读文章“解决问题”的最后一节。



Docker-in-Docker:好


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

  • 骇客骇客
  • 部件
  • 停止正在运行的docker守护进程;
  • 启动一个新的docker守护进程;
  • 测试;
  • 循环重复。

如果要制作美观,可复制的程序集(即在容器中),它将变得更加复杂:

  • 骇客骇客
  • 确保运行正常的Docker版本;
  • 用旧码头工人建造一个新码头工人;
  • 停止docker守护进程;
  • 启动一个新的docker守护进程;
  • 去测试;
  • 停止新的Docker守护程序;
  • 重复。

随着Docker-in-Docker的出现,该过程已得到简化:

  • 骇客骇客
  • 一步一步组装+发射;
  • 循环重复。

不是更好吗?



Docker-in-Docker:不好


但是,与普遍的看法相反,Docker-in-Docker不是100%由星星,小马和独角兽组成。我的意思是,开发人员需要了解几个问题。

其中之一涉及LSM(Linux安全模块),例如AppArmor和SELinux:当容器启动时,“内部Docker”可能会尝试应用会与“外部Docker”产生冲突或混淆的安全配置文件。当尝试结合–privileged标志的原始实现时,这是最需要解决的问题。我所做的更改有效,所有测试也将在我的Debian机器和Ubuntu测试虚拟机上通过,但它们将崩溃并在Michael Crosby机器上刻录(据我所记得,他有Fedora)。我不记得问题的确切原因,但这可能是因为Mike是一个明智的人,他使用SELINUX = force(我使用AppArmor),并且我的更改未考虑SELinux配置文件。

Docker-in-Docker:生气


第二个问题与Docker存储驱动程序有关。当您启动Docker-in-Docker时,外部Docker在常规文件系统(EXT4,BTRFS或您拥有的任何文件)之上运行,而内部Docker在写复制系统(AUFS,BTRFS,Device Mapper等)之上运行。 ,具体取决于配置为使用外部Docker的对象)。在这种情况下,有许多组合将不起作用。例如,您不能在AUFS之上运行AUFS。

如果在BTRFS之上运行BTRFS,则应该首先运行,但是一旦出现子项,就不能删除父子卷。 Device Mapper模块没有名称空间,因此,如果多个Docker实例在同一台计算机上使用它,它们可以相互查看(并影响)彼此以及容器备份设备上的映像。这是不好的。

有解决方法可以解决许多这些问题。例如,如果您想在内部Docker中使用AUFS,只需将/ var / lib / docker文件夹变成一个,一切都会好起来的。 Docker在设备映射器的目标名称中添加了一些基本的命名空间,因此,如果在同一台计算机上进行多个Docker调用,它们将不会彼此“踩”。

但是,从GitHub的dind存储库中的这些文章可以看到,此设置远非简单

Docker-in-Docker:变得更糟


那构建缓存呢?这也可能非常困难。人们经常问我“如果我正在运行Docker-in-Docker,如何使用主机上的映像,而不是将所有内容都拉回内部Docker”?

一些进取心的人们试图将/ var / lib / docker从主机绑定到Docker-in-Docker容器。有时他们与多个容器共享/ var / lib / docker。


要破坏数据吗?因为这恰恰会损坏您的数据!

docker守护程序的设计明确地具有对/ var / lib / docker的独占访问权限。任何其他都不应“触摸,戳或触摸”此文件夹中的任何Docker文件。

为什么会这样呢?因为这是从开发dotCloud中学到的最困难的经验之一的结果。 dotCloud容器引擎与同时访问/ var / lib / dotcloud的多个进程一起使用。棘手的技巧,例如原子文件替换(而不是就地编辑),使用咨询和强制性锁“插入”代码以及使用安全系统(例如SQLite和BDB)进行的其他实验,并不总是有效。当我们重新设计容器引擎(最终变成Docker)时,主要的设计决策之一是将所有容器操作收集在一个守护程序下,以消除所有这些同时访问的废话。

不要误会我的意思:做好,可靠和快速的事情很有可能,其中包括多个过程和现代并行控制。但是我们认为使用Docker作为唯一的播放器来编写和维护代码更加容易。

这意味着,如果您在多个Docker实例之间共享/ var / lib / docker目录,则会遇到问题。当然,这可能会起作用,尤其是在测试的早期阶段。 “听着,马,我可以将ubuntu作为docker运行!”但是尝试做一些更复杂的事情,例如,从两个不同的实例中提取相同的图像,您将看到世界如何燃烧。

这意味着,如果您的CI系统执行组装和重新组装,那么每次重新启动Docker-in-Docker容器时,都有冒着将核弹放入其缓存中的风险。这根本不酷!

解决问题


让我们退后一步。您是否真的需要Docker-in-Docker,还是只希望能够运行Docker,即在此CI系统本身位于容器中的情况下,从您的CI系统构建并运行容器和映像?

我敢打赌,大多数人都需要后一种选择,也就是说,他们想要像Jenkins这样的CI系统来运行容器。最简单的方法是将Docker套接字简单地插入到CI容器中,并将其与-v标志关联。

简而言之,当您启动CI容器(Jenkins或其他容器)时,不要从Docker-in-Docker中入侵某些东西,而是从以下一行开始:

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

现在,此容器将可以访问Docker套接字,因此将能够启动容器。除了运行“相关”容器之外,它不会启动“子”容器。

使用官方docker镜像(包含Docker二进制文件)尝试以下操作:

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

它的外观和工作方式类似于Docker-in-Docker,但不是Docker-in-Docker:当此容器创建其他容器时,它们将在顶级Docker中创建。您将不会遇到嵌套的副作用,并且构建缓存将在多个调用之间共享。

注意:本文的早期版本建议将Docker二进制文件从主机绑定到容器。由于Docker机制不再扩展到静态或几乎静态的库,现在这已经变得不可靠。

因此,如果要使用Jenkins CI中的Docker,则有两个选择:
使用基本映像打包系统安装Docker CLI(即,如果您的映像基于Debian,则使用.deb软件包),以及使用Docker API。

一点广告:)


感谢您与我们在一起。你喜欢我们的文章吗?想看更多有趣的资料吗?通过下订单或向您的朋友推荐给开发人员的基于云的VPS,最低价格为4.99美元这是我们为您发明的入门级服务器 独特类似物:关于VPS(KVM)E5-2697 v3(6核)的全部真相10GB DDR4 480GB SSD 1Gbps从$ 19还是如何划分服务器?(RAID1和RAID10提供选件,最多24个内核和最大40GB DDR4)。

阿姆斯特丹的Equinix Tier IV数据中心的戴尔R730xd便宜2倍吗?在荷兰2台Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100电视戴尔R420-2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB-$ 99起!阅读有关如何构建基础设施大厦的信息。使用Dell R730xd E5-2650 v4服务器花费一欧元9000欧元的c类?

All Articles