【问题标题】:How can I keep all artifacts when building multi-stage Dockerfiles with docker-compose?使用 docker-compose 构建多阶段 Dockerfile 时如何保留所有工件?
【发布时间】:2021-05-28 20:09:43
【问题描述】:

我正在开发一个使用多阶段 Dockerfile 的项目。这适用于 CircleCI,但我对 Docker 不是很有经验,所以我在本地运行时遇到了麻烦。来自文档:

第二个 FROM 指令开始一个新的构建阶段 alpine:latest 图像作为其基础。 COPY --from=0 行仅复制 上一阶段的建好的神器进入了这个新阶段。围棋 SDK 和任何中间工件都被留下,而不是保存在 最终图像。

如果我理解正确,这意味着如果我在第 1 阶段安装了一组依赖项,在第 2 阶段安装了另一组,那么在我运行 docker-compose up 之后,我只会看到安装了一些依赖项。这就是当地正在发生的事情。但是,为了附加到容器并运行开发服务器,我需要安装 all 项目依赖项。我在这里读到可以在构建命令中使用--rm=false 参数(我不确定这是否指的是docker builddocker-compose build,或者是否有任何区别)。我尝试将 rm 设置添加到我的 docker-compose 但显然它不存在。

version: "3.8"
services:
    app:
        container_name: my.container
        build:
            context: .
            dockerfile: Dockerfile.dev
            rm: false
        entrypoint: sh -c
        command: tail -f /dev/null
        stdin_open: true
        tty: true
    app2:
        ...

我的 Dockerfile 大致如下:

ARG PYTHON_VERSION=3.8-alpine3.12

FROM python:$PYTHON_VERSION AS base
ARG USER=myuser

# Install application run-time dependencies.
RUN addgroup -S ${USER} && \
    adduser -S -g ${USER} ${USER} && \
    apk add --no-cache \
            --upgrade \
        bash && \
    rm -rf /var/cache/apk/*

FROM base AS builder

RUN mkdir /install
RUN mkdir /e2e_install
WORKDIR /install

# Install application build-time dependencies.
RUN apk update && \
    apk add --no-cache --upgrade \
    bash \
    build-base \
    libffi-dev \
    pcre-dev \
    linux-headers \
    zlib-dev \
    python3-dev \
    openssh \
    openssh-keygen \
    git \
    gcc \
    musl-dev && \
    rm -rf /var/cache/apk/*

# Install project and test dependencies.
RUN pip install --no-cache-dir --disable-pip-version-check pip==20.0.2

COPY requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir \
    --no-warn-script-location \
    --prefix=/install \
    --disable-pip-version-check -r /tmp/requirements.txt

COPY test_requirements.txt /tmp/test_requirements.txt
RUN pip install --no-cache-dir \
    --ignore-installed \
    --no-warn-script-location \
    --prefix=/install \
    --disable-pip-version-check -r /tmp/test_requirements.txt

# install e2e-test dependencies
COPY e2e/requirements.txt /tmp/e2e_test_requirements.txt
RUN cat /tmp/e2e_test_requirements.txt
RUN pip install --no-cache-dir \
                --ignore-installed \
                --disable-pip-version-check \
                --no-warn-script-location \
                --prefix=/e2e_install \
                -r /tmp/e2e_test_requirements.txt

# Tag version number into source code.
ARG VERSION_NUMBER=unversioned
WORKDIR /source
COPY . .
RUN sed -i -e "s/__version__ = \"unversioned\"/__version__ = \"${VERSION_NUMBER}\"/" external_api/__init__.py

# -------------------- e2e Image --------------------
FROM base as e2e-test
ARG USER=myuser

COPY --from=builder /e2e_install /usr/local

WORKDIR /opt/company/myapp
COPY --from=builder /source/e2e ./e2e
COPY --from=builder /source/scripts/entry_point.sh .

# Set file owner group,
RUN chown -R ${USER}:${USER} /opt/company
ENTRYPOINT ["./entry_point.sh"]

# -------------------- Production Image --------------------
FROM base
ARG USER=myuser

# Copy necessary files from builder.
WORKDIR /opt/company
COPY --from=builder /install /usr/local
COPY --from=builder /source .

# Set file owner group,
RUN chown -R ${USER}:${USER} /opt/company

# Run unit tests.
RUN PYTHONPATH=. pytest tests/unit_test --cov=. --cov-report=xml

COPY ./scripts/entry_point.sh .
ENTRYPOINT ["./entry_point.sh"]

当我运行docker-compose build 时,我得到了一堆这些:

Step 20/26 : ARG VERSION_NUMBER=unversioned
 ---> Running in 1ce3e682b483
Removing intermediate container 1ce3e682b483
 ---> dcc4d4b6b4b5
Step 21/26 : WORKDIR /source
 ---> Running in c8d9c1349305
Removing intermediate container c8d9c1349305

当我附加到容器并尝试运行 Django 开发服务器时,它不起作用,因为未安装依赖项(即使 docker-compose build 输出显示它们已安装)。我想这是因为“中间容器”正在被删除。我试图将多阶段 Dockerfile 转换为单阶段文件,但我仍然不断收到“正在删除中间容器”消息,并且依赖项并未全部安装。我是否必须在单个 RUN 指令中安装所有依赖项?这是我进入容器时安装所有依赖项的唯一方法吗?我有没有办法保持这个 Dockerfile 完整,并以某种方式告诉 docker-compose 在构建时停止“留下中间工件”?我是否正确使用了“神器”这个词?我找不到明确的解释,也不确定我完全理解什么是“docker artifact”。

【问题讨论】:

  • 日常开发可以使用普通的Python虚拟环境吗?
  • 我正在做的项目使用微服务架构。涉及到 4 个独立的 Python 项目,它们都必须相互通信,其中 2 个有自己的数据库,其他依赖项如 redis、rabbitmq。在本地安装所有这些东西是不可能的。
  • 您应该能够将--no-rm 应用于docker-compose build 命令本身。您是正确的,在 Compose 文件中似乎无法执行此操作。
  • 我认为你有 2 个选择。我不确定哪个更可取,我也没有尝试过。首先,确定哪个中间容器包含您需要的依赖项,并且您的 Python 脚本不会被 COPY ---from 命令移动。编辑 Compose 文件以按 ID 引用 intermediate 容器图像。其次,将多阶段的 Dockerfile 合并为一个阶段。正如您所写,多阶段的目的通常是清除运行时不需要的构建|开发依赖项,因此这种方法可以为您提供一切。
  • 第二个可能是更好的选择。它还为您提供了两种我认为有用的图像。包含所有内容的开发人员|开发映像和不包含所有内容的运行时|最终用户映像。

标签: docker docker-compose dockerfile


【解决方案1】:

在我看来,您需要有单独的 docker-compose 服务,每个图像一个。您说涉及 4 个独立的 Python 项目,所以听起来应该(至少)在您的 docker-compose.yaml 中定义 4 个服务。

因此,与其在一个 Dockerfile 中定义所有服务,我希望每个服务都有其自己的 Dockerfile,并在您的docker-compose.yaml 中单独定义。

version: "3.7"

services:

  e2e:
    - build ./e2e
    ...
  production:
    - build ./production
    ...

您说您的所有服务都必须相互通信。 Docker Compose 使这变得非常容易,例如,production 镜像可以使用 URI e2e 访问e2e 镜像。 (例如curl e2e)。

【讨论】:

  • 抱歉,我与单个服务共享了我的 docker-compose 的最小版本,但实际上我有很多服务,每个 Python 应用程序(+ postgres、redit 等)都有一个服务,它们每个人都有自己的 Dockerfile。问题是这些 Dockerfile 中的每一个都是多阶段的,而当我构建后进入容器时,依赖项不存在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-16
  • 2019-03-12
  • 2020-12-19
  • 2023-04-06
  • 1970-01-01
相关资源
最近更新 更多