cd485a6370
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m47s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m35s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m51s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m13s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m6s
CI/CD Pipeline / Integration Tests (push) Successful in 2m15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 11m37s
CI/CD Pipeline / Unit Tests (push) Successful in 11m44s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 27m9s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m50s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 40s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m55s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m58s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
117 lines
3.7 KiB
Bash
Executable File
117 lines
3.7 KiB
Bash
Executable File
#!/bin/bash
|
||
# 通用Docker镜像构建+推送脚本(local cache为主 + registry cache共享)
|
||
# 用法: docker_build_push.sh [--no-cache] <Dockerfile> <image_tag> <cache_ref> [build_arg...]
|
||
set -eu
|
||
|
||
# 单次 build 超时时间(秒),防止 docker buildx build 无限挂起
|
||
BUILD_TIMEOUT=1500
|
||
|
||
NO_CACHE_FLAG=""
|
||
if [ "$1" = "--no-cache" ]; then
|
||
NO_CACHE_FLAG="--no-cache"
|
||
shift
|
||
echo "模式: --no-cache (不使用缓存,全新构建)"
|
||
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
|
||
|
||
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_ref中提取缓存名称(如 api-cache:develop -> api-cache-develop)
|
||
CACHE_NAME=$(echo "$CACHE_REF" | tr '/' '_' | tr ':' '-')
|
||
LOCAL_CACHE_DIR="/tmp/buildx-cache/${CACHE_NAME}"
|
||
|
||
mkdir -p "$LOCAL_CACHE_DIR"
|
||
|
||
# 缓存源:local优先(带自动修复),registry兜底读写
|
||
# 本地缓存损坏时自动清理后重试,避免snapshot not found导致构建全挂
|
||
build_with_cache_retry() {
|
||
local attempt=1
|
||
local max_attempts=2
|
||
while [ $attempt -le $max_attempts ]; do
|
||
local build_output
|
||
local exit_code
|
||
set +e
|
||
build_output=$(timeout ${BUILD_TIMEOUT} docker buildx build \
|
||
$NO_CACHE_FLAG \
|
||
$BUILD_ARGS \
|
||
--cache-from "type=local,src=${LOCAL_CACHE_DIR}" \
|
||
--cache-from "type=registry,ref=${CACHE_REF}" \
|
||
--cache-to "type=local,dest=${LOCAL_CACHE_DIR},mode=max" \
|
||
--cache-to "type=registry,ref=${CACHE_REF},mode=max,ignore-error=true" \
|
||
-f "${DOCKERFILE}" \
|
||
-t "${IMAGE_TAG}" \
|
||
--push \
|
||
. 2>&1)
|
||
exit_code=$?
|
||
set -e
|
||
if [ $exit_code -eq 0 ]; then
|
||
echo "$build_output"
|
||
return 0
|
||
fi
|
||
# 超时退出(exit code 124)
|
||
if [ $exit_code -eq 124 ]; then
|
||
echo "❌ Docker build TIMEOUT after ${BUILD_TIMEOUT}s - build hung and was killed"
|
||
echo "$build_output" | tail -20
|
||
return $exit_code
|
||
fi
|
||
# 检测到缓存损坏类错误,清掉本地缓存重试
|
||
if echo "$build_output" | grep -qE "parent snapshot.*not found|snapshot.*does not exist|cache.*corrupt|failed to compute cache key"; then
|
||
echo "$build_output"
|
||
echo ""
|
||
echo "⚠️ Local cache appears corrupted, cleaning up and retrying (attempt $attempt/$max_attempts)..."
|
||
rm -rf "${LOCAL_CACHE_DIR}"
|
||
mkdir -p "${LOCAL_CACHE_DIR}"
|
||
# 清理buildx builder的内部snapshot状态
|
||
docker buildx prune -f -a > /dev/null 2>&1 || true
|
||
attempt=$((attempt + 1))
|
||
else
|
||
# 非缓存类错误,直接输出并返回
|
||
echo "$build_output"
|
||
return $exit_code
|
||
fi
|
||
done
|
||
# 重试完还是失败,不用本地缓存最后试一次(只从registry读)
|
||
echo "⚠️ All cached attempts failed, building without local cache..."
|
||
timeout ${BUILD_TIMEOUT} docker buildx build \
|
||
$NO_CACHE_FLAG \
|
||
$BUILD_ARGS \
|
||
--cache-from "type=registry,ref=${CACHE_REF}" \
|
||
--cache-to "type=local,dest=${LOCAL_CACHE_DIR},mode=max" \
|
||
--cache-to "type=registry,ref=${CACHE_REF},mode=max,ignore-error=true" \
|
||
-f "${DOCKERFILE}" \
|
||
-t "${IMAGE_TAG}" \
|
||
--push \
|
||
.
|
||
}
|
||
|
||
echo "=== Step 1: Build & push image (local cache + registry cache, with auto-repair) ==="
|
||
echo "Local cache: ${LOCAL_CACHE_DIR}"
|
||
echo "Registry cache: ${CACHE_REF}"
|
||
echo "Build timeout: ${BUILD_TIMEOUT}s"
|
||
echo ""
|
||
|
||
build_with_cache_retry
|
||
|
||
echo ""
|
||
echo "Image pushed: ${IMAGE_TAG}"
|
||
echo "Local cache updated"
|
||
echo "Registry cache updated (if supported)"
|
||
|
||
echo ""
|
||
echo "Build completed: ${IMAGE_TAG}"
|