如何在使用 Docker 开发环境时处理 Python 的 requirements.txt 文件?

4
假设我编写了一个名为docker-compose.dev.yml的文件,用于使用Docker设置Flask项目(Web应用程序)的开发环境。在docker-compose.dev.yml中,我设置了两个服务,一个用于数据库,另一个在调试模式下运行Flask应用程序(这使我能够进行热更改而无需重新创建/重启容器)。这使得开发团队中的每个人都可以非常轻松地使用相同的开发环境。但是,存在一个问题:显然,在开发应用程序时需要安装库,并将它们列在requirements.txt文件中(在Python的情况下)。为此,我只看到在使用Docker开发环境时有两种选择:
1. 进入运行Flask应用程序的容器的控制台,并使用pip install ...pip freeze > requirements.txt命令。 2. 手动将依赖项编写到requirements.txt文件中并重新构建容器。
第一种选项有点费力,而第二种选项有点“肮脏”。有没有比提到的两种选择更合适的选择?

1
你如何在没有Docker的情况下管理这些依赖关系?你能否在非Docker Python虚拟环境中进行日常开发,并将setup.cfgrequirements.txt文件与应用程序的其余部分一起跟踪存储在源代码控制中? - David Maze
为什么不再创建一个文件:requirements_dev.txt,并在其第一行添加:-r requirement.txt。然而,更好的解决方案是使用依赖/包管理工具,如poetrypip-tools - 就我个人而言,我更喜欢pip-tools。另一方面,poetry有一种更直接的方法来分离生产和开发/测试依赖。 - rocksteady
4个回答

5

对于这样的情况,我使用多层Docker镜像。

免责声明:以下示例未经过测试。请将其视为伪代码描述;)

作为一个非常简单的示例,这种方法可能是这样的:

# Make sure all layers are based on the same python version.
FROM python:3.10-slim-buster as base

# The actual dev/test image.
# This is where you can install additional dev/test requirements.
FROM base as test
COPY ./requirements_test.txt /code/requirements_test.txt
RUN python -m pip install --no-cache-dir --upgrade -r /code/requirements_test.txt

ENTRYPOINT ["python"]
# Assuming you run tests using pytest.
CMD ["-m", "pytest", "..."]

# The actual production image.
FROM base as runtime
COPY ./requirements.txt /code/requirements.txt
RUN python -m pip install --no-cache-dir --upgrade -r /code/requirements.txt

ENTRYPOINT ["python"]
# Assuming you wantto run main.py as a script.
CMD ["/path/to/main.py"]

假设有这样一个 requirements.txt 文件(仅为示例):

requests

假设有这样一个 requirements_test.txt 文件(仅为示例):

-r requirements.txt

pytest

在你的docker-compose.yml文件中,你只需要像这样传递--target参数(在多层Dockerfile中,例如:testruntime),格式如下(不完整):
services:
  service:
    build:
      context: .
      dockerfile: ./Dockerfile
      target: runtime  # or test for running tests
最后想说的一句话: 如我在评论中所提到的,更好的处理这种依赖关系的方法可能是使用像poetrypip-tools之类的工具 - 或者其他可用的工具。 更新2022-05-23: 如评论中所述,为了完整起见,因为这种方法可能接近于一个可能的解决方案(正如问题中所请求的那样):
一个示例无需等待的方法可能如下所示 - 假设容器有一个特定的名称(<containe_name>):
# This requires to mount the file 'requirements_dev.txt' into the container - as a volume.
docker exec -it <container_name> python -m pip install --upgrade -r requirements_dev.txt

这个命令会在正在运行的容器中安装新依赖项。

Dockerfile的“base”层甚至可以安装两个环境(目标)(“runtime”和“test”)共同使用的系统或Python依赖项。 - rocksteady
这是一种有趣的管理应用程序不同环境的方式,感谢提到 poetry,但我们回到了同样的问题:开发人员如何管理在开发过程中出现的新 Python 依赖项?值得再次注意我的主要目标:消除团队中其他开发人员需要在本地安装 Python 或数据库的需求。 - Tedpac
使用Docker可以避免其他开发人员安装Python或数据库的需要 - 我认为这里没有问题。我想这就是你选择Docker的原因。关于引入新依赖项的问题:在我看来,这正是开发环境的用途和我对开发人员的期望 - 在本地更改或添加requirements_test.txt文件中的依赖项,然后再次构建/启动镜像/容器即可。结果将包含更改内容。是否应将其添加到版本控制或在推送之前还原是另一个问题。 - rocksteady
1
一个fire-and-forget方法的例子可能是这样的 - 假设容器有一个特定的名称(<container_name>):# 这需要将文件'requirements_dev.txt'作为卷挂载到容器中。 docker exec -it pyton -m pip install --upgrade -r requirements_dev.txt这个命令只是在运行的容器中安装新的依赖项。 - rocksteady
你在上一条评论中提到的是我一直在寻找的最接近的方法。这种方法允许我使用单个命令安装库,并使用单个命令更新requirements.txt文件,就像在本地Python虚拟环境/venv中一样,此外,requirements.txt的更改还将在本地反映出来,因此我可以将其上传到存储库中。您能否在您的答复中包含该信息呢? - Tedpac
我能提供的另一个想法是将主机系统上的目录挂载到容器上,并执行复制指令。然后用户可以使用pip将安装的软件包转储到共享目录中的文本文件中。Docker容器旨在是短暂的。我所描述的做法更符合这个想法。 - VanBantam

1
如果目标是拥有一致的开发环境,我能想到的最安全的方法是构建一个基础镜像,其中包含更新的依赖项,并发布到私有仓库,以便您可以引用特定的标签,如app:v1.2。因此,Dockerfile可能如下所示:
FROM AppBase:v1.2
...

这意味着无需安装依赖项,可以更快地、更一致地设置开发环境。

这是一个有趣的想法,但一旦开发人员下载了镜像(例如 AppBase:v.1.2)并创建了容器,我们又回到了同样的问题:在开发过程中如何管理新的Python依赖项? - Tedpac
1
我明白了,很抱歉,我假设添加软件包的频率不是那么高,而是关注“可重复”的环境方面。如果容器用作开发环境,那么我可能会选择选项1,因为它是安全的,但使用一个包管理器(所以不是非常费力),比如poetry。这样开发人员就可以通过poetry add <package>添加软件包,该软件包将被添加到.toml文件中。然后,您可以在流水线中使用poetry.lock文件来发布基础镜像,以实现可重复性,对吧? - Anwar Husain

1
在容器外部挂载的卷中,使用虚拟环境安装要求。请注意,虚拟环境的创建和安装应该发生在容器运行时,而不是镜像构建时(因为没有挂载卷)。
假设您已经挂载(而不是复制!)了项目源代码,您可以将其保存在一个名为“./.venv”的文件夹中,这是一种相当标准的做法。
然后您可以像在本地一样工作:在首次设置项目时仅需执行一次安装,除非要求更改,否则无需重新安装要求,即使重新构建容器,您也可以保留虚拟环境,每次重启应用程序时都不需要重新安装要求等等。
只是不要期望虚拟环境可在容器外部使用,例如通过您的IDE(但使用“site”模块进行一些黑客攻击可以让您与计算机上的虚拟环境共享站点包)。
这是与通常在生产docker镜像中管理要求的方法非常不同的方法,那里的源代码和要求会在镜像构建时被复制和安装。因此,您可能需要为生产部署和本地开发编写两个非常不同的Dockerfile,就像您已经有不同的docker-compose.yml文件一样。
但是,如果您希望它们两个更加相似,请记住,在生产docker镜像中也使用虚拟环境没有任何问题,尽管当前不这样做的趋势。

我不确定我是否理解错误,但我想要避免的是需要在我的本地机器上创建一个virtualenv,因为这也会迫使其他开发人员在他们的机器上安装特定版本的Python。 - Tedpac
虚拟环境应该在容器内部创建!只需在外部挂载卷中。使用这种技术,所有开发人员都可以使用容器中的特定Python解释器。 - N1ngu
哦,我明白了。现在,开发人员将如何使用容器内的 virtualenv?他们是否可以在本地计算机上没有安装 Python 解释器的情况下使用 virtualenv - Tedpac
virtualenv 应该仅在容器内使用,替换所有解释器引用为已挂载的 /my/app/path/.venv/bin/python。为此,使用 pipenv 或 poetry 管理 venv 可以通过 pipenv|poetry run 子命令使你的生活更轻松。例如,要启动解释器:docker-compose run my-app pipenv run python。 - N1ngu

0
第二个选项通常在Python环境中使用。您只需将新软件包添加到requirements.txt中,然后重新启动容器,该容器具有在其dockerfile中带有pip install -r requirements.txt的行来执行安装。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接