diff --git a/.jenkins/Dockerfile b/.jenkins/Dockerfile index bcc921f..bbdfb9e 100644 --- a/.jenkins/Dockerfile +++ b/.jenkins/Dockerfile @@ -1,154 +1,159 @@ +# syntax=docker/dockerfile:1.7 # ============================================================ -# Dockerfile:单阶段构建 Python 应用镜像(RockyLinux 8 + Python 3.12.8) +# Dockerfile:项目镜像(多阶段构建) +# 基础镜像:python-base:3.12.8(RockyLinux 8 + Python 3.12.8 + mariadb-connector-c) # -# 设计思路: -# - 单阶段构建,所有依赖一次装齐,避免多阶段"包找不到/库缺失"问题 -# - 编译工具链和运行库都保留在最终镜像里,换取构建稳定性和排错简单 -# - 通过 --build-arg 动态注入变量,便于 CI 复用同一份 Dockerfile +# 设计要点 +# - builder 只编译依赖,装到 /opt/deps,不碰基础镜像的 Python 目录 +# - runtime 继承基础镜像的 Python,只把依赖的 site-packages COPY 过来 +# - MySQL 运行库(mariadb-connector-c)由基础镜像预置,runtime 无需重复安装 +# - 源码 COPY 放在 pip install 之后,保护依赖层缓存 +# - 使用 BuildKit cache mount 加速 dnf / pip # -# 构建参数说明: -# VAR_BASE_IMAGE 基础镜像(RockyLinux 8 + Python 3.12.8 + esxi 用户) -# VAR_PROFILES 应用 profile(dev / prod) -# VAR_PORT 应用监听端口 -# VAR_PYFILE 启动入口文件名(不含 .py),如 manage -# VAR_PYFILE_PATH 项目源码目录(相对构建上下文) +# 构建参数 +# VAR_BASE_IMAGE 基础镜像 +# VAR_PYTHON_PREFIX Python 安装前缀(与基础镜像一致) +# VAR_PYTHON_SHORT_VERSION Python 主次版本号,用于 site-packages 路径 +# VAR_PYFILE_PATH 项目源码目录 +# VAR_PROFILES 应用 profile(dev / prod) +# VAR_PORT 应用监听端口 +# VAR_PYFILE 入口文件名(不含 .py) # ============================================================ + # ------------------------------------------------------------ -# 声明构建参数 +# 全局构建参数 +# 在 FROM 之前声明,可供后续各阶段引用 # ------------------------------------------------------------ ARG VAR_BASE_IMAGE +ARG VAR_PYTHON_PREFIX=/usr/local/python3.12.8 +ARG VAR_PYTHON_SHORT_VERSION=3.12 -# ------------------------------------------------------------ -# 基础镜像 -# ------------------------------------------------------------ -FROM ${VAR_BASE_IMAGE} -# 镜像作者信息 -LABEL MAINTAINER="djangoadmin@163.com" +# ============================================================ +# 阶段一:builder —— 编译并安装项目依赖 +# ============================================================ +FROM ${VAR_BASE_IMAGE} AS builder -# ------------------------------------------------------------ -# 切回 root -# 基础镜像末尾通常设置了非 root 用户(如 esxi), -# 不切回 root,后面的 dnf install / mkdir / chown 都会因权限不足失败 -# ------------------------------------------------------------ +# 阶段内重新声明,否则读不到全局 ARG +# Docker 规则:全局 ARG 只在 FROM 行可见,阶段内使用必须重新声明 +ARG VAR_PYTHON_PREFIX +ARG VAR_PYTHON_SHORT_VERSION +ARG VAR_PYFILE_PATH + +# 基础镜像末尾是 esxi 用户,装包需切回 root USER root -# ------------------------------------------------------------ -# Python 运行期环境变量 -# PYTHONUNBUFFERED=1 Python 输出不缓冲,日志实时可见 -# PYTHONDONTWRITEBYTECODE=1 不生成 .pyc,避免容器内产生垃圾文件 -# ------------------------------------------------------------ -ENV PYTHONUNBUFFERED=1 \ - PYTHONDONTWRITEBYTECODE=1 +LABEL MAINTAINER="djangoadmin@126.com" -# ------------------------------------------------------------ -# 声明运行期构建参数 -# ------------------------------------------------------------ +# 编译阶段环境变量 +# PYTHONUNBUFFERED=1 日志实时输出 +# PYTHONDONTWRITEBYTECODE=1 不生成 .pyc +# PIP_NO_CACHE_DIR=1 不缓存 wheel +# PIP_DISABLE_PIP_VERSION_CHECK=1 关闭 pip 版本检查 +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 + +# 安装编译工具链 +# gcc / gcc-c++ / make 编译 C 扩展(mysqlclient、uwsgi 等) +# openssl-devel cryptography、_ssl 等 +# bzip2-devel / libffi-devel +# mariadb-devel 编译 mysqlclient(提供 .pc 文件) +# pkgconf-pkg-config 提供 pkg-config 命令(mysqlclient 2.2.x 必需) +# 使用 BuildKit cache mount,重复构建不再重新下载 RPM +RUN --mount=type=cache,target=/var/cache/dnf,sharing=locked,id=dnf-cache \ + set -eux; \ + # 替换 dnf 源为阿里云镜像 + sed -e 's|^mirrorlist=|#mirrorlist=|g' \ + -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \ + -i.bak /etc/yum.repos.d/*.repo; \ + dnf install -y --setopt=install_weak_deps=False \ + gcc gcc-c++ make \ + openssl-devel bzip2-devel libffi-devel \ + mariadb-devel pkgconf-pkg-config + +# 配置 pip 国内镜像源(阿里云 PyPI) +RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple && \ + pip3 config set install.trusted-host mirrors.aliyun.com + +# 只拷 requirements.txt +# 源码变化但依赖未变时,可复用 pip install 层缓存 +WORKDIR /opt/apps +COPY ${VAR_PYFILE_PATH}/requirements.txt ./ + +# 依赖装到 /opt/deps,与基础镜像 Python 目录解耦 +# --prefix=/opt/deps 安装到独立目录,runtime 只 COPY 这个目录的产物 +# uwsgi 生产环境 WSGI 服务器,单独安装 +# 使用 pip cache mount,重复构建不再重新下载 wheel +RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked,id=pip-cache \ + python3 -m pip install --prefix=/opt/deps -r requirements.txt && \ + python3 -m pip install --prefix=/opt/deps uwsgi + + +# ============================================================ +# 阶段二:runtime —— 最终业务镜像 +# ============================================================ +FROM ${VAR_BASE_IMAGE} AS runtime + +# 阶段内重新声明 +ARG VAR_PYTHON_PREFIX +ARG VAR_PYTHON_SHORT_VERSION ARG VAR_PROFILES ARG VAR_PORT ARG VAR_PYFILE ARG VAR_PYFILE_PATH -# ------------------------------------------------------------ -# 将 ARG 转为 ENV,运行期可通过环境变量读取 -# ------------------------------------------------------------ +USER root + +LABEL MAINTAINER="djangoadmin@126.com" + +# 运行期环境变量 +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 + +# ARG 转 ENV,运行期可读取 +# PORT 供 EXPOSE 与 CMD 使用 +# PYFILE 入口文件名 +# PROFILES 应用读取的 profile ENV PORT=${VAR_PORT} \ PYFILE=${VAR_PYFILE} \ PROFILES=${VAR_PROFILES} -# ------------------------------------------------------------ -# 安装系统依赖(dnf 源换成国内镜像) -# -# RockyLinux 8 官方源可用,但换阿里云镜像更快: -# baseurl=https://mirrors.aliyun.com/rockylinux -# -# 编译期工具: -# gcc / gcc-c++ C/C++ 编译器 -# make / cmake 构建工具 -# openssl-devel 编译 _ssl / cryptography -# bzip2-devel 编译 _bz2 -# libffi-devel 编译 _ctypes -# mariadb-devel 编译 mysqlclient(提供 .pc 文件) -# pkgconf-pkg-config 提供 pkg-config 命令(mysqlclient 2.2.x 必需) -# -# 运行期库: -# net-tools netstat 等网络排查工具 -# openssl-libs SSL 运行库 -# bzip2-libs bz2 运行库 -# libffi ctypes 运行库 -# mariadb-connector-c MySQL/MariaDB 客户端运行库 -# -# 注意: -# 1) RockyLinux 8 里 MySQL 实际由 MariaDB 提供, -# 运行库包名为 mariadb-connector-c(不是 mysql-libs) -# 2) mariadb-devel 和 mysql-devel 不要同时安装,会冲突 -# 3) 末尾清理 dnf 缓存,避免缓存进入镜像层 -# ------------------------------------------------------------ -RUN set -eux; \ - # ---- 把 RockyLinux 8 官方源替换为阿里云镜像 ---- +# 运行期依赖 +# mariadb-connector-c 基础镜像已预置,无需重复安装 +# net-tools 按需保留,如不需要可删 +RUN --mount=type=cache,target=/var/cache/dnf,sharing=locked,id=dnf-cache \ + set -eux; \ sed -e 's|^mirrorlist=|#mirrorlist=|g' \ -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \ -i.bak /etc/yum.repos.d/*.repo; \ - # ---- 安装编译工具链 + 运行库 ---- - dnf install -y --setopt=install_weak_deps=False \ - yum-utils net-tools \ - gcc gcc-c++ make cmake \ - openssl-devel bzip2-devel libffi-devel \ - mariadb-devel pkgconf-pkg-config \ - openssl-libs bzip2-libs libffi mariadb-connector-c; \ - # ---- 清理 dnf 缓存,减小镜像体积 ---- - dnf clean all; rm -rf /var/cache/dnf + dnf install -y --setopt=install_weak_deps=False net-tools -# ------------------------------------------------------------ -# 配置 pip 国内镜像源(阿里云 PyPI) -# ------------------------------------------------------------ -RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple && \ - pip3 config set install.trusted-host mirrors.aliyun.com +# 把 builder 编译好的 site-packages 和可执行文件拷到基础镜像 Python 对应目录 +# 目标路径必须与基础镜像 Python 的实际 site-packages 一致 +# 用 VAR_PYTHON_SHORT_VERSION 拼接,避免硬编码 python3.12 +COPY --from=builder /opt/deps/lib/python${VAR_PYTHON_SHORT_VERSION}/site-packages/ \ + ${VAR_PYTHON_PREFIX}/lib/python${VAR_PYTHON_SHORT_VERSION}/site-packages/ +COPY --from=builder /opt/deps/bin/ ${VAR_PYTHON_PREFIX}/bin/ -# ------------------------------------------------------------ -# 升级 pip / setuptools / wheel -# --no-cache-dir 避免 pip 缓存进入镜像层 -# ------------------------------------------------------------ -RUN python3 -m pip install --upgrade --no-cache-dir pip setuptools wheel - -# ------------------------------------------------------------ -# 创建工作区 -# ------------------------------------------------------------ -RUN mkdir -p /opt/apps WORKDIR /opt/apps -# ------------------------------------------------------------ -# 拷贝项目源码 -# ------------------------------------------------------------ +# 拷项目源码(放在依赖之后,保护 pip 层缓存) +# 建议配合 .dockerignore 排除 .git、__pycache__、本地虚拟环境等 COPY ${VAR_PYFILE_PATH}/ ./ -# ------------------------------------------------------------ -# 安装 Python 依赖 -# -r requirements.txt 项目依赖 -# uwsgi 生产环境 WSGI 服务器 -# ------------------------------------------------------------ -RUN python3 -m pip install --no-cache-dir -r requirements.txt && \ - python3 -m pip install --no-cache-dir uwsgi +# 修正属主,保证 esxi 可读写源码和 Python 目录 +RUN chown -R esxi:esxi /opt/apps ${VAR_PYTHON_PREFIX} -# ------------------------------------------------------------ -# 修正属主 -# ------------------------------------------------------------ -RUN chown -R esxi:esxi /opt/apps - -# ------------------------------------------------------------ -# 切回非 root 用户运行应用 -# ------------------------------------------------------------ USER esxi - -# ------------------------------------------------------------ -# 声明暴露端口 -# ------------------------------------------------------------ EXPOSE ${PORT} -# ------------------------------------------------------------ # 启动命令 # ${PYFILE} 为入口文件名(不含 .py),如 manage -# 生产环境建议改用 uwsgi -# ------------------------------------------------------------ -CMD ["sh", "-c", "python3 manage.py runserver 0.0.0.0:${PORT} --noreload"] \ No newline at end of file +# runserver 仅适合开发/调试;生产环境建议改用 uwsgi / gunicorn,例如: +# CMD ["sh","-c","uwsgi --http 0.0.0.0:${PORT} \ +# --module 你的项目名.wsgi:application \ +# --processes 4 --threads 2"] +CMD ["sh", "-c", "python3 ${PYFILE}.py runserver 0.0.0.0:${PORT} --noreload"] \ No newline at end of file