将 Docker-in-Docker 用于 CI 或测试环境前请慎重考虑

将 Docker-in-Docker 用于 CI 或测试环境前请慎重考虑

Docker-in-Docker 是一个虚拟化的 Docker 守护进程环境,在容器本身内运行以构建容器映像。 创建 Docker-in-Docker 的主要目的是帮助开发 Docker 本身。 很多人用它来运行 Jenkins CI。 乍一看这似乎很正常,但随后出现的问题可以通过在 Jenkins CI 容器中安装 Docker 来避免。 本文告诉您如何执行此操作。 如果您对最终解决方案感兴趣,但没有详细信息,请阅读本文的最后一部分“解决问题”。

将 Docker-in-Docker 用于 CI 或测试环境前请慎重考虑

Docker-in-Docker:“好”

两年多前我投入了 Docker ——有特权并写道 dind 的第一个版本。 目标是帮助核心团队更快地开发 Docker。 在 Docker-in-Docker 出现之前,典型的开发周期如下所示:

  • 黑客攻击;
  • 建造;
  • 停止正在运行的 Docker 守护进程;
  • 启动新的 Docker 守护进程;
  • 测试;
  • 重复循环。

如果你想制作一个漂亮的、可复制的组件(即在容器中),那么它就会变得更加复杂:

  • 黑客攻击;
  • 确保 Docker 的工作版本正在运行;
  • 使用旧 Docker 构建新 Docker;
  • 停止 Docker 守护进程;
  • 启动一个新的 Docker 守护进程;
  • 测试;
  • 停止新的 Docker 守护进程;
  • 再说一次

随着 Docker-in-Docker 的出现,这个过程变得更加简单:

  • 黑客攻击;
  • 组装+发射一站式完成;
  • 重复循环。

这样不是好很多吗?

将 Docker-in-Docker 用于 CI 或测试环境前请慎重考虑

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 调用在同一台计算机上运行,​​它们就不会互相干扰。

然而,这样的设置一点也不简单,从这些可以看出 用品 在 GitHub 上的 dind 存储库中。

Docker-in-Docker:情况变得更糟

构建缓存怎么样? 这也可能相当困难。 人们经常问我“如果我运行 Docker-in-Docker,我如何使用主机上托管的映像,而不是将所有内容拉回到我的内部 Docker 中”?

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

将 Docker-in-Docker 用于 CI 或测试环境前请慎重考虑
您想破坏您的数据吗? 因为这正是会损坏您的数据的原因!

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。

一些广告🙂

感谢您与我们在一起。 你喜欢我们的文章吗? 想看更多有趣的内容? 通过下订单或推荐给朋友来支持我们, 面向开发人员的云 VPS,4.99 美元起, 我们为您发明的入门级服务器的独特模拟: VPS (KVM) E5-2697 v3(6 核)10​​4GB DDR480 1GB SSD 19Gbps XNUMX 美元或如何共享服务器的全部真相? (适用于 RAID1 和 RAID10,最多 24 个内核和最多 40GB DDR4)。

Dell R730xd 在阿姆斯特丹的 Equinix Tier IV 数据中心便宜 2 倍? 只有这里 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 电视低至 199 美元 在荷兰! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - 99 美元起! 阅读 如何建设基础设施公司同级使用价值730欧元的Dell R5xd E2650-4 v9000服务器一分钱?

来源: habr.com

添加评论