我有一个包含所有项目的整体仓库。我当前的设置是启动一个构建容器,安装我的整体仓库,并按顺序构建我的项目。复制二进制文件,并依次构建它们各自的运行时(生产)容器。
我发现此过程相当缓慢,并且希望提高速度。我想采用的两个主要方法
在构建容器中,同时构建我的项目二进制文件。而不是顺序进行。
就像第1步一样,同时构建我的运行时(生产)容器。
我做了一些研究,似乎有两个我感兴趣的Docker 功能:
Multi-stage building。这使我无需担心构建容器,而是将所有内容放入一个Dockerfiles
。
docker-compose
的 --parallel
选项,可以解决方法#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缓存吗?
答案 0 :(得分:2)
因此,这里有几件事情可以尝试。首先,是的,请尝试--parallel
,看看对整体构建时间的影响会很有趣。看来您无法控制并行构建的数量,所以我想知道是否会尝试一次性完成所有构建。
如果发现确实如此,则可以编写仅包含服务子集的docker-compose.yml
文件,这样一次只包含五个服务,然后依次针对每个服务构建。确实,您可以编写一个脚本来读取现有的YAML配置并将其拆分,这样就无需分别维护整体配置和拆分配置。
我在评论中建议多阶段操作无济于事,但我现在认为并非如此。我想知道Dockerfile中的第二阶段是否会阻塞,直到第一个阶段完成,但事实并非如此-如果第二阶段从已知映像开始,则只有在遇到COPY --from=first_stage
命令时才阻塞,当您从编译阶段复制二进制文件时,可以在最后完成。
当然,如果多阶段构建没有并行化,那么docker commit
是值得尝试的。您问过这是否使用了图层缓存,答案是我认为这并不重要-因此,您在此处的操作将是:
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安装和构建是该项目的瓶颈,并且是迄今为止最慢的。
我正在使用的策略是将构建分为两个阶段:
第一步是启动一个整体的构建泊坞窗,以cache
的一致性作为绑定卷将其安装在其上。然后使用Goroutines在其内部并行构建容器的所有二进制依赖项。每个Goroutine都会调用一个build.sh bash脚本来执行构建。生成的二进制文件将写入相同的安装卷。缓存以已安装的docker卷的形式使用,并且二进制文件将在每次运行时尽最大努力得到保存。
第二阶段是并行构建图像。这是使用Docker的here文档中的Go SDK来完成的。这也是使用Goroutines并行完成的。除了一些基本的优化之外,这个阶段没有什么特别的。
我没有有关旧构建系统的性能数据,但是构建所有16个项目很容易花费了30分钟的时间。此构建非常基础,并且没有并行构建映像或使用任何优化。
新版本非常快。如果所有内容都已缓存并且没有任何更改,则构建大约需要2分钟。换句话说,启动构建系统,检查缓存以及构建相同的缓存docker映像的开销大约需要2分钟。如果根本没有缓存,则新构建大约需要5分钟。对旧版本进行了巨大的改进。
感谢@halfer的帮助。