Docker内部并行构建

时间:2019-06-08 07:31:27

标签: docker build docker-compose

我有一个包含所有项目的整体仓库。我当前的设置是启动一个构建容器,安装我的整体仓库,并按顺序构建我的项目。复制二进制文件,并依次构建它们各自的运行时(生产)容器。

我发现此过程相当缓慢,并且希望提高速度。我想采用的两个主要方法

  1. 在构建容器中,同时构建我的项目二进制文件。而不是顺序进行。

  2. 就像第1步一样,同时构建我的运行时(生产)容器。

我做了一些研究,似乎有两个我感兴趣的Docker 功能

  1. Multi-stage building。这使我无需担心构建容器,而是将所有内容放入一个Dockerfiles

  2. docker-compose
  3. --parallel选项,可以解决方法#2,允许我同时构建运行时容器。

但是,仍然有两个主要的问题

  1. 我如何将两个功能粘合在一起?

  2. 如何在构建Docker中同时构建二进制文件?换句话说,如何实现方法#1?

说明

无论是否使用多阶段,都有两个逻辑阶段。

首先是二进制构建阶段。在此阶段中,工件是构建容器中已编译的可执行文件(二进制文件)。由于我没有使用多阶段构建,因此将这些二进制文件复制到主机中,因此主机充当中间暂存区。目前,二进制文件是按顺序构建的,我想在构建容器中同时构建它们。因此,方法#1。

第二个是图像构建阶段。在此阶段,上一阶段的二进制文件(现在存储在主机上)用于构建我的生产映像。我还想同时构建这些图像,因此方法#2。

多阶段允许我消除对中间阶段区域(主机)的需要。 --parallel允许我同时构建生产映像。

我想知道的是如何使用多阶段和--parallel实现方法#1和#2。因为对于每个项目,我都可以定义一个单独的多阶段Dockerfiles并在所有项目上调用--parallel来分别构建其图像。这将实现方法#2,但这将为每个项目生成一个单独的构建容器并占用大量资源(我为所有项目使用相同的构建容器,它的大小为6 GB)。另一方面,我可以编写脚本以在构建容器中同时构建我的项目二进制文件。这样可以实现方法#1,但是如果要同时构建生产映像,则不能使用多阶段。

我真正想要的是像这样的Dockerfiles

FROM alpine:latest AS builder
RUN concurrent_build.sh binary_a binary_b

FROM builder AS prod_img_a
COPY binary_a .

FROM builder AS prod_img_b
COPY binary_b .

并且能够像这样运行docker-compose命令(我正在这样做):

docker-compose --parallel prod_img_a prod_img_b

进一步的澄清

运行时二进制文件和运行时容器不是分开的东西。我只希望能够并行构建二进制文件和生产映像。

--parallel不使用其他主机,但是我的构建容器很大。如果我使用多阶段构建,并且在本地开发机上并行运行其中的15个构建容器,那就不好了。

我也在考虑分别编译二进制和运行时容器,但是我没有找到一种简便的方法来进行编译。我从未使用过docker commit,那会牺牲docker缓存吗?

2 个答案:

答案 0 :(得分:2)

因此,这里有几件事情可以尝试。首先,是的,请尝试--parallel,看看对整体构建时间的影响会很有趣。看来您无法控制并行构建的数量,所以我想知道是否会尝试一次性完成所有构建。

如果发现确实如此,则可以编写仅包含服务子集的docker-compose.yml文件,这样一次只包含五个服务,然后依次针对每个服务构建。确实,您可以编写一个脚本来读取现有的YAML配置并将其拆分,这样就无需分别维护整体配置和拆分配置。

我在评论中建议多阶段操作无济于事,但我现在认为并非如此。我想知道Dockerfile中的第二阶段是否会阻塞,直到第一个阶段完成,但事实并非如此-如果第二阶段从已知映像开始,则只有在遇到COPY --from=first_stage命令时才阻塞,当您从编译阶段复制二进制文件时,可以在最后完成。

当然,如果多阶段构建没有并行化,那么docker commit是值得尝试的。您问过这是否使用了图层缓存,答案是我认为这并不重要-因此,您在此处的操作将是:

  • 启动二进制容器以运行shell或sleep命令
  • 以相同的方式启动运行时容器
  • 使用docker cp将二进制文件从第一个复制到第二个
  • 使用docker commit从新的运行时容器中创建新的运行时映像

这不涉及任何网络操作,因此应该非常快-此时您将已经从并行化中受益匪浅。如果二进制文件的大小不重要,那么您甚至可以尝试并行执行复制操作:

docker cp binary1:/path/to/binary runtime1:/path/to/binary &
docker cp binary2:/path/to/binary runtime2:/path/to/binary &
docker cp binary3:/path/to/binary runtime3:/path/to/binary &

请注意,尽管这些操作是磁盘绑定操作,所以您可能会发现串行执行它们没有任何好处。

您能尝试一下并报告一下吗?

  • 每个容器的现有构建时间
  • 您现有的总体构建时间
  • 并行化后的新构建时间

首先在本地完成所有操作,如果可以得到一些有用的提速,请在可能具有更多CPU核心的构建基础结构上进行尝试。

答案 1 :(得分:1)

结果

我的mono-repo容器有16个项目,一些是微服务,只有几个MB,一些是较大的服务,大约有300到500 MB。

该版本包含两个先决条件的编译,一个是gRPC,另一个是XDR。两者都很小,仅需1到2秒钟即可构建。

该版本包含一个node_modules安装阶段。 NPM安装和构建是该项目的瓶颈,并且是迄今为止最慢的。

我正在使用的策略是将构建分为两个阶段:

  1. 第一步是启动一个整体的构建泊坞窗,以cache的一致性作为绑定卷将其安装在其上。然后使用Goroutines在其内部并行构建容器的所有二进制依赖项。每个Goroutine都会调用一个build.sh bash脚本来执行构建。生成的二进制文件将写入相同的安装卷。缓存以已安装的docker卷的形式使用,并且二进制文件将在每次运行时尽最大努力得到保存。

  2. 第二阶段是并行构建图像。这是使用Docker的here文档中的Go SDK来完成的。这也是使用Goroutines并行完成的。除了一些基本的优化之外,这个阶段没有什么特别的。

我没有有关旧构建系统的性能数据,但是构建所有16个项目很容易花费了30分钟的时间。此构建非常基础,并且没有并行构建映像或使用任何优化。

新版本非常快。如果所有内容都已缓存并且没有任何更改,则构建大约需要2分钟。换句话说,启动构建系统,检查缓存以及构建相同的缓存docker映像的开销大约需要2分钟。如果根本没有缓存,则新构建大约需要5分钟。对旧版本进行了巨大的改进。

感谢@halfer的帮助。