Skip to Content
How-toДеплой сервиса

Как задеплоить сервис

Пошаговая процедура выкатки новой версии Go-сервиса в staging и prod. Охватывает pre-flight проверки, mechanics rolling update в Kubernetes, синхронизацию с миграциями, что смотреть после cutover. Откат — в отдельном how-to: rollback-deploy.

Handbook не описывает конкретный CI-пайплайн сервиса (имя job’ов, секреты, chart-ы в Helm) — это контекст каждого сервис-репо. Здесь — правила и чеклист, единые для всех сервисов.

Содержание

Модель деплоя

  • Unit деплоя — один Docker-образ сервиса. См. ../architecture-overview.
  • Стратегия — Kubernetes rolling update с maxSurge=1, maxUnavailable=0. Новая версия поднимается рядом со старой, трафик переключается после /readyz=200 на новом pod’е, старый pod уходит. Ноль downtime, если pod’ов >1.
  • Blue-green не используем как дефолт: rolling update справляется на наших объёмах и не требует удвоенного footprint’а инфраструктуры.
  • Canary — опциональный шаг для высокорисковых изменений (поведение под нагрузкой, новая зависимость, большая миграция). См. §Canary-паттерны.
  • Релизный триггер — git-tag vX.Y.Z в сервис-репо. Прямые push’и в main собирают staging-образ, но prod не деплоится без тега.

Пайплайн целиком

Rolling update в prod занимает 1–5 минут в зависимости от размера pool’а, warmup pod’а и terminationGracePeriodSeconds (см. ../conventions/shutdown).

Pre-flight checklist

Перед каждым prod-деплоем автор релиза (обычно — author главного PR из релиза) проходит:

  • PR (или серия PR) смержены в main; CI зелёный.
  • Образ собран и успешно запущен в staging не менее часа назад — прошёл /readyz, принял реальный трафик (или smoke test), Grafana не подсвечивает алерты.
  • Миграции (если есть) применены в staging из того же образа, что деплоим в prod. Up-миграции прошли без ошибок, down- миграции (где применимо) протестированы.
  • Миграции соответствуют expand-contract (см. ../conventions/db-pgx#expand-contract). Ни одна миграция в этом релизе не ломает предыдущую версию сервиса — только текущую или будущую. См. §Миграции.
  • В CHANGELOG.md сервис-репо добавлена запись: bullet’ы изменений, ссылки на PR, upgrade-notes (если есть).
  • Secret’ы / config-изменения (новые env-переменные) выкачены в секрет-менеджер заранее — до деплоя образа. См. ../conventions/configuration.
  • Новые Prometheus-alert’ы и dashboard’ы закоммичены в infra- репо и применены. Alert не должен появиться «после инцидента».
  • SLO-budget сервиса на последние 7 дней не пустой (>20% осталось). Пустой бюджет — повод отложить feature-релиз. См. ../conventions/slo-and-budget#error-budget.
  • Есть on-call дежурный на ближайший час (или автор релиза сам on-call).
  • Не пятница после 18:00 и не канун выходных / праздников. В эти окна деплой — только security-hotfix с одобрением lead- инженера.

Чеклист целиком — в ../checklists/production-ready для первого релиза сервиса; для последующих деплоев — этот сокращённый список.

Staging

# tag / merge в main → CI автоматически деплоит staging-образ # имя образа: ghcr.io/<org>/<svc>:<sha>

Ручная проверка после staging-деплоя:

curl -sf https://staging.<svc>.kazmaps.internal/healthz curl -sf https://staging.<svc>.kazmaps.internal/readyz curl -sf https://staging.<svc>.kazmaps.internal/metrics | head -30

Smoke test — минимум:

  1. Happy-path endpoint (GET /v1/<ресурс>/<id> или эквивалент) → 200.
  2. Auth-required endpoint с валидным токеном → 200.
  3. Auth-required endpoint без токена → 401.
  4. Один write-endpoint из релиза (если изменилась бизнес-логика).
  5. Прочитать новые метрики в staging-Grafana — появились ли, какие значения.

Минимум 1 час staging-burn-in’а перед prod-cutover’ом для обычных изменений, 24 часа — для изменений, затрагивающих outbox, consumer’ов, background worker’ов (нужно дать forwarder’у / consumer’у пройти заметный объём событий, чтобы поймать регрессии типа memory leak или медленного growth-lag’а).

Prod cutover

После tag vX.Y.Z:

  1. Наблюдение заранее открыто. Grafana-dashboard сервиса и dashboard SLO burn-rate (см. ../conventions/slo-and-budget#multi-window-multi-burn-rate-alerts) открыты на отдельном экране.

  2. Деплой тега. CI/CD раскатывает образ в prod через Helm / kustomize. Типичная команда (пример на GitOps-инструменте используется тот, что в infra-репо):

    # пример через Helm, реальные параметры — в infra-репо helm upgrade --install <svc> charts/<svc> \ --namespace backend \ --set image.tag=v1.4.2 \ --wait --timeout 5m
  3. Rolling mechanics. Kubernetes один за другим создаёт pod’ы новой версии, ждёт их /readyz=200, снимает трафик со старого pod’а через preStop + terminationGracePeriodSeconds (см. ../conventions/shutdown), убивает старый. Повторяется, пока все pod’ы не обновлены.

  4. Точка cut-over прошла, когда в kubectl get pods -l app=<svc> -n backend все реплики имеют новый image и статус Running / READY 1/1:

    kubectl get pods -l app=<svc> -n backend \ -o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\t"}{.status.phase}{"\n"}{end}'
  5. Отметить в runbook-чате. Одно сообщение: «deploy <svc> vX.Y.Z прошёл, watching SLO 30 мин». Не «раскатил, спасибо, всем хорошего дня».

Миграции и expand-contract

Порядок миграций и кода — всегда expand-contract (см. ../conventions/db-pgx#expand-contract). Для деплоя это значит:

  • Добавляем колонку / таблицу NOT NULL — в два релиза: сначала миграция ADD COLUMN NULLABLE + код, который пишет новое поле, выкатывается. Потом (следующий релиз / через сутки-неделю) миграция ALTER SET NOT NULL.
  • Удаляем колонку / таблицу — сначала релиз, где код перестал читать/писать колонку, затем — миграция DROP. Никогда не наоборот.
  • Переименование — expand + backfill + contract.

Практическая развилка на деплое:

  1. Миграция применяется перед новым кодом, если она expand (добавляет nullable) — старый код её переживёт, новый сможет использовать.
  2. Миграция применяется после нового кода, если она contract (удаляет / добавляет constraint) — старый код уже выключен.

В коде сервиса migrate-runner запускается на старте (см. ../how-to/add-migration), защищён advisory lock’ом. Первый pod новой версии применит expand- миграцию; остальные подхватят уже применённую схему.

Если миграция требует большого backfill’а (минуты-часы), она отделяется от деплоя — гонится отдельным CronJob / job’ом перед деплоем. Деплой ждёт её завершения.

Наблюдение после cutover

30 минут минимум автор релиза на связи, смотрит:

  1. SLO burn-rate (fast-burn / slow-burn, см. ../conventions/slo-and-budget). Любое срабатывание fast-burn alert’а через 5–15 мин после cutover’а — кандидат на откат, не на «подождём».

  2. Error-rate 5xx по endpoint’ам релиза. Базовое:

    sum by (route, code) (rate(http_requests_total{service="<svc>",code=~"5.."}[5m]))
  3. Латенси — p95/p99 по ключевым endpoint’ам. Регрессия на 30%+ — повод разобраться здесь и сейчас.

  4. Логи в Loki (см. read-logs) — нет ли новых level=error паттернов.

  5. Outbox / consumer freshnessoutbox_unpublished_rows, kafka_consumer_lag_messages. Если деплой затронул publisher или handler — следи за этими метриками прицельно.

  6. Ресурсы pod’а — CPU, RSS. Резкий рост RSS у новой версии = memory leak (см. ../troubleshooting/memory-leak).

Если за 30 минут ничего не деградировало — деплой успешен, уведомление в канал: «deploy <svc> vX.Y.Z: all green».

Если деградация — rollback-deploy.

Canary-паттерны

Rolling update по умолчанию — все pod’ы одновременно получают трафик по мере ready. Для высокорисковых изменений этого мало; нужен canary — часть трафика идёт на новую версию, большая часть — на старую, сравниваешь метрики двух групп.

Случаи, когда canary оправдан:

  • Поведение под нагрузкой непредсказуемо (новая зависимость, переработан hot-path).
  • Изменение в кросс-сервисном протоколе (новый envelope-header, изменение consumer-handler’а).
  • Большая миграция state-машины (версия саги, версия outbox- forwarder’а).

У нас canary реализуется через отдельный Deployment с меткой track=canary и replicas=1 на весь Service. Cilium Gateway API делит трафик по весам backend’ов в HTTPRoute / по HTTP-header’у (настройки в infra-репо).

Шаги:

  1. Выкатать canary Deployment с новой версией, track=canary.

  2. Перевести 5–10% трафика на canary (weighted routing в Cilium Gateway API или service-mesh, если был).

  3. Ждать 30–60 минут, сравнивать метрики canary vs stable по label’у track:

    sum by (track) (rate(http_requests_total{service="<svc>",code=~"5.."}[5m]))
  4. Регрессии нет → track=canary промотируется в stable (увеличить до 100%), старый Deployment удаляется. Есть регрессия → canary масштабируется до 0, trаffic возвращается на stable.

Canary — не замена rolling update, а дополнение. После успешного canary-окна всё равно идёт rolling update всех stable- pod’ов на новую версию.

Feature flags

Для изменений, затрагивающих бизнес-логику, работает правило code-path-gate: новая логика — под feature flag, включается через env / config. Это отделяет деплой от релиза:

  • День 1: деплой с новой логикой под флагом FEATURE_X=false — прод идёт по старому пути.
  • День N: переключение FEATURE_X=true — без нового деплоя образа, просто kubectl rollout restart после правки ConfigMap (или hot-reload, если сервис поддерживает).
  • Rollback фичи — снова FEATURE_X=false, даже без редеплоя.

Флаги живут в config-е сервиса, не в коде как const. Имена — FEATURE_<AREA>_<NAME> (FEATURE_SEARCH_RERANKER). После полного включения и успешного прогона 2 недель — флаг и старый код-путь удаляются отдельным PR.

Когда НЕ деплоить

  • Прод-инцидент активен (SEV-2 / SEV-3 открыты). Новый деплой маскирует симптомы первоначального, путает on-call.
  • SLO-budget сервиса сгорел (<10% осталось) и релиз не fix-related. Помощи от фичи = 0, риска = много.
  • CI зелёный «случайно» (часть тестов пропущена по skip / flakey). Разбираться до деплоя, не после.
  • Нет on-call’а на ближайший час.
  • Только что выкатился другой сервис, от которого есть runtime-зависимость. Подожди его стабилизации (30 мин) — проще разобраться, чей регресс.

Что не делать

  • kubectl edit deployment прямо в prod. Нефиксируется в git, следующий GitOps-sync откатит твою правку без предупреждения. Все изменения — через PR в infra-репо.
  • kubectl exec -it <pod> -- /bin/sh для «быстро поправить что-то в runtime». Никогда. Состояние pod’а расходится с образом, при рестарте теряется.
  • Пропускать staging «на маленьких PR». Маленьких PR в этом смысле не бывает — баг в одной строчке кладёт сервис не хуже большой рефакторной ветки.
  • Деплоить без тега в prod «вручную, SHA’ей». Теряется версионирование, следующий rollback не знает, куда откатывать.
  • Выкатывать миграцию contract вместе с кодом, который ещё использует старое поле. Rolling update → половина pod’ов на старом, половина на новом, старый обращается к несуществующему полю → 500. Всегда expand-первым-релизом.
  • Смешивать миграцию и bug-fix в одном релизе. При откате миграции придётся оставить; при форвард-fix’е сама миграция недоживёт до следующей итерации.

Связанные разделы

Last updated on