Docker-in-Docker 是一个虚拟化的 Docker 守护进程环境,在容器本身内运行以构建容器映像。 创建 Docker-in-Docker 的主要目的是帮助开发 Docker 本身。 很多人用它来运行 Jenkins CI。 乍一看这似乎很正常,但随后出现的问题可以通过在 Jenkins CI 容器中安装 Docker 来避免。 本文告诉您如何执行此操作。 如果您对最终解决方案感兴趣,但没有详细信息,请阅读本文的最后一部分“解决问题”。
Docker-in-Docker:“好”
两年多前我投入了 Docker
- 黑客攻击;
- 建造;
- 停止正在运行的 Docker 守护进程;
- 启动新的 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=enforce 的聪明人(我使用了 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 已向 Device Mapper 目标名称添加了一些基本命名空间,这样如果多个 Docker 调用在同一台计算机上运行,它们就不会互相干扰。
然而,这样的设置一点也不简单,从这些可以看出
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 目录,则会出现问题。 当然,这是可行的,尤其是在测试的早期阶段。 “听着,妈妈,我可以作为 docker 运行 ubuntu!” 但尝试一些更复杂的事情,比如从两个不同的实例中提取相同的图像,你会看到世界在燃烧。
这意味着,如果您的 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,您有 2 个选择:
使用基本映像打包系统(即,如果您的映像基于 Debian,则使用 .deb 包)和 Docker API 安装 Docker CLI。
一些广告🙂
感谢您与我们在一起。 你喜欢我们的文章吗? 想看更多有趣的内容? 通过下订单或推荐给朋友来支持我们,
Dell R730xd 在阿姆斯特丹的 Equinix Tier IV 数据中心便宜 2 倍? 只有这里
来源: habr.com