了解多阶段Dockerfile的工作流程



当涉及到多阶段Dockerfile时,我很难理解一些过程。

以这个为例,我在下面有几个问题:

# Dockerfile
# Uses multi-stage builds requiring Docker 17.05 or higher
# See https://docs.docker.com/develop/develop-images/multistage-build/
# Creating a python base with shared environment variables
FROM python:3.8.1-slim as python-base
ENV PYTHONUNBUFFERED=1 
PYTHONDONTWRITEBYTECODE=1 
PIP_NO_CACHE_DIR=off 
PIP_DISABLE_PIP_VERSION_CHECK=on 
PIP_DEFAULT_TIMEOUT=100 
POETRY_HOME="/opt/poetry" 
POETRY_VIRTUALENVS_IN_PROJECT=true 
POETRY_NO_INTERACTION=1 
PYSETUP_PATH="/opt/pysetup" 
VENV_PATH="/opt/pysetup/.venv"
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"

# builder-base is used to build dependencies
FROM python-base as builder-base
RUN apt-get update 
&& apt-get install --no-install-recommends -y 
curl 
build-essential
# Install Poetry - respects $POETRY_VERSION & $POETRY_HOME
ENV POETRY_VERSION=1.0.5
RUN curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python
# We copy our Python requirements here to cache them
# and install only runtime deps using poetry
WORKDIR $PYSETUP_PATH
COPY ./poetry.lock ./pyproject.toml ./
RUN poetry install --no-dev  # respects 

# 'development' stage installs all dev deps and can be used to develop code.
# For example using docker-compose to mount local volume under /app
FROM python-base as development
ENV FASTAPI_ENV=development
# Copying poetry and venv into image
COPY --from=builder-base $POETRY_HOME $POETRY_HOME
COPY --from=builder-base $PYSETUP_PATH $PYSETUP_PATH
# Copying in our entrypoint
COPY ./docker/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
# venv already has runtime deps installed we get a quicker install
WORKDIR $PYSETUP_PATH
RUN poetry install
WORKDIR /app
COPY . .
EXPOSE 8000
ENTRYPOINT /docker-entrypoint.sh $0 $@
CMD ["uvicorn", "--reload", "--host=0.0.0.0", "--port=8000", "main:app"]

# 'lint' stage runs black and isort
# running in check mode means build will fail if any linting errors occur
FROM development AS lint
RUN black --config ./pyproject.toml --check app tests
RUN isort --settings-path ./pyproject.toml --recursive --check-only
CMD ["tail", "-f", "/dev/null"]

# 'test' stage runs our unit tests with pytest and
# coverage.  Build will fail if test coverage is under 95%
FROM development AS test
RUN coverage run --rcfile ./pyproject.toml -m pytest ./tests
RUN coverage report --fail-under 95

# 'production' stage uses the clean 'python-base' stage and copyies
# in only our runtime deps that were installed in the 'builder-base'
FROM python-base as production
ENV FASTAPI_ENV=production
COPY --from=builder-base $VENV_PATH $VENV_PATH
COPY ./docker/gunicorn_conf.py /gunicorn_conf.py
COPY ./docker/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
COPY ./app /app
WORKDIR /app
ENTRYPOINT /docker-entrypoint.sh $0 $@
CMD [ "gunicorn", "--worker-class uvicorn.workers.UvicornWorker", "--config /gunicorn_conf.py", "main:app"]

我的问题:

  1. 您是将整个映像docker build ...,然后仅将docker run ... --target=<stage>用于运行特定阶段(developmenttestlintproduction等),还是仅构建并运行所需的特定阶段(例如docker build ... -t test --target=test && docker run test ...)?

    我想说的不是前者,因为你最终会得到一个带有构建工具包的臃肿图像,而不是。。。对的

  2. 当涉及到本地Kubernetes开发(minikubeskaffolddevspace等)和运行单元测试时,您应该参考Dockerfile中的这些阶段(devspace挂钩或其他什么),还是使用容器中的本地测试工具(例如npm test./manage.py test等)?

感谢您澄清这些问题。

从一个不那么DevSpace-y的视角和一个更通用的Docker-y视角回答(不要不尊重Lukas!):

问题1

细分

❌你是码头工人吗。。。这整个图像,然后只是docker运行--target=运行特定阶段的

您的理解很接近,并设法在查询的第二部分中概述了方法:

✅还是只构建和运行您需要的特定阶段(例如docker构建…-t测试--target=test&&docker运行测试…)?

docker run命令中不存在--target选项,在调用docker run --help时可以看到该选项。

我想说它不是前者,因为你最终会得到一个带有构建工具包的臃肿图像,而不是。。。对的

是的,不可能用第一种方式,因为如果没有指定--target,那么只有最后一个阶段会被合并到您的图像中。这是一个很大的好处,因为它减少了容器的最终大小,同时允许您使用多个指令。

详细信息和示例

这是一个可以在构建时传递的标志,这样您就可以选择具体构建哪些层。这是一个非常有用的指令,可以用几种不同的方式使用。这里有一篇不错的博客文章,讨论了多阶段构建(--target就是其中之一)的新功能

例如,我在利用不同阶段和目标的CI中成功地构建了大量项目,以下是伪代码,但希望上下文能应用

# Dockerfile
FROM python as base
FROM base as dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
FROM dependencies as test
COPY src/ src/
COPY test/ test/
FROM dependencies as publish
COPY src/ src/
CMD ...

像这样的Dockerfile可以让你在CI工作流中做这样的事情,同样,伪代码式的

docker build . -t my-app:unit-test --target test
docker run my-app:unit-test pyunit ...
docker build . -t my-app:latest
docker push ...

在某些情况下,对何时构建的内容进行细粒度控制可能非常有利,并且能够在不构建整个应用程序的情况下运行那些只包含几个阶段的图像也是一件好事。

这里的关键是,不期望您需要使用--target,但它可以用于解决特定问题。

问题2

当涉及到本地Kubernetes开发(minikube、skaffold、devspace等)和运行单元测试时,你应该在Dockerfile中参考这些阶段(devspace Hooks或其他什么),还是在容器中使用本地测试工具(如npm测试、./manage.py测试等)?

Lukas很好地涵盖了特定于开发空间的方法,但最终您可以随心所欲地进行测试。使用devspace使测试更容易运行(并记住运行)听起来当然是个好主意。无论你使用什么工具来实现更简单的工作流程,都可能会在后台使用npm test等。

如果你想在容器外调用npm test,那没关系,如果你想把它放在容器中调用,那也没关系。你的问题的解决方案总是会随着你的情况而变化。CICD有助于对外部因素进行标准化,并提供统一的方法来确保执行测试,并且部署是可审计的

希望以任何形式或形式提供帮助👍

从Reddit复制我对此的回应,以帮助其他可能在StackOverflow上查找此内容的人:

此处为DevSpace维护人员。对于我的工作流(以及默认的DevSpace行为,如果你用devspace init设置的话),在开发过程中会跳过图像构建,因为它往往是工作流中最烦人、最耗时的部分。相反,大多数使用DevSpace的团队都会将开发映像推送到注册表中,并由CI/CD构建,然后在devspace.yaml中使用replacePods.replaceImage,如下所示:https://devspace.sh/cli/docs/configuration/development/replace-pods

这意味着,您的清单或舵图正在参考产品映像进行部署(应该是这样),然后devspace将(在部署后)用交付所有工具的开发优化映像替换您的pod映像。在这些pod中,您可以使用终端构建应用程序,运行测试以及集群中运行的其他依赖项等。

然而,通常团队也会在一段时间后开始在CI/CD中使用DevSpace,然后添加profiles(例如prod配置文件或integration-testing配置文件等)https://devspace.sh/cli/docs/configuration/profiles/basics)在CCD_ 30中,他们再次添加图像构建,因为他们希望使用kaniko或docker在管道中构建图像。为此,您还需要在devspace.yaml中指定构建目标:https://devspace.sh/cli/docs/configuration/images/docker#target

关于1的FWIW:我从不使用docker run --target,但我也总是直接通过手动docker命令使用Kubernetes来运行任何工作负载。

最新更新