diff --git a/.gitea/workflows/ci-pipeline.yml b/.gitea/workflows/ci-pipeline.yml index 64c9551ea..936543ca9 100755 --- a/.gitea/workflows/ci-pipeline.yml +++ b/.gitea/workflows/ci-pipeline.yml @@ -472,6 +472,36 @@ jobs: echo "Docker login failed ($i/3), retrying in 5s..." sleep 5 done + - name: Pre-build worker base images (fallback if not exist) + if: matrix.service == 'worker' + id: prebuild + shell: sh + run: | + set -eu + REGISTRY="git.xiaoxiajianji.com/xiaoxia-saas" + BASE_BUILDER="${REGISTRY}/worker-base-builder:latest" + BASE_RUNTIME="${REGISTRY}/worker-base-runtime:latest" + + # 尝试拉取基础镜像 + echo "检查基础镜像..." + if docker pull "$BASE_BUILDER" 2>/dev/null && docker pull "$BASE_RUNTIME" 2>/dev/null; then + echo "基础镜像已存在,使用远程镜像" + echo "fallback=false" >> $GITHUB_OUTPUT + else + echo "基础镜像不存在,本地构建(fallback模式)..." + + # 构建builder基础镜像 + echo "构建 worker-base-builder..." + docker build -f infra/docker/worker-base-builder.Dockerfile -t "$BASE_BUILDER" . + + # 构建runtime基础镜像 + echo "构建 worker-base-runtime..." + docker build -f infra/docker/worker-base-runtime.Dockerfile -t "$BASE_RUNTIME" . + + echo "fallback=true" >> $GITHUB_OUTPUT + echo "基础镜像本地构建完成" + fi + - name: Build PR image (verify only, no push) shell: sh run: | @@ -485,6 +515,18 @@ jobs: EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-staging.conf" fi + # Worker fallback模式:基础镜像本地已构建,用普通docker build绕过buildx + if [ "${{ matrix.service }}" = "worker" ] && [ "${{ steps.prebuild.outputs.fallback }}" = "true" ]; then + echo "Fallback模式:用普通docker build(基础镜像本地已构建)" + BUILD_ARG_STR="" + for arg in $EXTRA_BUILD_ARGS; do + BUILD_ARG_STR="$BUILD_ARG_STR --build-arg $arg" + done + docker build -f ${{ matrix.dockerfile }} -t "${IMAGE_TAG}" $BUILD_ARG_STR . + echo "Fallback PR Build successful" + exit 0 + fi + NO_CACHE_FLAG="" for i in 1 2 3; do echo "PR Build attempt $i/3" diff --git a/.gitea/workflows/worker-base-image.yml b/.gitea/workflows/worker-base-image.yml new file mode 100644 index 000000000..c07286aa8 --- /dev/null +++ b/.gitea/workflows/worker-base-image.yml @@ -0,0 +1,103 @@ +name: Worker Base Image Build + +on: + push: + branches: + - develop + - main + paths: + - 'requirements-base.txt' + - 'requirements-worker.txt' + - 'infra/docker/worker-base-builder.Dockerfile' + - 'infra/docker/worker-base-runtime.Dockerfile' + workflow_dispatch: # 支持手动触发 + +jobs: + build-worker-base: + name: Build Worker Base Images + runs-on: runtime-builder + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: builder + dockerfile: infra/docker/worker-base-builder.Dockerfile + image_name: worker-base-builder + cache_name: worker-base-builder-cache + - name: runtime + dockerfile: infra/docker/worker-base-runtime.Dockerfile + image_name: worker-base-runtime + cache_name: worker-base-runtime-cache + steps: + - name: Checkout code + shell: sh + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash + + - name: Docker login to Registry + shell: sh + env: + ACR_USERNAME: ${{ secrets.ACR_USERNAME }} + ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} + GITEA_REGISTRY_USER: xiaoxia + GITEA_REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -eu + for i in 1 2 3; do + echo "=== Docker login 尝试 $i/3 ===" + if printf '%s' "${ACR_PASSWORD}" | docker login xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com -u "${ACR_USERNAME}" --password-stdin && docker login git.xiaoxiajianji.com -u "${GITEA_REGISTRY_USER}" -p "${GITEA_REGISTRY_TOKEN}"; then + echo "✅ Docker login successful" + break + fi + echo "❌ Docker login 失败(尝试 $i/3),5s 后重试..." + sleep 5 + done + + - name: Setup buildx builder + shell: sh + run: | + set -eu + BUILDER_NAME="ci-builder-${GITHUB_RUN_ID}-${{ matrix.name }}" + if ! docker buildx inspect "$BUILDER_NAME" > /dev/null 2>&1; then + docker buildx create --use --name "$BUILDER_NAME" --driver docker-container + echo "Created $BUILDER_NAME" + else + docker buildx use "$BUILDER_NAME" + echo "Using existing $BUILDER_NAME" + fi + docker buildx inspect --bootstrap + + - name: Build and push base image + shell: sh + run: | + set -eu + REGISTRY="xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com/xiaoxiakeji" + IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:latest" + SAFE_REF_NAME=$(echo "${GITHUB_REF_NAME}" | tr '/' '-') + CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:${SAFE_REF_NAME}" + + echo "=== Building ${{ matrix.name }} base image ===" + echo "Image: ${IMAGE_TAG}" + echo "Cache: ${CACHE_REF}" + + # 用通用构建脚本 + bash scripts/ci/docker_build_push.sh ${{ matrix.dockerfile }} "${IMAGE_TAG}" "${CACHE_REF}" + + # 同时推送到 Gitea Packages 作为备份(可选) + GITEA_IMAGE="git.xiaoxiajianji.com/xiaoxia-saas/${{ matrix.image_name }}:latest" + docker tag "${IMAGE_TAG}" "${GITEA_IMAGE}" + docker push "${GITEA_IMAGE}" || echo "Gitea Packages push failed (non-fatal)" + + echo "" + echo "✅ ${{ matrix.name }} base image built and pushed" + + - name: Cleanup buildx builder + if: always() + shell: sh + run: | + docker buildx rm "ci-builder-${GITHUB_RUN_ID}-${{ matrix.name }}" 2>/dev/null || true + docker buildx prune -f 2>/dev/null || true + echo "Builder cleanup done" diff --git a/infra/docker/worker-base-builder.Dockerfile b/infra/docker/worker-base-builder.Dockerfile new file mode 100644 index 000000000..4a1bdb1d3 --- /dev/null +++ b/infra/docker/worker-base-builder.Dockerfile @@ -0,0 +1,43 @@ +# ============================================================ +# Worker Builder 基础镜像 +# 预编译:编译工具 + 基础依赖 + Worker大包 +# 当 requirements-base.txt 或 requirements-worker.txt 变更时重新构建 +# 业务构建从此镜像开始,只需要安装业务依赖,节省15+分钟 +# ============================================================ + +FROM git.xiaoxiajianji.com/xiaoxia/base/python:3.12-slim + +# 使用阿里云镜像加速 +RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \ + sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list 2>/dev/null || true + +# 安装编译工具 +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + g++ \ + python3-dev \ + binutils \ + && rm -rf /var/lib/apt/lists/* + +# 创建 venv +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +WORKDIR /tmp + +# 基础依赖(变化极少) +COPY requirements-base.txt /tmp/requirements-base.txt +RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ + pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com \ + -r /tmp/requirements-base.txt \ + && rm /tmp/requirements-base.txt + +# Worker 大包(变化少) +COPY requirements-worker.txt /tmp/requirements-worker.txt +RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ + pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com \ + -r /tmp/requirements-worker.txt \ + && rm /tmp/requirements-worker.txt + +# 预先做一次 strip(基础层瘦身,业务层增量) +RUN find /opt/venv -name "*.so" -type f -exec strip --strip-all {} \; 2>/dev/null || true diff --git a/infra/docker/worker-base-runtime.Dockerfile b/infra/docker/worker-base-runtime.Dockerfile new file mode 100644 index 000000000..4f83780ef --- /dev/null +++ b/infra/docker/worker-base-runtime.Dockerfile @@ -0,0 +1,17 @@ +# ============================================================ +# Worker Runtime 基础镜像 +# 预安装:ffmpeg + 运行时依赖 +# 变化极少,业务构建从此镜像开始 +# ============================================================ + +FROM git.xiaoxiajianji.com/xiaoxia/base/python:3.12-slim + +# 使用阿里云镜像加速 +RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \ + sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list 2>/dev/null || true + +# 运行时依赖:ffmpeg + opencv需要的libglib +RUN apt-get update && apt-get install -y --no-install-recommends \ + ffmpeg \ + libglib2.0-0 \ + && rm -rf /var/lib/apt/lists/* diff --git a/infra/docker/worker.Dockerfile b/infra/docker/worker.Dockerfile index 0639128cb..d00043021 100755 --- a/infra/docker/worker.Dockerfile +++ b/infra/docker/worker.Dockerfile @@ -1,88 +1,43 @@ # ============================================================ -# Worker Dockerfile - 优化版(多阶段构建 + 镜像瘦身 + cache mount加速) -# 优化项: -# 1. 多阶段构建:builder 阶段安装编译依赖,runtime 阶段只保留运行时 -# 2. ffmpeg 通过 apt 安装(阿里云镜像加速,几秒完成,稳定可靠) -# 3. Python 依赖瘦身:strip .so 调试符号 + 清理测试文件 + 清理缓存 -# 4. pip cache mount:加速依赖下载(跨构建共享pip wheel缓存) +# Worker Dockerfile - 分层缓存优化版 +# 优化:基础依赖 + Worker大包预构建为基础镜像,业务构建仅叠加业务依赖 +# 基础镜像:worker-base-builder / worker-base-runtime +# 预计节省:依赖不变时构建时间从23min降至5min以内 # ============================================================ # ==================== Builder 阶段 ==================== -FROM git.xiaoxiajianji.com/xiaoxia/base/python:3.12-slim AS builder +# 从预构建的builder基础镜像开始,已经包含: +# - 编译工具 (gcc/g++/python3-dev/binutils) +# - requirements-base.txt 全部依赖 +# - requirements-worker.txt 全部依赖 (numpy/scipy/opencv) +# - 预strip的.so文件 +FROM git.xiaoxiajianji.com/xiaoxia-saas/worker-base-builder:latest AS builder -# 使用阿里云镜像加速 -RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \ - sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list 2>/dev/null || true - -# 安装编译工具(仅 builder 需要) -RUN apt-get update && apt-get install -y --no-install-recommends \ - gcc \ - g++ \ - python3-dev \ - binutils \ - && rm -rf /var/lib/apt/lists/* - - -# ---- 安装 Python 依赖 ---- -WORKDIR /tmp - -# 创建 venv -RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" -# 基础依赖 -COPY requirements-base.txt /tmp/requirements-base.txt -RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ - pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com \ - -r /tmp/requirements-base.txt \ - && rm /tmp/requirements-base.txt +WORKDIR /tmp -# Worker 专属大包 -COPY requirements-worker.txt /tmp/requirements-worker.txt -RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ - pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com \ - -r /tmp/requirements-worker.txt \ - && rm /tmp/requirements-worker.txt - -# 业务依赖 +# ---- 安装业务依赖(变化频繁,单独一层)---- COPY requirements.txt /tmp/requirements.txt RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com \ -r /tmp/requirements.txt \ && rm /tmp/requirements.txt -# ---- Python 依赖瘦身 ---- -# 1. strip .so 文件的调试符号(节省约 80-100MB) +# ---- 增量瘦身(只处理新增的业务依赖)---- RUN find /opt/venv -name "*.so" -type f -exec strip --strip-all {} \; 2>/dev/null || true - -# 2. 清理测试文件(节省约 20MB) -RUN find /opt/venv -type d -name "tests" -exec rm -rf {} + 2>/dev/null; \ - find /opt/venv -type d -name "test" -exec rm -rf {} + 2>/dev/null; \ - find /opt/venv -name "test_*.py" -delete 2>/dev/null || true - -# 3. 清理 .pyc 缓存和 __pycache__(节省约 10MB,运行时按需生成) RUN find /opt/venv -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null; \ find /opt/venv -name "*.pyc" -delete 2>/dev/null || true -# 4. 清理 dist-info 中的文档 -RUN find /opt/venv -name "*.dist-info" -type d -exec sh -c 'rm -f "$1"/DESCRIPTION.rst "$1"/INSTALLER "$1"/LICENSE* "$1"/WHEEL "$1"/entry_points.txt' _ {} \; 2>/dev/null || true - # ==================== Runtime 阶段 ==================== -FROM git.xiaoxiajianji.com/xiaoxia/base/python:3.12-slim AS runtime +# 从预构建的runtime基础镜像开始,已经包含: +# - ffmpeg +# - libglib2.0-0 +FROM git.xiaoxiajianji.com/xiaoxia-saas/worker-base-runtime:latest AS runtime # 构建参数:版本号 ARG APP_VERSION=dev -# 使用阿里云镜像加速 -RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \ - sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list 2>/dev/null || true - -# 安装最小运行时依赖(opencv-python-headless 需要 libglib2.0-0) -RUN apt-get update && apt-get install -y --no-install-recommends \ - ffmpeg \ - libglib2.0-0 \ - && rm -rf /var/lib/apt/lists/* - # 从 builder 复制 Python 虚拟环境 COPY --from=builder /opt/venv /opt/venv