如何在Dockerfile中优雅地激活virtualenv

Joe 于 2023-12-06 发布

背景

当你打算将 Python 应用程序打包到 Docker 映像中时,您通常会使用virtualenv.

debian:12-slim-bookworm作为基础镜像的时候,进行pip install xxx操作时,CLI会出现以下提示:

error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.
    
    See /usr/share/doc/python3.11/README.venv for more information.

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

这里就会引出Python3virtualenv机制,具体介绍可以自行查阅相关文档。

Linux的shell模式下启用virtualenv

假如在shell命令行的模式下,我们可以在linux 虚拟机状态下面,创建venv环境。

通过运行以下命令确保venv已安装:

sudo apt install python3-venv

要在/opt/venv的目录中创建新的虚拟环境venv,请运行:

python3 -m venv /opt/venv
root@debian:/opt/venv# ls
bin  include  lib  lib64  pyvenv.cfg

要激活此虚拟环境(修改PATH环境变量),请运行以下命令:

source /opt/venv/bin/activate

现在您可以像在这个/opt/venv虚拟环境中一样安装库requests

pip install requests

这些文件将安装在该//opt/venv/目录下。

如果你想离开虚拟环境,可以运行:

deactivate

如果您不想运行source env/bin/activateand deactivate,则可以通过为其路径添加前缀来运行可执行文件,如下所示:

 $ /opt/venv/bin/pip install requests
 $ /opt/venv/bin/python3
 >>> import request
 >>> help(requests)

写进成Dockerfile形式

如果你只是盲目地将 Linux shell 命令行粘贴进 Dockerfile里面,virtualenv并不会像你想象的那样在container里生效。

FROM python:3.11.7-slim-bookworm
RUN python3 -m venv /opt/venv

# This is a wrong usage
RUN . /opt/venv/bin/activate

# Install dependencies:
COPY requirements.txt .
RUN pip install -r requirements.txt

# Run the application:
COPY app.py .
CMD ["python", "app.py"]

之所以说以上的用法是错误的是因为2个原因:

现在在知道什么原因的情况下:

方案一

显式写出virtualenv的绝对路径

显式使用 virtualenv 中二进制文件的路径

FROM python:3.11.7-slim-bookworm

RUN python3 -m venv /opt/venv

# Install dependencies:
COPY requirements.txt .
RUN /opt/venv/bin/pip install -r requirements.txt

# Run the application:
COPY app.py .
CMD ["/opt/venv/bin/python", "app.py"]

以上的写法在大部分情况下都是可行,但是唯一需要注意的是如果任何 Python 进程启动子进程,该子进程将不会virtualenv 中运行。

方案二

改进版

通过实际分别激活每个虚拟环境RUN以及以下来解决CMD命令中任何python进程启动的子进程,无法在virtualenv中运行的问题。

FROM python:3.11.7-slim-bookworm

RUN python3 -m venv /opt/venv

# Install dependencies:
COPY requirements.txt .
RUN . /opt/venv/bin/activate && pip install -r requirements.txt

# Run the application:
COPY app.py .
CMD . /opt/venv/bin/activate && exec python app.py

以上这种写法中exec的的作用是用于获得正确的信号处理,保证CMD这个命令是同一个进程,而不是启动子进程,能够接收docker传过来的SIGTERM信号。

最终方案

优雅方式

查阅下virtualenv 文档 中相关内容

如果你去阅读activate的部分代码,它会做很多事情:

可以看到以上这些内容,最重要的是设置PATHPATH是搜索要运行的命令的目录列表。 activate只需将 virtualenvbin/目录添加到列表的开头即可,python 会根据PATH查找相应的依赖和可执行二进制文件。

所以我们在写Dockerfile的时候,我们可以activate转化为通过设置适当的环境变量来替换:Docker 的ENV命令适用于后续的RUN命令 以及CMD.

FROM python:3.11.7-slim-bookworm

ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

# Install dependencies:
COPY requirements.txt .
RUN pip install -r requirements.txt

# Run the application:
COPY app.py .
CMD ["python", "app.py"]

以上virtualenv现在都会在后续的RUNCMD命令生效。

参考文档

https://pythonspeed.com/articles/activate-virtualenv-dockerfile/

https://askubuntu.com/questions/1465218/pip-error-on-ubuntu-externally-managed-environment-%C3%97-this-environment-is-extern