使用 PyInstaller 可以将 Python 项目打包为自带环境的可执行文件。通过 Dockerfile 构建一个打包镜像,避免可执行文件在较旧版本的系统上执行时报错。
使用 PyInstaller
PyInstaller 使用起来非常简单:
$ mamba activate build-env
$ mamba install -y pyinstaller
$ pyinstaller my_script.py
$ pyinstaller --onefile my_script.py
|
执行打包命令后会在当前目录下创建两个目录和一个文件:
| 名称 |
内容 |
build/ |
临时构建文件 |
dist/ |
最终可执行文件或应用目录 |
*.spec |
打包配置文件 |
我们需要的可执行文件位于 dist 目录下。
GLIBC 问题
GLIBC(GNU C Library)是 Linux 系统中最核心的运行时库,它为程序提供文件读写、内存管理、进程控制、网络通信等基础系统接口,并负责程序与 Linux 内核之间的交互。
当 PyInstaller 打包时系统的 glibc 版本比服务器上高,PyInstaller 生成的可执行文件可能依赖高版本 glibc,导致打包得到的二进制文件在服务器上运行时由于 glibc 版本报错。
为了解决 GLIBC 版本问题,需要在与服务器系统版本一致或接近的环境中打包,使用 Docker 可以很方便得到一个这样的环境。
容器内交互式打包
启动一个与服务器的系统版本一致或接近的容器,确保容器内的 glibc 版本与服务器一致,安装 Python 环境并使用 PyInstaller 打包:
$ docker run -dit --name=centos7-builder centos:7.9.2009 tail -f /dev/null
$ docker exec -it centos7-builder bash
$ ldd --version ldd (GNU lilbc) 2.17 ...
...
|
打包完成后,在宿主机上执行命令,将容器内的打包产物复制出来:
$ docker cp centos7-builder:/root/my_py_prj/dist ./
|
构建打包镜像
miniforge 基础镜像
该镜像以 centos:7.9.2009 为基础,安装了 Miniforge 并将 conda 命令配置到环境变量中,为构建项目专用打包环境提供一个基础镜像。
在线构建会联网从 GitHub 下载 Miniforge 安装器,请确保当前网络能够访问 GitHub 或传入一个代理地址:
DockerfileARG HTTPS_PROXY=""
FROM centos:7.9.2009
ARG HTTPS_PROXY
ENV CONDA_DIR=/opt/conda ENV PATH=$CONDA_DIR/bin:$PATH
RUN set -ex && \ sed -i 's/^mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*.repo && \ sed -i 's|^#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*.repo && \ if [ -n "$HTTPS_PROXY" ]; then \ https_proxy="$HTTPS_PROXY" curl -fsSL -o /tmp/Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"; \ else \ curl -fsSL -o /tmp/Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"; \ fi && \ bash /tmp/Miniforge3.sh -b -p $CONDA_DIR && \ rm /tmp/Miniforge3.sh && \ conda clean --all -y
WORKDIR /workspace
|
离线构建需要从 miniforge-releases 提前下载好对应架构的 Miniforge 安装器放在 Dockerfile 同级目录:
DockerfileFROM centos:7.9.2009
ENV CONDA_DIR=/opt/conda ENV PATH=$CONDA_DIR/bin:$PATH
COPY Miniforge3-Linux-x86_64.sh /tmp/Miniforge3.sh
RUN set -ex && \ sed -i 's/^mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*.repo && \ sed -i 's|^#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*.repo && \ bash /tmp/Miniforge3.sh -b -p $CONDA_DIR && \ rm /tmp/Miniforge3.sh && \ conda clean --all -y
WORKDIR /workspace
|
构建 miniforge 镜像:
$ docker build -t miniforge:centos7 .
$ docker build --build-arg HTTPS_PROXY=$https_proxy -t miniforge:centos7 .
|
测试能否使用 conda 命令:
$ docker run --rm miniforge:centos7 conda --version conda 26.3.2
|
项目专用打包镜像
当 Python 项目使用的模块不再频繁变动,可以考虑构建项目专用打包镜像。
由于 miniforge:centos7 镜像中不含 Python 解释器、项目所需模块以及 PyInstaller,构建专用打包镜像时需要将这些安装进来,并通过 ENTRYPOINT 脚本接收通过命令行传入的 pyinstaller 打包命令。
因此需要将入口脚本 entrypoint.sh 和项目的 environment.yml 放到 Dockerfile 同级目录。
Dockerfile 内容:
DockerfileFROM miniforge:centos7
ENV ENV_NAME="build-env"
COPY environment.yml /tmp/environment.yml
RUN set -eux && \ conda env create -n $ENV_NAME -f /tmp/environment.yml && \ conda run -n $ENV_NAME pip install pyinstaller && \ conda clean --all -y
COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
|
入口脚本:
entrypoint.sh#!/bin/bash set -e
exec conda run -n build-env "$@"
|
环境信息:
environment.ymlname: ranger-manager channels: - conda-forge dependencies: - openpyxl - pandas - python=3.12 - pyyaml - pip: - StrEnum==0.4.15 - apache-ranger==0.0.12
|
构建 ranger-manager-builder 镜像:
$ docker build -t ranger-manager-builder:centos7 .
|
使用该镜像进行打包:
docker run --rm \ -u $(id -u):$(id -g) \ -v $(pwd):/workspace \ ranger-manager-builder:centos7 \ pyinstaller main.py --distpath ./output
|
参考资料
CI 管线中安装 miniforge
PyInstaller 的功能和工作原理