背景
当你打算将 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.
这里就会引出Python3的virtualenv机制,具体介绍可以自行查阅相关文档。
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个原因:
Dockerfile中的每一行RUN都是一个不同的进程。在单独的RUN中运行activate对后续的RUN调用没有影响;从实际目的来看,它是一个无效的命令。- 当您运行生成的
Docker镜像时,它将运行CMD命令,但这也不会在虚拟环境venv中运行,因为它也不受RUN进程的影响。
现在在知道什么原因的情况下:
方案一
显式写出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的部分代码,它会做很多事情:
- 确定你正在使用的
shell。 - 向你的
shell添加一个deactivate函数,并且对pydoc进行一些操作。 - 更改
shell提示符以包含虚拟环境的名称。 - 如果设置了
PYTHONHOME环境变量,它会取消设置。 - 设置两个环境变量:
VIRTUAL_ENV和PATH。
可以看到以上这些内容,最重要的是设置PATH:PATH是搜索要运行的命令的目录列表。 activate只需将 virtualenv 的bin/目录添加到列表的开头即可,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现在都会在后续的RUN和CMD命令生效。
参考文档
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