From 085f48ecd69cb460dcb68aa6d9f0d8168a0bfee5 Mon Sep 17 00:00:00 2001 From: XiaoXia Bot Date: Wed, 15 Jul 2026 11:46:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E5=A4=8Dregistry=E4=B8=8D?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E5=AF=BC=E8=87=B4=E9=95=9C=E5=83=8F=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E5=A4=B1=E8=B4=A5=20-=20=E8=A7=A3=E8=80=A6=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=8E=A8=E9=80=81=E4=B8=8E=E9=95=9C=E5=83=8F=E6=9E=84?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - Worker镜像构建因registry缓存推送时连接断开(use of closed network connection)而失败 - docker buildx exporting cache to registry阶段大层传输超时 - 缓存推送失败导致整个构建失败,镜像其实已经构建完成 修复: 1. 创建scripts/ci/docker_build_push.sh通用构建脚本 2. 镜像构建与缓存推送解耦:先构建+推送镜像(只读缓存)保证成功 3. 再单独尝试推送缓存,自动重试3次,失败不影响主构建结果 4. 缓存使用zstd压缩+oci-mediatypes减小传输体积 5. 缓存引用改为按分支名区分,支持多分支独立缓存 涉及:全部6个镜像构建job(staging/production x api/worker/web) --- .gitea/workflows/ci-cd.yml | 12 ++--- scripts/ci/docker_build_push.sh | 85 +++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 6 deletions(-) create mode 100755 scripts/ci/docker_build_push.sh diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index b975e84cc..25a26511d 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -448,7 +448,7 @@ jobs: run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n" - name: Build and push API image (buildx cache) shell: sh - run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_NAME=\"xiaoxia-saas-api\"\nCACHE_REF=\"${REGISTRY}/api-cache:develop\"\n\nCACHE_FROM=\"type=registry,ref=${CACHE_REF},ignore-error=true\"\n\nif [ \"${CACHE_MODE}\" = \"read-write\" ]; then\n CACHE_TO=\"type=registry,ref=${CACHE_REF},mode=max\"\n echo \"Building API image with read-write cache...\"\n docker buildx build --build-arg APP_VERSION=\"${GITHUB_SHA}\" --cache-from \"${CACHE_FROM}\" --cache-to \"${CACHE_TO}\" -f infra/docker/api.Dockerfile -t \"${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\" --push .\nelse\n echo \"Building API image with read-only cache...\"\n docker buildx build --build-arg APP_VERSION=\"${GITHUB_SHA}\" --cache-from \"${CACHE_FROM}\" -f infra/docker/api.Dockerfile -t \"${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\" --push .\nfi\necho\ + run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-api:${GITHUB_SHA}\"\nCACHE_REF=\"${REGISTRY}/api-cache:${GITHUB_REF_NAME}\"\n\nbash scripts/ci/docker_build_push.sh \\\n infra/docker/api.Dockerfile \\\n \"${IMAGE_TAG}\" \\\n \"${CACHE_REF}\" \\\n APP_VERSION=\"${GITHUB_SHA}\"\n\necho\necho \"API image pushed: ${IMAGE_TAG}\"" \ \"API image pushed: ${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\"\n" - name: Job duration summary if: always() @@ -510,7 +510,7 @@ jobs: run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n" - name: Build and push Worker image (buildx cache) shell: sh - run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_NAME=\"xiaoxia-saas-worker\"\nCACHE_REF=\"${REGISTRY}/worker-cache:develop\"\n\nCACHE_FROM=\"type=registry,ref=${CACHE_REF},ignore-error=true\"\n\nif [ \"${CACHE_MODE}\" = \"read-write\" ]; then\n CACHE_TO=\"type=registry,ref=${CACHE_REF},mode=min\"\n echo \"Building Worker image with read-write cache...\"\n docker buildx build --build-arg APP_VERSION=\"${GITHUB_SHA}\" --cache-from \"${CACHE_FROM}\" --cache-to \"${CACHE_TO}\" -f infra/docker/worker.Dockerfile -t \"${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\" --push .\nelse\n echo \"Building Worker image with read-only cache...\"\n docker buildx build --build-arg APP_VERSION=\"${GITHUB_SHA}\" --cache-from \"${CACHE_FROM}\" -f infra/docker/worker.Dockerfile -t \"${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\" --push \ + run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-worker:${GITHUB_SHA}\"\nCACHE_REF=\"${REGISTRY}/worker-cache:${GITHUB_REF_NAME}\"\n\nbash scripts/ci/docker_build_push.sh \\\n infra/docker/worker.Dockerfile \\\n \"${IMAGE_TAG}\" \\\n \"${CACHE_REF}\" \\\n APP_VERSION=\"${GITHUB_SHA}\"\n\necho\necho \"Worker image pushed: ${IMAGE_TAG}\"" \ .\nfi\necho \"Worker image pushed: ${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\"\n" - name: Job duration summary if: always() @@ -575,7 +575,7 @@ jobs: run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n" - name: Build and push Web image (buildx cache) shell: sh - run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_NAME=\"xiaoxia-saas-web\"\nCACHE_REF=\"${REGISTRY}/web-cache:develop\"\nNGINX_CONF=\"infra/docker/nginx-staging.conf\"\n\nCACHE_FROM=\"type=registry,ref=${CACHE_REF},ignore-error=true\"\n\nif [ \"${CACHE_MODE}\" = \"read-write\" ]; then\n CACHE_TO=\"type=registry,ref=${CACHE_REF},mode=max\"\n echo \"Building Web image with read-write cache...\"\n docker buildx build --cache-from \"${CACHE_FROM}\" --cache-to \"${CACHE_TO}\" -f infra/docker/web-artifact.Dockerfile --build-arg \"NGINX_CONF=${NGINX_CONF}\" -t \"${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\" --push .\nelse\n echo \"Building Web image with read-only cache...\"\n docker buildx build --cache-from \"${CACHE_FROM}\" -f infra/docker/web-artifact.Dockerfile --build-arg \"NGINX_CONF=${NGINX_CONF}\" -t \"${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\"\ + run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-web:${GITHUB_SHA}\"\nCACHE_REF=\"${REGISTRY}/web-cache:${GITHUB_REF_NAME}\"\n\nbash scripts/ci/docker_build_push.sh \\\n infra/docker/web.Dockerfile \\\n \"${IMAGE_TAG}\" \\\n \"${CACHE_REF}\" \\\n APP_VERSION=\"${GITHUB_SHA}\"\n\necho\necho \"Web image pushed: ${IMAGE_TAG}\"" \ --push .\nfi\necho \"Web image pushed: ${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\"\n" - name: Job duration summary if: always() @@ -825,7 +825,7 @@ jobs: run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n" - name: Build and push API image (buildx cache) shell: sh - run: 'set -eu + run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-api:${GITHUB_SHA}\"\nCACHE_REF=\"${REGISTRY}/api-cache:${GITHUB_REF_NAME}\"\n\nbash scripts/ci/docker_build_push.sh \\\n infra/docker/api.Dockerfile \\\n \"${IMAGE_TAG}\" \\\n \"${CACHE_REF}\" \\\n APP_VERSION=\"${GITHUB_SHA}\"\n\necho\necho \"API image pushed: ${IMAGE_TAG}\"" REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas" @@ -900,7 +900,7 @@ jobs: run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n" - name: Build and push Worker image (buildx cache) shell: sh - run: 'set -eu + run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-worker:${GITHUB_SHA}\"\nCACHE_REF=\"${REGISTRY}/worker-cache:${GITHUB_REF_NAME}\"\n\nbash scripts/ci/docker_build_push.sh \\\n infra/docker/worker.Dockerfile \\\n \"${IMAGE_TAG}\" \\\n \"${CACHE_REF}\" \\\n APP_VERSION=\"${GITHUB_SHA}\"\n\necho\necho \"Worker image pushed: ${IMAGE_TAG}\"" REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas" @@ -978,7 +978,7 @@ jobs: run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n" - name: Build and push Web image (buildx cache) shell: sh - run: 'set -eu + run: "set -eu\nREGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"\nIMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-web:${GITHUB_SHA}\"\nCACHE_REF=\"${REGISTRY}/web-cache:${GITHUB_REF_NAME}\"\n\nbash scripts/ci/docker_build_push.sh \\\n infra/docker/web.Dockerfile \\\n \"${IMAGE_TAG}\" \\\n \"${CACHE_REF}\" \\\n APP_VERSION=\"${GITHUB_SHA}\"\n\necho\necho \"Web image pushed: ${IMAGE_TAG}\"" REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas" diff --git a/scripts/ci/docker_build_push.sh b/scripts/ci/docker_build_push.sh new file mode 100755 index 000000000..33c644fba --- /dev/null +++ b/scripts/ci/docker_build_push.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Docker镜像构建与推送脚本 - 处理registry不稳定问题 +# 特性: +# 1. 构建+推送镜像(缓存只读模式)保证成功 +# 2. 单独尝试推送缓存,失败不影响主构建 +# 3. 缓存推送自动重试3次 +# 4. zstd压缩减小缓存体积 +set -eu + +usage() { + echo "Usage: $0 [build_args...]" + echo " dockerfile: Dockerfile路径" + echo " image_tag: 完整镜像标签 (registry/repo:tag)" + echo " cache_ref: 缓存引用 (registry/repo-cache:branch)" + echo " build_args: 额外的--build-arg参数 (可选)" + exit 1 +} + +if [ $# -lt 3 ]; then + usage +fi + +DOCKERFILE="$1" +IMAGE_TAG="$2" +CACHE_REF="$3" +shift 3 +BUILD_ARGS="" +for arg in "$@"; do + BUILD_ARGS="$BUILD_ARGS --build-arg $arg" +done + +CACHE_FROM="type=registry,ref=${CACHE_REF},ignore-error=true" +CACHE_TO="type=registry,ref=${CACHE_REF},mode=min,compression=zstd,oci-mediatypes=true" + +echo "========================================" +echo "构建镜像: $IMAGE_TAG" +echo "Dockerfile: $DOCKERFILE" +echo "缓存引用: $CACHE_REF" +echo "========================================" + +# Step 1: 构建并推送镜像(只读缓存,保证主构建成功) +echo "" +echo "[1/2] 构建并推送镜像 (只读缓存模式)..." +docker buildx build \ + $BUILD_ARGS \ + --cache-from "$CACHE_FROM" \ + -f "$DOCKERFILE" \ + -t "$IMAGE_TAG" \ + --push \ + . +echo "✅ 镜像构建并推送成功: $IMAGE_TAG" + +# Step 2: 尝试推送缓存(失败不影响,重试3次) +echo "" +echo "[2/2] 尝试推送缓存到 registry (最多重试3次)..." +CACHE_PUSH_SUCCESS=false +for attempt in 1 2 3; do + echo " 缓存推送尝试 $attempt/3..." + if docker buildx build \ + $BUILD_ARGS \ + --cache-from "$CACHE_FROM" \ + --cache-to "$CACHE_TO" \ + -f "$DOCKERFILE" \ + -t "$IMAGE_TAG" \ + --push \ + . 2>&1 | tail -5; then + CACHE_PUSH_SUCCESS=true + echo " ✅ 缓存推送成功 (第$attempt次尝试)" + break + else + echo " ⚠️ 缓存推送失败 (第$attempt次尝试),等待后重试..." + sleep $((attempt * 5)) + fi +done + +if [ "$CACHE_PUSH_SUCCESS" = false ]; then + echo "" + echo "⚠️ 缓存推送最终失败,跳过(不影响构建结果)" + echo " 下一次构建将使用旧缓存或无缓存构建" +fi + +echo "" +echo "========================================" +echo "✅ 构建完成: $IMAGE_TAG" +echo "========================================"