diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 167527a7b..398fcd465 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -130,7 +130,7 @@ jobs: -v /:/host \ docker:27-cli sh -lc ' chmod +x /host/var/lib/xiaoxia-saas-production/repo/infra/docker/deploy-production.sh && \ - /host/var/lib/xiaoxia-saas-production/repo/infra/docker/deploy-production.sh + RELEASE_VERSION="${GITHUB_REF_NAME}" /host/var/lib/xiaoxia-saas-production/repo/infra/docker/deploy-production.sh ' - name: Verify production health diff --git a/docs/PRODUCTION-RELEASE-CHECKLIST.md b/docs/PRODUCTION-RELEASE-CHECKLIST.md index 25aedbd40..6920a1f91 100644 --- a/docs/PRODUCTION-RELEASE-CHECKLIST.md +++ b/docs/PRODUCTION-RELEASE-CHECKLIST.md @@ -142,6 +142,20 @@ OSS 生命周期规则必须在云控制台或 IaC 中绑定到生产 bucket, ## 7. 发布执行 - 只能通过 `v*` tag 触发 production deploy。 +- 生产 API/Worker 必须使用预构建 runtime image tar,不允许生产机临时 build。 +- 在专用构建机执行: + +```bash +scripts/build_release_images.sh v0.1.x +``` + +- 将输出的 `dist/release-images/xiaoxia-runtime-images-v0.1.x.tar` 上传到: + +```bash +/var/lib/xiaoxia-saas-production/runtime-images-v0.1.x.tar +``` + +- Gitea production deploy 会把 `RELEASE_VERSION=${GITHUB_REF_NAME}` 传给 `infra/docker/deploy-production.sh`;如果缺少对应 runtime image tar,部署必须失败,禁止静默复用旧 API/Worker 镜像。 - 发布期间持续观察 Gitea Actions deploy log。 - 部署后确认: diff --git a/infra/docker/deploy-production.sh b/infra/docker/deploy-production.sh index a2a785c53..1326e497a 100755 --- a/infra/docker/deploy-production.sh +++ b/infra/docker/deploy-production.sh @@ -5,6 +5,12 @@ HOST_PREFIX="${HOST_PREFIX-/host}" ROOT_DIR="$HOST_PREFIX/var/lib/xiaoxia-saas-production/repo" COMPOSE_DIR="$ROOT_DIR/infra/docker" ENV_FILE="$HOST_PREFIX/var/lib/xiaoxia-saas-production/.env" +RELEASE_VERSION="${RELEASE_VERSION:-}" +RUNTIME_IMAGE_TAR="${RUNTIME_IMAGE_TAR:-}" + +if [ -n "$RELEASE_VERSION" ] && [ -z "$RUNTIME_IMAGE_TAR" ]; then + RUNTIME_IMAGE_TAR="$HOST_PREFIX/var/lib/xiaoxia-saas-production/runtime-images-$RELEASE_VERSION.tar" +fi ensure_container_running() { name="$1" @@ -40,6 +46,17 @@ ensure_container_running xiaoxia-postgres-production ensure_container_running xiaoxia-redis-production cd "$COMPOSE_DIR" +if [ -n "$RELEASE_VERSION" ]; then + if [ ! -f "$RUNTIME_IMAGE_TAR" ]; then + echo "Missing production runtime image artifact: $RUNTIME_IMAGE_TAR" + echo "Build it on a dedicated build host with scripts/build_release_images.sh $RELEASE_VERSION, then upload it before production deploy." + exit 1 + fi + docker load -i "$RUNTIME_IMAGE_TAR" + export API_IMAGE="xiaoxia-saas-api:$RELEASE_VERSION" + export WORKER_IMAGE="xiaoxia-saas-worker:$RELEASE_VERSION" +fi + export DOCKER_BUILDKIT=0 export COMPOSE_DOCKER_CLI_BUILD=0 export COMPOSE_PROJECT_NAME=xiaoxia-production-app @@ -51,6 +68,8 @@ if [ "${ALLOW_PRODUCTION_BUILDS:-false}" = "true" ]; then docker compose --env-file "$ENV_FILE" build --pull=false worker else echo "Skipping production API/worker image builds. Set ALLOW_PRODUCTION_BUILDS=true only on a dedicated build host." + docker image inspect "${API_IMAGE:-xiaoxia-saas-api:dev}" >/dev/null + docker image inspect "${WORKER_IMAGE:-xiaoxia-saas-worker:dev}" >/dev/null fi docker compose --env-file "$ENV_FILE" build --pull=false web docker compose --env-file "$ENV_FILE" run --rm --no-deps api sh -c ' diff --git a/tests/unit/test_release_scripts.py b/tests/unit/test_release_scripts.py index 5dad950fb..2acb05070 100644 --- a/tests/unit/test_release_scripts.py +++ b/tests/unit/test_release_scripts.py @@ -16,6 +16,7 @@ def test_gitea_production_deploy_uses_production_ports(): assert "WEB_PORT=3001" not in production_section assert "http://127.0.0.1:8001/health" in production_section + assert "RELEASE_VERSION=\"${GITHUB_REF_NAME}\"" in production_section assert "http://127.0.0.1:8000/health" not in production_section @@ -28,6 +29,11 @@ def test_deploy_production_uses_production_infra_and_project(): assert "WEB_DOCKERFILE=infra/docker/web-artifact.Dockerfile" in script assert "WEB_NGINX_CONF=infra/docker/nginx-production.conf" in script assert "Skipping production API/worker image builds" in script + assert "RELEASE_VERSION" in script + assert "RUNTIME_IMAGE_TAR" in script + assert "docker load -i \"$RUNTIME_IMAGE_TAR\"" in script + assert "docker image inspect \"${API_IMAGE:-xiaoxia-saas-api:dev}\"" in script + assert "docker image inspect \"${WORKER_IMAGE:-xiaoxia-saas-worker:dev}\"" in script assert "ALLOW_PRODUCTION_BUILDS=true" in script assert "xiaoxia-postgres-production" in script assert "xiaoxia-redis-production" in script