29ca51da9c
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 14s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m0s
CI/CD Pipeline / Integration Tests (push) Successful in 1m32s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m40s
CI/CD Pipeline / Validate - Style (push) Successful in 2m15s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m16s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m44s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 2m12s
CI/CD Pipeline / Validate - Security (push) Successful in 5m30s
CI/CD Pipeline / Unit Tests (push) Successful in 7m44s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Failing after 126h13m58s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 126h19m7s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 126h21m46s
CI/CD Pipeline / PR Build Web Image (push) Failing after 126h21m33s
CI/CD Pipeline / PR Build API Image (push) Failing after 126h21m34s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 126h18m54s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 126h18m54s
CI/CD Pipeline / CI Gate (push) Failing after 126h13m45s
CI/CD Pipeline / Build Production API Image (push) Failing after 126h13m45s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 126h21m35s
CI/CD Pipeline / Deploy Production (push) Failing after 126h13m42s
CI/CD Pipeline / Canary Release to Production (push) Failing after 126h13m42s
CI/CD Pipeline / Build Production Web Image (push) Failing after 126h13m45s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 126h16m37s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 126h16m37s
CI/CD Pipeline / Frontend Lint (push) Failing after 126h21m29s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 126h51m43s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
172 lines
5.4 KiB
Bash
Executable File
172 lines
5.4 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
# ============================================
|
|
# Staging 部署脚本 - Registry 方式
|
|
# 用法:IMAGE_TAG=<sha|version> REGISTRY_TOKEN=<token> sh deploy-staging.sh
|
|
# ============================================
|
|
|
|
IMAGE_TAG="${IMAGE_TAG:-}"
|
|
REGISTRY="${REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}"
|
|
REGISTRY_USER="${REGISTRY_USER:-xiaoxia}"
|
|
REGISTRY_TOKEN="${REGISTRY_TOKEN:-}"
|
|
|
|
ENV_FILE="${ENV_FILE:-/var/lib/xiaoxia-saas-staging/.env}"
|
|
COMPOSE_DIR="${COMPOSE_DIR:-/var/lib/xiaoxia-saas-staging/repo/infra/docker}"
|
|
GENERATED_DIR="${GENERATED_DIR:-/var/lib/xiaoxia-saas-staging/generated}"
|
|
|
|
if [ -z "$IMAGE_TAG" ]; then
|
|
echo "ERROR: IMAGE_TAG is required"
|
|
exit 1
|
|
fi
|
|
|
|
test -f "$ENV_FILE"
|
|
mkdir -p "$GENERATED_DIR"
|
|
|
|
# ---- 登录 Registry ----
|
|
if [ -n "$REGISTRY_TOKEN" ]; then
|
|
echo "Logging in to registry: $REGISTRY"
|
|
printf %s "$REGISTRY_TOKEN" | docker login "$(echo $REGISTRY | cut -d/ -f1)" -u "$REGISTRY_USER" --password-stdin 2>/dev/null || {
|
|
echo "WARN: docker login failed, will try to pull anyway"
|
|
}
|
|
fi
|
|
|
|
# ---- Pull 三镜像 ----
|
|
REGISTRY_API="${REGISTRY}/xiaoxia-saas-api:${IMAGE_TAG}"
|
|
REGISTRY_WORKER="${REGISTRY}/xiaoxia-saas-worker:${IMAGE_TAG}"
|
|
REGISTRY_WEB="${REGISTRY}/xiaoxia-saas-web:${IMAGE_TAG}"
|
|
|
|
LOCAL_API="${REGISTRY}/xiaoxia-saas-api:staging"
|
|
LOCAL_WORKER="${REGISTRY}/xiaoxia-saas-worker:staging"
|
|
LOCAL_WEB="${REGISTRY}/xiaoxia-saas-web:staging"
|
|
|
|
echo "Pulling API image..."
|
|
docker pull "$REGISTRY_API"
|
|
echo "Pulling Worker image..."
|
|
docker pull "$REGISTRY_WORKER"
|
|
echo "Pulling Web image..."
|
|
docker pull "$REGISTRY_WEB"
|
|
|
|
# ---- Re-tag 成本地名 ----
|
|
docker tag "$REGISTRY_API" "$LOCAL_API"
|
|
docker tag "$REGISTRY_WORKER" "$LOCAL_WORKER"
|
|
docker tag "$REGISTRY_WEB" "$LOCAL_WEB"
|
|
echo "All images pulled and tagged."
|
|
|
|
# ---- 确保基础设施容器在运行 ----
|
|
for c in xiaoxia-postgres-staging xiaoxia-redis-staging; do
|
|
if ! docker inspect "$c" >/dev/null 2>&1; then
|
|
echo "ERROR: Required container not found: $c"
|
|
exit 1
|
|
fi
|
|
state=$(docker inspect -f {{.State.Status}} "$c")
|
|
if [ "$state" != "running" ]; then
|
|
echo "ERROR: Container not running: $c ($state)"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# ---- 确保 staging 网络存在 ----
|
|
docker network create xiaoxia-net-staging 2>/dev/null || true
|
|
|
|
# ---- 执行数据库 Migration ----
|
|
echo "Running database migrations..."
|
|
docker run --rm --env-file "$ENV_FILE" --network xiaoxia-net-staging "$LOCAL_API" sh -c "cd /app && alembic upgrade head"
|
|
echo "Migrations completed."
|
|
|
|
# ---- 停止旧容器 ----
|
|
docker rm -f xiaoxia-api-staging 2>/dev/null || true
|
|
docker rm -f xiaoxia-worker-staging 2>/dev/null || true
|
|
docker rm -f xiaoxia-web-staging 2>/dev/null || true
|
|
|
|
# ---- 启动 API ----
|
|
echo "Starting API container..."
|
|
docker run -d \
|
|
--name xiaoxia-api-staging \
|
|
--env-file "$ENV_FILE" \
|
|
--network xiaoxia-net-staging \
|
|
-p 127.0.0.1:8000:8000 \
|
|
-e APP_ENV=staging \
|
|
-e APP_VERSION="$IMAGE_TAG" \
|
|
-e GENERATED_FILES_DIR=/app/generated \
|
|
-e GENERATED_FILES_URL_PREFIX=/generated-files \
|
|
-v "$GENERATED_DIR:/app/generated" \
|
|
--restart unless-stopped \
|
|
--label com.centurylinklabs.watchtower.enable=true \
|
|
--health-cmd "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=5)\"" \
|
|
--health-interval 30s \
|
|
--health-timeout 10s \
|
|
--health-retries 3 \
|
|
--health-start-period 40s \
|
|
"$LOCAL_API"
|
|
|
|
# ---- 启动 Worker ----
|
|
echo "Starting Worker container..."
|
|
docker run -d \
|
|
--name xiaoxia-worker-staging \
|
|
--env-file "$ENV_FILE" \
|
|
--network xiaoxia-net-staging \
|
|
-e APP_ENV=staging \
|
|
-e APP_VERSION="$IMAGE_TAG" \
|
|
-e WORKER_CONCURRENCY="${WORKER_CONCURRENCY:-4}" \
|
|
-e GENERATION_CONCURRENCY="${GENERATION_CONCURRENCY:-2}" \
|
|
-e WORKER_MAX_TASKS_PER_CHILD=100 \
|
|
-e GENERATED_FILES_DIR=/app/generated \
|
|
-e GENERATED_FILES_URL_PREFIX=/generated-files \
|
|
-v "$GENERATED_DIR:/app/generated" \
|
|
--restart unless-stopped \
|
|
--label com.centurylinklabs.watchtower.enable=true \
|
|
--health-cmd "sh -c \"grep -lq celery /proc/[0-9]*/cmdline 2>/dev/null || exit 1\"" \
|
|
--health-interval 30s \
|
|
--health-timeout 10s \
|
|
--health-retries 3 \
|
|
--health-start-period 30s \
|
|
"$LOCAL_WORKER"
|
|
|
|
# ---- 启动 Web ----
|
|
echo "Starting Web container..."
|
|
docker rm -f xiaoxia-web-staging 2>/dev/null || true
|
|
docker run -d \
|
|
--name xiaoxia-web-staging \
|
|
--network xiaoxia-net-staging \
|
|
-p 127.0.0.1:3001:80 \
|
|
--restart unless-stopped \
|
|
--label com.centurylinklabs.watchtower.enable=true \
|
|
-e APP_ENV=staging \
|
|
--health-cmd "wget --spider -q http://127.0.0.1:80" \
|
|
--health-interval 30s \
|
|
--health-timeout 5s \
|
|
--health-retries 3 \
|
|
"$LOCAL_WEB"
|
|
|
|
# ---- 等待 API 健康 ----
|
|
echo "Waiting for API to become healthy..."
|
|
i=0
|
|
while [ "$i" -lt 30 ]; do
|
|
if curl -sf --max-time 5 http://127.0.0.1:8000/health >/dev/null 2>&1; then
|
|
echo "API is healthy!"
|
|
break
|
|
fi
|
|
i=$((i + 1))
|
|
echo " Waiting... ($i/30)"
|
|
sleep 2
|
|
done
|
|
|
|
if [ "$i" -ge 30 ]; then
|
|
echo "ERROR: API did not become healthy within 60s"
|
|
docker logs --tail 30 xiaoxia-api-staging
|
|
exit 1
|
|
fi
|
|
|
|
# ---- 清理旧镜像 ----
|
|
docker image prune -af --filter "until=72h" 2>/dev/null || true
|
|
|
|
echo ""
|
|
echo "=== Staging deployment complete ==="
|
|
echo "API: http://127.0.0.1:8000"
|
|
echo "Web: http://127.0.0.1:3001"
|
|
echo "Version: $IMAGE_TAG"
|
|
docker ps --format "table {{.Names}}\t{{.Status}}" | grep staging
|
|
|
|
# Watchtower auto-update: 容器加com.centurylinklabs.watchtower.enable=true标签,用:staging tag启动
|