【问题标题】:Why does docker-compose build run my steps twice?为什么 docker-compose build 运行我的步骤两次?
【发布时间】:2021-02-05 00:17:26
【问题描述】:

我正在使用这样的 Dockerfile 进行多阶段构建:

#####################################
## Build the client
#####################################

FROM node:12.19.0 as web-client-builder
WORKDIR /workspace
COPY web-client/package*.json ./

# Running npm install before we update our source allows us to take advantage
# of docker layer caching. We are excluding node_modules in .dockerignore
RUN npm ci

COPY web-client/ ./
RUN npm run test:ci
RUN npm run build


#####################################
## Host the client on a static server
#####################################

FROM nginx:1.19 as web-client
COPY --from=web-client-builder /workspace/nginx-templates /etc/nginx/templates/
COPY --from=web-client-builder /workspace/nginx.conf /etc/nginx/nginx.conf
COPY --from=web-client-builder /workspace/build /var/www/

#####################################
## Build the server
#####################################

FROM openjdk:11-jdk-slim as server-builder
WORKDIR /workspace
COPY build.gradle settings.gradle gradlew ./
COPY gradle ./gradle
COPY server/ ./server/
RUN ./gradlew --no-daemon :server:build

#####################################
## Start the server
#####################################

FROM openjdk:11-jdk-slim as server
WORKDIR /app
ARG JAR_FILE=build/libs/*.jar
COPY --from=server-builder /workspace/server/$JAR_FILE ./app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]

我也有一个像这样的 docker-compose.yml:

version: "3.8"
services:
  server:
    restart: always
    container_name: server
    build:
      context: .
      dockerfile: Dockerfile
      target: server
    image: server
    ports:
      - "8090:8080"
  web-client:
    restart: always
    container_name: web-client
    build:
      context: .
      dockerfile: Dockerfile
      target: web-client
    image: web-client
    environment:
      - LISTEN_PORT=80
    ports:
      - "8091:80"

这里涉及到的两个图像web-clientserver 是完全独立的。我想利用多阶段构建并行化。

当我运行 docker-compose build(我在 docker-compose 1.27.4 上)时,我得到这样的输出

λ docker-compose build
Building server
Step 1/24 : FROM node:12.19.0 as web-client-builder
 ---> 1f560ce4ce7e
... etc ...
Step 6/24 : RUN npm run test:ci
 ---> Running in e9189b2bff1d
... Runs tests ...
... etc ...
Step 24/24 : ENTRYPOINT ["java","-jar","/app/app.jar"]
 ---> Using cache
 ---> 2ebe48e3b06e

Successfully built 2ebe48e3b06e
Successfully tagged server:latest
Building web-client
Step 1/11 : FROM node:12.19.0 as web-client-builder
 ---> 1f560ce4ce7e
... etc ...
Step 6/11 : RUN npm run test:ci
 ---> Using cache
 ---> 0f205b9549e0
... etc ...
Step 11/11 : COPY --from=web-client-builder /workspace/build /var/www/
 ---> Using cache
 ---> 31c4eac8c06e

Successfully built 31c4eac8c06e
Successfully tagged web-client:latest

请注意,我的测试 (npm run test:ci) 运行了两次(针对服务器目标的步骤 6/24,然后针对 Web 客户端目标在步骤 6/11 再次运行)。我想了解为什么会发生这种情况,但我想这不是一个大问题,因为至少在第二次进行测试时它已经被缓存了。

当我尝试并行运行构建时,这将成为一个更大的问题。现在我得到这样的输出:

λ docker-compose build --parallel
Building server     ...
Building web-client ...
Building server
Building web-client
Step 1/11 : FROM node:12.19.0 as web-client-builderStep 1/24 : FROM node:12.19.0 as web-client-builder
 ---> 1f560ce4ce7e
... etc ...
Step 6/24 : RUN npm run test:ci
 ---> e96afb9c14bf
Step 6/11 : RUN npm run test:ci
 ---> Running in c17deba3c318
 ---> Running in 9b0faf487a7d

> web-client@0.1.0 test:ci /workspace
> react-scripts test --ci --coverage --reporters=default --reporters=jest-junit --watchAll=false


> web-client@0.1.0 test:ci /workspace
> react-scripts test --ci --coverage --reporters=default --reporters=jest-junit --watchAll=false
... Now my tests run in parallel twice, and the output is interleaved for both parallel runs ...

很明显,现在测试运行了两次,因为现在我正在并行运行构建,它们没有机会缓存​​。

谁能帮我理解这个?我认为 docker 多阶段构建的优点之一是它们是可并行的,但这种行为对我来说没有意义。我误会了什么?

注意 我还尝试为 docker-compose 启用 BuildKit。我很难理解输出。我不相信它会运行两次,但我也不确定它是否在并行化。我需要深入研究它,但我的主要问题是:我希望了解为什么多阶段构建不会以我期望的方式并行运行没有 BuildKit。

【问题讨论】:

    标签: docker docker-compose dockerfile docker-multi-stage-build docker-buildkit


    【解决方案1】:

    为什么在没有 BuildKit 的情况下多阶段构建不会像我预期的那样并行运行。

    这就是 BuildKit 的亮点。

    Docker 中多阶段的主要目的是通过仅保留应用程序正常工作所需的内容来生成更小的图像。例如

    FROM node as builder
    
    COPY package.json package-lock.json
    RUN npm ci
    
    COPY . /app
    
    RUN npm run build
    
    FROM nginx
    COPY --from=/app/dist --chown=nginx /app/dist /var/www
    

    构建项目所需的所有开发工具都不会复制到最终映像中。这会转化为更小的最终图像。


    编辑:

    来自BuildKit documentation

    BuildKit 构建基于称为 LLB 的二进制中间格式,用于定义运行部分构建的进程的依赖关系图。 tl;dr:LLB 之于 Dockerfile 就像 LLVM IR 之于 C。

    换句话说,BuildKit 能够评估每个阶段的依赖关系,从而允许并行执行。

    【讨论】:

      【解决方案2】:

      您可以将其拆分为两个单独的 Dockerfile。我可能会编写一个包含前两个阶段的web-client/Dockerfile(将相对的COPY 路径更改为./),然后离开根目录Dockerfile 来构建服务器应用程序。然后你的docker-compose.yml 文件可以指向这些单独的目录:

      services:
        server:
          build: . # equivalent to {context: ., dockerfile: Dockerfile}
        web-client:
          build: web-client
      

      正如@Stefano 在their answer 中指出的那样,多阶段构建围绕构建单个最终映像进行了更多优化,并且在“经典”构建器中,它们始终从开始运行到指定的目标阶段,没有任何特定的逻辑从哪里开始。

      【讨论】:

      • 知道了 - 谢谢。我将它们放在同一个 Dockerfile 中,因为即使它们现在是独立的,最终 Web 客户端也会依赖于服务器的构建(以生成 Typescript 接口)。所以听起来如果我想要在不重复步骤的情况下进行并行构建,BuildKit 将是可行的方法。
      猜你喜欢
      • 2018-03-21
      • 2019-04-04
      • 2018-10-18
      • 1970-01-01
      • 2023-03-28
      • 2023-01-22
      • 1970-01-01
      • 1970-01-01
      • 2016-08-14
      相关资源
      最近更新 更多