This commit is contained in:
2026-09-12 00:28:46 +08:00
parent 4fb13a55ed
commit 210dee8803
+87 -82
View File
@@ -1,34 +1,45 @@
# ============================================================ # ============================================================
# 基础镜像参数 # Dockerfile:多阶段构建 Python 应用镜像
#
# 构建思路:
# - builder 阶段:安装编译工具链,编译并安装 Python 依赖
# - runtime 阶段:只保留运行期必需内容,丢弃编译器
#
# 通过 --build-arg 动态注入以下变量,便于在 CI 中复用同一份 Dockerfile:
# VAR_BASE_IMAGE 基础镜像(如 python:3.12-slim、自定义的带 esxi 用户的镜像)
# VAR_PYTHON_PREFIX Python 安装前缀(依赖安装位置)
# VAR_PYFILE_PATH 项目源码目录(相对构建上下文)
# VAR_PROFILES 应用 profile(dev / prod ...)
# VAR_PORT 应用监听端口
# VAR_PYFILE 启动入口文件名(不含 .py)
# ============================================================ # ============================================================
# VAR_BASE_IMAGE 通过 docker build --build-arg 传入,
# 例如:--build-arg VAR_BASE_IMAGE=python:3.12-slim # ------------------------------------------------------------
# 用 ARG 而不是直接 FROM xxx,方便在 CI 中动态切换基础镜像 # 声明构建参数
# 基础镜像用 ARG 而不是直接 FROM,方便 CI 动态切换
# Python 前缀单独抽出,避免在 COPY / pip --prefix 里重复硬编码
# ------------------------------------------------------------
ARG VAR_BASE_IMAGE ARG VAR_BASE_IMAGE
ARG VAR_PYTHON_PREFIX=/usr/local/python3.12.8
# ============================================================ # ============================================================
# 阶段一:builder —— 负责编译依赖 # 阶段一:builder —— 编译并安装 Python 依赖
# 说明:
# - 这一阶段会安装 gcc/make/cmake 等编译工具
# - 编译好的 Python 依赖会安装到 Python 实际 prefix
# (即 /usr/local/python3.12.8)
# - 最终该阶段整体被丢弃,编译工具不会进入 runtime 镜像
# ============================================================ # ============================================================
FROM ${VAR_BASE_IMAGE} AS builder FROM ${VAR_BASE_IMAGE} AS builder
# 基础镜像末尾设置了 USER esxi,这里切回 root, # 基础镜像末尾可能设置了非 root 用户(如 esxi),
# 否则下面的 yum install 会因权限不足而失败 # 这里切回 root,否则 yum install 会因权限不足失败
USER root USER root
# 镜像作者信息(仅元数据,不影响体积) # 镜像作者信息(仅元数据)
LABEL MAINTAINER="djangoadmin@126.com" LABEL MAINTAINER="djangoadmin@126.com"
# ------------------------------------------------------------ # ------------------------------------------------------------
# 编译阶段的环境变量 # 编译阶段环境变量
# PYTHONUNBUFFERED=1 : Python 输出不缓冲,日志实时可见 # PYTHONUNBUFFERED=1 Python 输出不缓冲,日志实时可见
# PYTHONDONTWRITEBYTECODE=1 : 不生成 .pyc,避免污染构建产物 # PYTHONDONTWRITEBYTECODE=1 不生成 .pyc,避免污染构建产物
# PIP_NO_CACHE_DIR=1 : pip 不缓存 wheel,减小体积 # PIP_NO_CACHE_DIR=1 pip 不缓存 wheel,减小体积
# PIP_DISABLE_PIP_VERSION_CHECK=1 : 关闭 pip 版本检查,加快安装 # PIP_DISABLE_PIP_VERSION_CHECK=1 关闭 pip 版本检查,加快安装
# ------------------------------------------------------------ # ------------------------------------------------------------
ENV PYTHONUNBUFFERED=1 \ ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \ PYTHONDONTWRITEBYTECODE=1 \
@@ -36,18 +47,24 @@ ENV PYTHONUNBUFFERED=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_DISABLE_PIP_VERSION_CHECK=1
# ------------------------------------------------------------ # ------------------------------------------------------------
# 安装编译所需工具链 # 安装编译工具链与开发头文件
# yum-utils / net-tools : 系统工具 # yum-utils / net-tools 基础系统工具
# 末尾清理 yum 缓存,避免缓存被打进镜像层 # gcc / gcc-c++ C/C++ 编译器(编译 C 扩展必需)
# make / cmake 构建工具
# openssl-devel 编译 _ssl / cryptography
# bzip2-devel 编译 _bz2
# libffi-devel 编译 _ctypes
# mysql-devel 编译 mysqlclient 等 MySQL 驱动
# 末尾清理 yum 缓存,避免缓存进入镜像层
# ------------------------------------------------------------ # ------------------------------------------------------------
#RUN yum install -y yum-utils net-tools && \ RUN yum install -y yum-utils net-tools && \
# yum clean all && rm -rf /var/cache/yum yum install -y gcc gcc-c++ make cmake \
openssl-devel bzip2-devel libffi-devel mysql-devel && \
yum clean all && rm -rf /var/cache/yum
# ------------------------------------------------------------ # ------------------------------------------------------------
# 配置 pip 国内镜像源并升级 pip # 配置 pip 国内镜像并升级 pip / setuptools / wheel
# - global.index-url : 指定阿里云 PyPI 镜像 # 老版本的 pip 解析依赖容易出错,先升级到最新
# - install.trusted-host : 允许 http 源的 host
# - 升级 pip / setuptools / wheel,避免老版本解析依赖出错
# ------------------------------------------------------------ # ------------------------------------------------------------
RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple && \ RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple && \
pip3 config set install.trusted-host mirrors.aliyun.com && \ pip3 config set install.trusted-host mirrors.aliyun.com && \
@@ -55,116 +72,104 @@ RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple && \
# ------------------------------------------------------------ # ------------------------------------------------------------
# 拷贝依赖清单 # 拷贝依赖清单
# VAR_PYFILE_PATH 由 --build-arg 传入,指向项目目录 # 只拷贝 requirements.txt 而不是整个项目,
# 这里只拷贝 requirements.txt,而不是整个项目目录, # 这样源码变化但 requirements 未变时可以复用 pip install 层缓存
# 目的是:当项目源码变化但 requirements.txt 未变时,
# 可以复用 pip install 这一层的缓存,加快构建
# ------------------------------------------------------------ # ------------------------------------------------------------
ARG VAR_PYFILE_PATH ARG VAR_PYFILE_PATH
WORKDIR /opt/apps WORKDIR /opt/apps
COPY ${VAR_PYFILE_PATH}/requirements.txt ./ COPY ${VAR_PYFILE_PATH}/requirements.txt ./
# ------------------------------------------------------------ # ------------------------------------------------------------
# 将依赖安装到 Python 实际的 prefix # 将依赖安装到 Python 的 prefix 下
# --prefix=/usr/local/python3.12.8 : 基础镜像中 Python 的安装前缀 # --prefix=${VAR_PYTHON_PREFIX} 与 runtime 阶段的拷贝路径保持一致
# 用 python3 -m pip 保证使用同一个 Python 解释器 # python3 -m pip 确保使用同一个解释器
# uwsgi 单独安装(生产环境 WSGI 服务器) # uwsgi 生产环境 WSGI 服务器,单独安装
# 注意:末尾不要多写反斜杠,否则会把下一行注释拼进来导致语法错误 # 注意:末尾不要多写反斜杠,否则会把下一行注释拼进来导致语法错误
# ------------------------------------------------------------ # ------------------------------------------------------------
RUN python3 -m pip install --prefix=/usr/local/python3.12.8 -r requirements.txt && \ RUN python3 -m pip install --prefix=${VAR_PYTHON_PREFIX} -r requirements.txt && \
python3 -m pip install --prefix=/usr/local/python3.12.8 uwsgi python3 -m pip install --prefix=${VAR_PYTHON_PREFIX} uwsgi
# ============================================================ # ============================================================
# 阶段二:runtime —— 只保留运行时内容 # 阶段二:runtime —— 只保留运行时内容
# 说明:
# - 不再安装 gcc/make/cmake 等编译工具
# - 只装运行时需要的动态库
# - 从 builder 拷贝编译好的 Python 及其依赖
# ============================================================ # ============================================================
FROM ${VAR_BASE_IMAGE} AS runtime FROM ${VAR_BASE_IMAGE} AS runtime
# 基础镜像末尾设置了 USER esxi,这里切回 root, # 切回 root 以便 yum install / chown
# 否则下面的 yum install 会因权限不足而失败
USER root USER root
# 镜像作者信息 # 镜像作者信息
LABEL MAINTAINER="djangoadmin@126.com" LABEL MAINTAINER="djangoadmin@126.com"
# ------------------------------------------------------------ # ------------------------------------------------------------
# 运行时环境变量 # 运行期环境变量
# PYTHONUNBUFFERED=1 : 日志实时输出,便于 docker logs 观察 # PYTHONUNBUFFERED=1 日志实时输出,便于 docker logs 观察
# PYTHONDONTWRITEBYTECODE=1 : 不写 .pyc,避免容器内产生垃圾文件 # PYTHONDONTWRITEBYTECODE=1 不写 .pyc,避免容器内产生垃圾文件
# ------------------------------------------------------------ # ------------------------------------------------------------
ENV PYTHONUNBUFFERED=1 \ ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 PYTHONDONTWRITEBYTECODE=1
# ------------------------------------------------------------ # ------------------------------------------------------------
# 运行期参数(由 --build-arg 传入) # 运行期构建参数
# VAR_PROFILES : 配置 profile(如 dev / prod) # VAR_PYTHON_PREFIX Python 前缀(必须与 builder 保持一致)
# VAR_PORT : 应用监听端口 # VAR_PROFILES 配置 profile(dev / prod)
# VAR_PYFILE : 启动入口文件名(不含 .py) # VAR_PORT 应用监听端口
# VAR_PYFILE_PATH : 项目源码目录 # VAR_PYFILE 启动入口文件名(不含 .py)
# VAR_PYFILE_PATH 项目源码目录
# ------------------------------------------------------------ # ------------------------------------------------------------
ARG VAR_PYTHON_PREFIX=/usr/local/python3.12.8
ARG VAR_PROFILES ARG VAR_PROFILES
ARG VAR_PORT ARG VAR_PORT
ARG VAR_PYFILE ARG VAR_PYFILE
ARG VAR_PYFILE_PATH ARG VAR_PYFILE_PATH
# ------------------------------------------------------------ # ------------------------------------------------------------
# 把 ARG 转成 ENV,这样在容器运行期也能读取到这些变量 # 将 ARG 转成 ENV,运行期可通过环境变量读取
# PORT : 供 EXPOSE 和 CMD 使用 # PORT 供 EXPOSE 与 CMD 使用
# PYFILE : 入口文件名 # PYFILE 入口文件名
# PROFILES : 应用读取的 profile # PROFILES 应用读取的 profile
# ------------------------------------------------------------ # ------------------------------------------------------------
ENV PORT=${VAR_PORT} \ ENV PORT=${VAR_PORT} \
PYFILE=${VAR_PYFILE} \ PYFILE=${VAR_PYFILE} \
PROFILES=${VAR_PROFILES} PROFILES=${VAR_PROFILES}
# ------------------------------------------------------------ # ------------------------------------------------------------
# 只安装运行期需要的动态库(不再需要 gcc/make/cmake 等) # 安装运行期依赖
# net-tools : netstat 等网络排查工具 # 只装运行库,不装 -devel 头文件,可显著减小镜像体积:
# openssl-devel : 若应用运行期需要 SSL 头文件可保留; # net-tools netstat 等网络排查工具(如不需要可去掉)
# 若纯粹运行,可改为 openssl-libs 更省体积 # openssl-libs SSL 运行库(替代 openssl-devel)
# bzip2-devel : 同上,运行期一般只需 bzip2-libs # bzip2-libs bz2 运行库(替代 bzip2-devel)
# libffi-devel : 运行期一般只需 libffi # libffi ctypes 运行库(替代 libffi-devel)
# mysql-devel : 运行期一般只需 mysql-libs # mysql-libs MySQL 客户端运行库(替代 mysql-devel)
# 提示:如果确定运行时不需要 -devel 头文件,可替换为对应的 -libs 包, # 注意:若应用运行期确实需要头文件(如运行期编译),再改回 -devel
# 可进一步减小镜像体积
# ------------------------------------------------------------ # ------------------------------------------------------------
RUN yum install -y net-tools openssl-devel bzip2-devel libffi-devel mysql-devel && \ RUN yum install -y net-tools openssl-libs bzip2-libs libffi mysql-libs && \
yum clean all && rm -rf /var/cache/yum yum clean all && rm -rf /var/cache/yum
# ------------------------------------------------------------ # ------------------------------------------------------------
# 从 builder 阶段拷贝 Python 及其已安装的依赖 # 从 builder 拷贝已编译安装好的 Python 及依赖
# 源和目标都使用 Python 的实际 prefix, # 源与目标都使用 ${VAR_PYTHON_PREFIX},保证 runtime 的 Python
# 保证 runtime 的 Python 能在自己的 site-packages 里找到 Django 等包 # 能在自己的 site-packages 里找到 Django 等包
# ------------------------------------------------------------ # ------------------------------------------------------------
COPY --from=builder /usr/local/python3.12.8 /usr/local/python3.12.8 COPY --from=builder ${VAR_PYTHON_PREFIX} ${VAR_PYTHON_PREFIX}
# ------------------------------------------------------------ # ------------------------------------------------------------
# 创建工作区 # 工作区
# ------------------------------------------------------------ # ------------------------------------------------------------
WORKDIR /opt/apps WORKDIR /opt/apps
# ------------------------------------------------------------ # ------------------------------------------------------------
# 拷贝项目源码 # 拷贝项目源码
# 注意:这里会把 VAR_PYFILE_PATH 目录下所有内容拷进 /opt/apps
# 建议配合 .dockerignore 排除 .git、__pycache__、本地虚拟环境等 # 建议配合 .dockerignore 排除 .git、__pycache__、本地虚拟环境等
# ------------------------------------------------------------ # ------------------------------------------------------------
COPY ${VAR_PYFILE_PATH}/ ./ COPY ${VAR_PYFILE_PATH}/ ./
# ------------------------------------------------------------ # ------------------------------------------------------------
# 修正工作目录属主,保证 esxi 用户可读写 # 一次性修正属主:
# (root 身份 COPY 进来的文件属主是 root,需改回 esxi) # /opt/apps 保证 esxi 可读写源码
# ${VAR_PYTHON_PREFIX} 保证 esxi 可访问 Python 及其 site-packages
# 合并成一条 RUN,减少镜像层
# ------------------------------------------------------------ # ------------------------------------------------------------
RUN chown -R esxi:esxi /opt/apps RUN chown -R esxi:esxi /opt/apps ${VAR_PYTHON_PREFIX}
# ------------------------------------------------------------
# 修正 Python 安装目录属主,保证 esxi 用户可访问
# (builder 阶段以 root 安装依赖,属主是 root,
# runtime 阶段切换到 esxi 后可能无法写入)
# ------------------------------------------------------------
RUN chown -R esxi:esxi /usr/local/python3.12.8
# ------------------------------------------------------------ # ------------------------------------------------------------
# 切回非 root 用户运行应用(安全加固) # 切回非 root 用户运行应用(安全加固)
@@ -172,7 +177,7 @@ RUN chown -R esxi:esxi /usr/local/python3.12.8
USER esxi USER esxi
# ------------------------------------------------------------ # ------------------------------------------------------------
# 暴露端口(仅声明,实际映射由 docker run -p 决定) # 声明暴露端口(实际映射由 docker run -p 决定)
# ------------------------------------------------------------ # ------------------------------------------------------------
EXPOSE ${PORT} EXPOSE ${PORT}