From 1e2a05102b435bcb7322e198db0647043d4a5956 Mon Sep 17 00:00:00 2001 From: Muhammadcell Date: Sun, 7 Jun 2026 13:34:17 +0500 Subject: [PATCH] ci(cd): remove staging deployment route --- .github/workflows/cd.yml | 140 +++------------------------------------ docs/README.md | 2 +- docs/deployment/CD.md | 24 +++---- 3 files changed, 19 insertions(+), 147 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 4959fac..2672955 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,24 +1,22 @@ name: CD # ────────────────────────────────────────────────────────────────────────────── -# Deployment gates stay explicit so staging, smoke checks and production rollout -# remain auditable from a single workflow. +# Deployment gates stay explicit so image publication, production rollout and +# rollback remain auditable from a single workflow. # # Triggered only after CI passes on main. # Workflow: -# build-push → deploy-staging → smoke-staging → [manual approval] -# → deploy-production -# → smoke-production +# build-push → [manual approval] → deploy-production +# → smoke-production # # Rollback is a manually dispatched workflow_dispatch job that redeploys the # previous image tag stored in the GitHub deployment environment. # # Required GitHub secrets: -# SSH_HOST_STAGING — IP / hostname of staging server # SSH_HOST_PRODUCTION — IP / hostname of production server -# SSH_USER — SSH user on both servers +# SSH_USER — SSH user on the production server # SSH_PRIVATE_KEY — Private key (no passphrase) -# SSH_KNOWN_HOSTS — Output of `ssh-keyscan ` for both servers +# SSH_KNOWN_HOSTS — Output of `ssh-keyscan ` # POSTGRES_PASSWORD — injected via Docker env on deploy # REDIS_PASSWORD — injected via Docker env on deploy # JWT_SECRET — injected via Docker env on deploy @@ -40,9 +38,9 @@ on: environment: description: 'Target environment' required: true - default: staging + default: production type: choice - options: [staging, production] + options: [production] image_tag: description: 'Image tag to deploy (default: latest sha on main)' required: false @@ -126,26 +124,16 @@ jobs: runs-on: ubuntu-latest outputs: production_ready: ${{ steps.check.outputs.production_ready }} - staging_ready: ${{ steps.check.outputs.staging_ready }} steps: - name: Check deployment settings id: check env: DEPLOY_PATH: ${{ vars.DEPLOY_PATH }} SSH_HOST_PRODUCTION: ${{ secrets.SSH_HOST_PRODUCTION }} - SSH_HOST_STAGING: ${{ secrets.SSH_HOST_STAGING }} SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} SSH_USER: ${{ secrets.SSH_USER }} run: | - staging_ready=true - for name in DEPLOY_PATH SSH_HOST_STAGING SSH_KNOWN_HOSTS SSH_PRIVATE_KEY SSH_USER; do - if [[ -z "${!name}" ]]; then - echo "::notice::${name} is not configured; staging SSH deploy will be skipped" - staging_ready=false - fi - done - production_ready=true for name in DEPLOY_PATH SSH_HOST_PRODUCTION SSH_KNOWN_HOSTS SSH_PRIVATE_KEY SSH_USER; do if [[ -z "${!name}" ]]; then @@ -154,118 +142,8 @@ jobs: fi done - echo "staging_ready=${staging_ready}" >> "$GITHUB_OUTPUT" echo "production_ready=${production_ready}" >> "$GITHUB_OUTPUT" - # ── Deploy to staging ───────────────────────────────────────────────────── - deploy-staging: - name: Deploy to staging - needs: [guard, build-push, deploy-config] - runs-on: ubuntu-latest - if: ${{ needs.deploy-config.outputs.staging_ready == 'true' }} - environment: - name: staging - url: https://staging.metrixplatform.com - steps: - - uses: actions/checkout@v4 - - - name: Validate staging deploy settings - env: - DEPLOY_PATH: ${{ vars.DEPLOY_PATH }} - SSH_HOST: ${{ secrets.SSH_HOST_STAGING }} - SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - SSH_USER: ${{ secrets.SSH_USER }} - run: | - missing=0 - for name in DEPLOY_PATH SSH_HOST SSH_KNOWN_HOSTS SSH_PRIVATE_KEY SSH_USER; do - if [[ -z "${!name}" ]]; then - echo "::error::${name} is not configured for staging deployment" - missing=1 - fi - done - exit "$missing" - - - name: Write SSH key - env: - SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - run: | - mkdir -p ~/.ssh - printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key - chmod 600 ~/.ssh/deploy_key - printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts - - - name: Deploy to staging server - env: - DEPLOY_PATH: ${{ vars.DEPLOY_PATH }} - IMAGE_PREFIX: ${{ env.IMAGE_PREFIX }} - IMAGE_TAG: ${{ needs.guard.outputs.image_tag }} - SSH_HOST: ${{ secrets.SSH_HOST_STAGING }} - SSH_USER: ${{ secrets.SSH_USER }} - run: | - ssh_target="${SSH_USER}@${SSH_HOST}" - ssh -i ~/.ssh/deploy_key \ - -o StrictHostKeyChecking=yes \ - "$ssh_target" \ - "DEPLOY_PATH=${DEPLOY_PATH@Q} IMAGE_PREFIX=${IMAGE_PREFIX@Q} IMAGE_TAG=${IMAGE_TAG@Q} bash -s" << 'ENDSSH' - set -euo pipefail - cd "$DEPLOY_PATH/apps/bot" - - # Pull new images. - echo "${{ secrets.GITHUB_TOKEN }}" | \ - docker login ghcr.io -u ${{ github.actor }} --password-stdin - - TAG="${IMAGE_TAG}" - - for svc in admin-service analytics-service booking-service bot-gateway \ - calendar-service notification-service payment-service \ - security-service worker-service; do - docker pull "${IMAGE_PREFIX}/${svc}:${TAG}" - done - - # Write current tag for rollback reference. - echo "${TAG}" > .last-deployed-tag - - # Rolling restart: update each service image reference and redeploy. - export IMAGE_TAG="${TAG}" - docker compose up -d --no-build \ - admin-service analytics-service booking-service bot-gateway \ - calendar-service notification-service payment-service \ - security-service worker-service - - # Wait for all containers to be healthy (max 120 s). - deadline=$(($(date +%s) + 120)) - while [[ $(date +%s) -lt $deadline ]]; do - unhealthy=$(docker compose ps --format json \ - | jq -r 'select(.Health != "healthy" and .Health != "") | .Name') - [[ -z "$unhealthy" ]] && echo "All services healthy" && break - echo "Waiting for: $unhealthy" - sleep 5 - done - ENDSSH - - # ── Smoke test staging ──────────────────────────────────────────────────── - smoke-staging: - name: Smoke test staging - needs: deploy-staging - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - - run: npm ci - - - name: Run smoke tests against staging - env: - BOOKING_SERVICE_URL: https://staging.metrixplatform.com/booking - INTEGRATION_TEST: 'true' - run: npm run test:integration -- --test-name-pattern="smoke" - # ── Manual approval gate before production ─────────────────────────────── # GitHub Environments with required reviewers provides the approval UI. # Set up: Settings → Environments → production → Required reviewers. @@ -273,7 +151,7 @@ jobs: # ── Deploy to production ────────────────────────────────────────────────── deploy-production: name: Deploy to production - needs: [guard, smoke-staging, deploy-config] + needs: [guard, build-push, deploy-config] runs-on: ubuntu-latest if: ${{ needs.deploy-config.outputs.production_ready == 'true' }} environment: diff --git a/docs/README.md b/docs/README.md index e829696..a060287 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,7 @@ Metrix — это система бронирования офисных рес - architecture/TRACING.md — распределённая трассировка (OpenTelemetry + Jaeger). - architecture/LOGGING.md — structured JSON logging, Loki/Vector и корреляция с traceId. - deployment/README.md — как запускать проект. -- deployment/CD.md — CD pipeline: staging → approval → production → rollback. +- deployment/CD.md — CD pipeline: build/push → approval → production → rollback. - operations/README.md — что делать при инцидентах. - testing/PRODUCTION_READINESS_TEST_REPORT.md — что уже проверяли. - testing/CONTRACT_TESTING.md — контрактные тесты между микросервисами. diff --git a/docs/deployment/CD.md b/docs/deployment/CD.md index 4de5faa..8cc2c5c 100644 --- a/docs/deployment/CD.md +++ b/docs/deployment/CD.md @@ -2,7 +2,8 @@ ## Обзор -CD pipeline автоматически доставляет каждый коммит в `main` на production после прохождения CI и smoke-тестов на staging. Ручное действие не требуется, но есть два явных gates. +CD pipeline автоматически собирает Docker images после прохождения CI и +доставляет их на production через GitHub Environment approval gate. ``` CI (typecheck + tests + openapi + audit + contracts) @@ -11,12 +12,6 @@ CI (typecheck + tests + openapi + audit + contracts) build-push → GHCR (sha- + latest) │ ▼ - deploy-staging (SSH + docker compose pull + rolling restart) - │ - ▼ - smoke-staging (integration tests против staging URL) - │ - ▼ [manual approval — GitHub Environment: production] │ ▼ @@ -50,18 +45,18 @@ GHCR/Docker repository path должен быть lowercase, иначе `docker ## Окружения -В GitHub Settings → Environments нужно создать два окружения: +В GitHub Settings → Environments нужно создать production-окружение: | Окружение | Required reviewers | URL | |-------------|-------------------|----------------------------------| -| `staging` | нет | https://staging.metrixplatform.com | | `production`| ≥ 1 reviewer | https://metrixplatform.com | `production` с required reviewers — это и есть ручной approval gate. Если SSH secrets или `DEPLOY_PATH` не настроены, pipeline только соберёт и -опубликует GHCR images, а SSH deploy jobs будут skipped с notice в CD run. -Это защищает `main` от красного CD в репозиториях без подключённых серверов. +опубликует GHCR images, а production SSH deploy job будет skipped с notice в +CD run. Это защищает `main` от красного CD в репозиториях без подключённого +production-сервера. ## Необходимые секреты @@ -69,11 +64,10 @@ GHCR/Docker repository path должен быть lowercase, иначе `docker | Секрет | Описание | |------------------------|----------------------------------------------------| -| `SSH_HOST_STAGING` | IP или hostname staging-сервера | | `SSH_HOST_PRODUCTION` | IP или hostname production-сервера | -| `SSH_USER` | SSH-пользователь на обоих серверах | +| `SSH_USER` | SSH-пользователь на production-сервере | | `SSH_PRIVATE_KEY` | Приватный ключ (без passphrase) | -| `SSH_KNOWN_HOSTS` | `ssh-keyscan staging_host production_host` | +| `SSH_KNOWN_HOSTS` | `ssh-keyscan production_host` | | `POSTGRES_PASSWORD` | Передаётся в docker env при деплое | | `REDIS_PASSWORD` | Передаётся в docker env при деплое | | `JWT_SECRET` | Передаётся в docker env при деплое | @@ -108,6 +102,6 @@ Rollback происходит автоматически, если smoke-тес ## Добавление нового сервиса 1. Добавить имя сервиса в матрицу `build-push` в `cd.yml`. -2. Добавить имя сервиса в команды `docker compose up` в шагах `deploy-staging` и `deploy-production`. +2. Добавить имя сервиса в команды `docker compose up` в шаге `deploy-production`. 3. Добавить `image: ${IMAGE_PREFIX:-ghcr.io/hqzdev/metrix}/:${IMAGE_TAG:-latest}` в `docker-compose.yml`. 4. Добавить `OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318` в environment нового сервиса в `docker-compose.yml`.