tl;博士
- 应用程序代码需要一个构建步骤(引入依赖项)
- 多个容器需要相同的“构建”代码
问:使用 docker / docker-compose 存档的好的策略/工作流程是什么?
长
我们正在使用多个组件(容器/服务)对 PHP 应用程序进行 dockerizing,例如
- 工作节点(PHP 进程通过主管保持活动状态)
- 调度程序(管理工作人员并在 cron 中运行重复任务)
- PHP-FPM/Nginx(网页界面)
这些服务在 docker-compose 文件中定义。在开发过程中,我们通过卷从每个容器的主机上的目录中挂载应用程序代码,以便我们“立即”看到每个服务中的更改(示例)。生活很好。
我们现在正在建立一个基于 Jenkins 的 CI/CD 环境,该环境应该构建(+ 测试)容器,然后将其推送到注册表。由于“从主机挂载”不再可能,我现在想知道将应用程序代码放入每个容器中的最佳方法是什么。
我们的设置中有两件事使这个恕我直言特别复杂:
- 我们有多个容器需要相同的应用程序代码
- “build-artifact”不是一个单一的、自容器的二进制文件(就像我们使用 go 那样),而是“我们所有的代码 + 安装的依赖项”==>“很多文件”(慢...)
- 涉及的构建步骤需要最终映像中不需要的软件
“3”的解决方案。通常是:使用多阶段构建。我们这样做。但是:那里的所有示例似乎都假设构建的代码将仅在另一个容器中使用(在我们的示例中并非如此,请参见 1。)
我们目前所做的
- 文件夹结构
application-code/
.docker/
builder/
Dockerfile
php-fpm/
Dockerfile
docker-compose.yml
build.sh
index.php
- 引入一个额外的“构建器”容器来“构建”应用程序(获取“所有”应用程序代码作为构建上下文;运行“composer install”)
# ./builder/Dockerfile
COPY ./ /codebase
RUN cd /codebase && composer install
- 在每个需要应用程序代码的容器中从此构建器“复制”,例如通过
# ./php-fpm/Dockerfile
ARG APP_CODE_PATH="/var/www/current"
COPY --from=builder --chown=www-data /codebase ${APP_CODE_PATH}
- 通过 docker-compose 编排
# ./docker-compose.yml
version: '3.7'
services:
builder-ci:
image: builder
build:
# ../ contains the "raw" application code
context: ../
dockerfile: ./.docker/builder/Dockerfile
php-fpm:
build:
context: .
dockerfile: ./php-fpm/Dockerfile
args:
- APP_CODE_PATH=/var/www/current
- 通过构建
# build.sh
## build builder
docker-compose -f ./.docker/docker-compose.yml --project-directory ./.docker build builder
## build the rest
docker-compose -f ./.docker/docker-compose.yml --project-directory ./.docker build --parallel
临
- “较小”的图像(php-fpm 不会安装作曲家)
- 应用程序代码只构建一次,然后复制过来
魂斗罗
- 构建器容器除了“构建”之外没有其他用途 ==> 感觉不干净
- 构建“构建器”必须在构建任何其他容器之前完成
- 这意味着我们有一个额外的
备择方案
- 不要使用构建器容器,而是通过使用多阶段构建将构建步骤“合并”到例如“调度程序”容器中(因此我们最终不会在最终图像中使用作曲家)
- 摆脱“builder” - 但现在所有其他服务都依赖于“Scheduler” ==> 感觉更脏
- 使用卷共享代码
- 我们不必“复制”图像中的文件,而可以简单地“安装卷”==>感觉“干净”/没有“文件重复”(我首先认为这是一个非常好的方法......)
- 但:
- 您无法在构建期间填充卷,因此您需要“运行”容器以便在容器“中”获取应用程序代码 ==> 我们突然间不仅有一个构建器容器,而且我们还需要“运行”它填充音量
- 容器不再“自包含”,即从注册表中提取“仅调度程序”将不起作用 - 我们还必须有适当的卷并且必须由构建器填充 ==> 编排变得更加复杂
- 卷不是临时的,即在刷新之前它将包含“旧”应用程序代码 ==> 这可能会导致混乱和意外行为
链接
- 容器可以共享一个框架吗?(相关问题)
- https://github.com/moby/moby/issues/14080(不支持在构建期间挂载的卷)
- https://docs.docker.com/develop/develop-images/multistage-build/(构建器模式与多阶段构建)