11

我有一个包含多个目标的 Dockerfile。例如:

FROM x as frontend
...

FROM y as backend
...

FROM z as runtime
...
COPY --from=frontend ...
COPY --from=backend ...

为了构建和标记最终图像,我使用:

docker build -t my-project .

为了构建和标记中间目标,我提供--target了参数:

docker build -t my-project-backend --target backend .

但是是否可以构建最终图像并标记所有中间图像?换句话说,与:

docker build -t my-project-frontend --target frontend .
docker build -t my-project-backend --target backend .
docker build -t my-project .

但是只有一个命令?

我认为需要一些解释。如果使用 buildkit ( export DOCKER_BUILDKIT=1),那么所有独立的目标都是并行构建的。所以它比一个一个地构建它们要快得多。而且我需要标记每个目标以将它们推送到 docker 注册表以及最终目标。

目前我在没有 buildkit 的情况下在 CI 中构建我的图像,我正在尝试加快这个过程。

4

4 回答 4

6

我做了一些搜索,但dockerCLI 目前似乎没有提供任何直接的方法来做到这一点。最接近的是我在评论中提出的想法:构建主图像并标记所有中间图像。

以此Dockerfile为例:

FROM alpine AS frontend
RUN sleep 15 && touch /frontend

FROM alpine AS backend
RUN sleep 15 && touch /backend

FROM alpine AS runtime
COPY --from=frontend /frontend /frontend
COPY --from=backend /backend /backend

(这些sleeps 只是为了通过缓存明显加快速度)

建立这个:

export DOCKER_BUILDKIT=1 # enable buildkit for parallel builds
docker build -t my-project .
docker build -t my-project-backend --target backend .
docker build -t my-project-frontend --target frontend .

将要

  1. runtime通过首先构建所有必需的中间图像来构建主图像,例如frontend和,并且只用backend标记主图像my-project
  2. 构建backend标记为my-project-backend但使用先前构建中的缓存的目标
  3. 一样,但对于backend

这里的每个图像只会构建一次 - 但最终这与您在问题中所述所做的完全相同,只是顺序不同。

如果您真的希望能够在单个命令中执行此操作,您可以使用它docker-compose来构建“多个图像”:

version: "3.8"
services:
  my-project:
    image: my-project
    build: .
  backend:
    image: my-project-backend
    build:
      context: .
      target: backend
  frontend:
    image: my-project-frontend
    build:
      context: .
      target: frontend
export DOCKER_BUILDKIT=1 # enable buildkit for parallel builds
export COMPOSE_DOCKER_CLI_BUILD=1 # use docker cli for building
docker-compose build

这里docker-compose基本上会docker build为你运行与上面相同的命令。

在这两种情况下,尽管您应该知道,尽管缓存层大大加快了构建速度,但仍然会发生新的构建,每次都会:

  • 将构建上下文(即当前目录的内容)发送到 docker 守护进程
  • 将您的任何远程文件下载ADD到图像中,并且仅在内容再次相同时才使用缓存-对于大文件/慢速网络,这将显着减慢。

我在这个论坛帖子中发现的另一个解决方法是在图像中添加一个LABEL并用于docker image ls --filter在构建后获取图像 ID。

但是测试它似乎docker image ls不会在使用时显示中间图像buildkit。此外,这种方法将需要更多命令/专用脚本 - 这将再次比您当前的方法更多工作。

于 2021-01-05T17:29:56.153 回答
0

在我看来,强制构建多个阶段的最佳解决方案是使用从我们要构建的阶段复制“假”文件来创建阶段。

FROM scratch AS build-all-stages
COPY --from=first-stage-to-build fake-not-exist-file?.txt .
COPY --from=second-stage-to-build fake-not-exist-file?.txt .
...
COPY --from=x-stage-to-build fake-not-exist-file?.txt .

COPY 步骤的目的只是为了强制构建每个阶段。

从性能的角度来看,上述示例比上一个答案中提到的要好,因为我们使用了不存在的空基本映像“临时”和“复制”文件,从而浪费了最少的内存和时间。

接下来我们应该使用参数分别构建每个图像--target(所有层都将从缓存中获取)。

于 2022-02-03T10:40:15.527 回答
0

您现在最接近的方法是使用 Docker 的buildx bake命令。它允许您使用如下语法定义 HCL 文件:

group "default" {
    targets = ["app", "frontend", "backend"]
}

target "app" {
    dockerfile = "Dockerfile"
    tags = ["docker.io/username/app"]
}

target "frontend" {
    dockerfile = "Dockerfile"
    target = "frontend"
    tags = ["docker.io/username/frontend"]

}

target "backend" {
    dockerfile = "Dockerfile"
    target = "backend"
    tags = ["docker.io/username/backend"]
}

然后你会用docker buildx bake -f bake.hcl

也就是说,你所做的几乎可以肯定是一个错误。多阶段构建旨在将构建环境与运行时环境分开,而不是创建多个不同的映像。换句话说,当您需要螺丝刀时,您正在使用锤子,是的,它会起作用,但结果并不理想。

首选且更简单的解决方案是为您要构建的每个映像创建一个单独的 Dockerfile。如果你的图像有一个共同的基础,那么考虑将它移到它自己的图像中,并在你的FROM步骤中引用它。

作为开发人员,要在 docker 中构建多个镜像,通常使用一个docker-compose.yml定义所有三个镜像的文件,然后docker-compose up --build在构建每个镜像后使用单个命令启动整个堆栈。例如,撰写文件可能如下所示:

version: 2
services:
  app:
    build: Dockerfile.app
    image: username/app
    # ...
  frontend:
    build: Dockerfile.frontend
    image: username/frontend
    # ...
  backend:
    build: Dockerfile.backend
    image: username/backend
    # ...

对于部署到生产,这将是每个映像的单独 CI/CD 管道,以执行所需的单元测试、构建,然后扇入部署步骤,该步骤使用每个映像的指定版本运行整个堆栈。

于 2022-02-03T13:59:19.107 回答
-1

我不认为有一种方法可以为两个应用程序创建一个容器,而且我认为这不是正确的方法。

一个容器 docker 是为单个应用程序创建的,甚至为日志如何处理两个应用程序?如果要停止一个或重新启动一个?

我认为正确的方法是 docker-compose

并使用一些想法,例如:

码头工人-compose.yml:

version: "3.3"
services:
  my-project-frontend:
    build: "./my-project-frontend/"
    container_name: "front"
    restart: always
    depends_on:
      -  my-project-backend
      
   my-project-backend:
    build: "./my-project-backend/"
    container_name: "back"
    restart: always

并运行:

docker-compose up

或者

docker-compose build
于 2021-01-04T15:21:05.593 回答