fix(ci): 修复registry不稳定导致镜像构建失败 - 解耦缓存推送与镜像构建

问题:
- 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)
This commit is contained in:
XiaoXia Bot
2026-07-15 11:46:52 +08:00
parent 7c8748ece6
commit 085f48ecd6
2 changed files with 91 additions and 6 deletions
+6 -6
View File
@@ -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"
+85
View File
@@ -0,0 +1,85 @@
#!/bin/bash
# Docker镜像构建与推送脚本 - 处理registry不稳定问题
# 特性:
# 1. 构建+推送镜像(缓存只读模式)保证成功
# 2. 单独尝试推送缓存,失败不影响主构建
# 3. 缓存推送自动重试3次
# 4. zstd压缩减小缓存体积
set -eu
usage() {
echo "Usage: $0 <dockerfile> <image_tag> <cache_ref> [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 "========================================"