diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index b975e84cc..e286f2cae 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -448,8 +448,20 @@ 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\ - \ \"API image pushed: ${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\"\n" + run: "set -eu +REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\" +IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-api:${GITHUB_SHA}\" +CACHE_REF=\"${REGISTRY}/api-cache:${GITHUB_REF_NAME}\" + +bash scripts/ci/docker_build_push.sh \ + infra/docker/api.Dockerfile \ + \"${IMAGE_TAG}\" \ + \"${CACHE_REF}\" \ + APP_VERSION=\"${GITHUB_SHA}\" + +echo +echo \"API image pushed: ${IMAGE_TAG}\" +" - name: Job duration summary if: always() shell: sh @@ -510,8 +522,20 @@ 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 \ - \ .\nfi\necho \"Worker image pushed: ${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\"\n" + run: "set -eu +REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\" +IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-worker:${GITHUB_SHA}\" +CACHE_REF=\"${REGISTRY}/worker-cache:${GITHUB_REF_NAME}\" + +bash scripts/ci/docker_build_push.sh \ + infra/docker/worker.Dockerfile \ + \"${IMAGE_TAG}\" \ + \"${CACHE_REF}\" \ + APP_VERSION=\"${GITHUB_SHA}\" + +echo +echo \"WORKER image pushed: ${IMAGE_TAG}\" +" - name: Job duration summary if: always() shell: sh @@ -575,8 +599,20 @@ 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}\"\ - \ --push .\nfi\necho \"Web image pushed: ${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}\"\n" + run: "set -eu +REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\" +IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-web:${GITHUB_SHA}\" +CACHE_REF=\"${REGISTRY}/web-cache:${GITHUB_REF_NAME}\" + +bash scripts/ci/docker_build_push.sh \ + infra/docker/web.Dockerfile \ + \"${IMAGE_TAG}\" \ + \"${CACHE_REF}\" \ + APP_VERSION=\"${GITHUB_SHA}\" + +echo +echo \"WEB image pushed: ${IMAGE_TAG}\" +" - name: Job duration summary if: always() shell: sh @@ -825,24 +861,20 @@ 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 +REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\" +IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-api:${GITHUB_SHA}\" +CACHE_REF=\"${REGISTRY}/api-cache:${GITHUB_REF_NAME}\" - REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas" +bash scripts/ci/docker_build_push.sh \ + infra/docker/api.Dockerfile \ + \"${IMAGE_TAG}\" \ + \"${CACHE_REF}\" \ + APP_VERSION=\"${GITHUB_SHA}\" - IMAGE_NAME="xiaoxia-saas-api" - - VERSION="${GITHUB_REF_NAME}" - - CACHE_REF="${REGISTRY}/api-cache:main" - - - echo "Building Production API image: ${VERSION}" - - docker buildx build --build-arg APP_VERSION="${VERSION}" --cache-from "type=registry,ref=${CACHE_REF},ignore-error=true" --cache-to "type=registry,ref=${CACHE_REF},mode=max" -f infra/docker/api.Dockerfile -t "${REGISTRY}/${IMAGE_NAME}:${VERSION}" --push . - - echo "Production API image pushed: ${REGISTRY}/${IMAGE_NAME}:${VERSION}" - - ' +echo +echo \"API image pushed: ${IMAGE_TAG}\" +" - name: Job duration summary if: always() shell: sh @@ -900,24 +932,20 @@ 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 +REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\" +IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-worker:${GITHUB_SHA}\" +CACHE_REF=\"${REGISTRY}/worker-cache:${GITHUB_REF_NAME}\" - REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas" +bash scripts/ci/docker_build_push.sh \ + infra/docker/worker.Dockerfile \ + \"${IMAGE_TAG}\" \ + \"${CACHE_REF}\" \ + APP_VERSION=\"${GITHUB_SHA}\" - IMAGE_NAME="xiaoxia-saas-worker" - - VERSION="${GITHUB_REF_NAME}" - - CACHE_REF="${REGISTRY}/worker-cache:main" - - - echo "Building Production Worker image: ${VERSION}" - - docker buildx build --build-arg APP_VERSION="${VERSION}" --cache-from "type=registry,ref=${CACHE_REF},ignore-error=true" --cache-to "type=registry,ref=${CACHE_REF},mode=min" -f infra/docker/worker.Dockerfile -t "${REGISTRY}/${IMAGE_NAME}:${VERSION}" --push . - - echo "Production Worker image pushed: ${REGISTRY}/${IMAGE_NAME}:${VERSION}" - - ' +echo +echo \"WORKER image pushed: ${IMAGE_TAG}\" +" - name: Job duration summary if: always() shell: sh @@ -978,26 +1006,20 @@ 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 +REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\" +IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-web:${GITHUB_SHA}\" +CACHE_REF=\"${REGISTRY}/web-cache:${GITHUB_REF_NAME}\" - REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas" +bash scripts/ci/docker_build_push.sh \ + infra/docker/web.Dockerfile \ + \"${IMAGE_TAG}\" \ + \"${CACHE_REF}\" \ + APP_VERSION=\"${GITHUB_SHA}\" - IMAGE_NAME="xiaoxia-saas-web" - - VERSION="${GITHUB_REF_NAME}" - - CACHE_REF="${REGISTRY}/web-cache:main" - - NGINX_CONF="infra/docker/nginx-production.conf" - - - echo "Building Production Web image: ${VERSION}" - - docker buildx build --cache-from "type=registry,ref=${CACHE_REF},ignore-error=true" --cache-to "type=registry,ref=${CACHE_REF},mode=max" -f infra/docker/web-artifact.Dockerfile --build-arg "NGINX_CONF=${NGINX_CONF}" -t "${REGISTRY}/${IMAGE_NAME}:${VERSION}" --push . - - echo "Production Web image pushed: ${REGISTRY}/${IMAGE_NAME}:${VERSION}" - - ' +echo +echo \"WEB image pushed: ${IMAGE_TAG}\" +" - name: Cleanup old Docker images if: always() shell: sh diff --git a/scripts/ci/docker_build_push.sh b/scripts/ci/docker_build_push.sh new file mode 100755 index 000000000..84fb2c891 --- /dev/null +++ b/scripts/ci/docker_build_push.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# 通用Docker镜像构建+推送脚本(构建与缓存解耦) +# 用法: docker_build_push.sh [build_arg...] +set -eu + +DOCKERFILE="$1" +IMAGE_TAG="$2" +CACHE_REF="$3" +shift 3 +BUILD_ARGS="" +for arg in "$@"; do + BUILD_ARGS="$BUILD_ARGS --build-arg $arg" +done + +if ! docker buildx inspect ci-builder > /dev/null 2>&1; then + docker buildx create --use --name ci-builder --driver docker-container + echo "Created ci-builder" +else + docker buildx use ci-builder + echo "Using existing ci-builder" +fi +docker buildx inspect --bootstrap + +CACHE_FROM="type=registry,ref=${CACHE_REF},ignore-error=true" + +echo "=== Step 1: Build & push image (read-only cache) ===" +docker buildx build \ + $BUILD_ARGS \ + --cache-from "${CACHE_FROM}" \ + -f "${DOCKERFILE}" \ + -t "${IMAGE_TAG}" \ + --push \ + . + +echo "Image pushed: ${IMAGE_TAG}" + +echo "" +echo "=== Step 2: Push cache (best effort, retries 3x) ===" +CACHE_TO="type=registry,ref=${CACHE_REF},mode=max,compression=zstd" + +MAX_RETRIES=3 +SUCCESS=0 +for attempt in $(seq 1 $MAX_RETRIES); do + echo "Cache push attempt $attempt/$MAX_RETRIES" + if docker buildx build \ + $BUILD_ARGS \ + --cache-from "${CACHE_FROM}" \ + --cache-to "${CACHE_TO}" \ + -f "${DOCKERFILE}" \ + -t "${IMAGE_TAG}" \ + --push \ + .; then + echo "Cache pushed (attempt $attempt)" + SUCCESS=1 + break + else + echo "Cache push failed (attempt $attempt)" + if [ $attempt -lt $MAX_RETRIES ]; then + WAIT=$((attempt * 5)) + echo "Retrying in ${WAIT}s..." + sleep $WAIT + fi + fi +done + +if [ $SUCCESS -eq 0 ]; then + echo "WARNING: Cache push failed after $MAX_RETRIES attempts (non-fatal)" +fi + +echo "" +echo "Build completed: ${IMAGE_TAG}"