Перейти к содержанию

Production-ready чеклист

Пробегай полностью перед тем, как включать prod-трафик на новый сервис (или на крупное изменение существующего: смену схемы трафика, новый публичный endpoint с ожидаемой высокой нагрузкой, новую интеграцию). Список длинный — это осознанная цена работы в prod. Каждый пункт ссылается на источник стандарта; пропустить пункт без явного обоснования в PR-описании — не ok.

Если какой-то пункт не применим к твоему сервису — поставь галочку и напиши «N/A: потому что...». Галочка без пояснения = не проверено.

Код и сборка

  • Dockerfile multistage. Builder на golang:1.25-alpine, runtime на alpine:3.20 или gcr.io/distroless/static. См. ../how-to/add-new-service.md §5.
  • Non-root runtime. USER app с uid 10001 в Dockerfile.
  • Бинарь собран с CGO_ENABLED=0 и -ldflags="-s -w".
  • Image scanning прошёл без Critical/High (trivy image).
  • govulncheck ./... зелёный в CI.
  • SBOM сгенерирован (syft) на CI и прикреплён к release.
  • golangci-lint зелёный, без skip'ов.

Конфигурация

  • Все env-переменные задокументированы в .env.example с placeholder-значениями (change_me, _dev_). См. ../conventions/configuration.md.
  • Required-поля валидируются fail-fast на старте (required:"true" в envconfig + cfg.Validate() для кросс-полевых проверок).
  • Секреты из secret manager, не в docker-compose/manifest в явном виде.
  • Dual-key rotation поддержан для HMAC/JWT/internal токена: PRIMARY + PREVIOUS. См. ../how-to/rotate-jwt-key.md.
  • cfg.Redacted() маскирует секреты в лог на старте.

База данных

  • Миграции: up + down файлы, advisory lock в начале up.sql (см. ../how-to/add-migration.md).
  • Outbox-таблица создана, если сервис публикует события. Схема совпадает с watermill-sql/DefaultPostgreSQLSchema (см. ../patterns/outbox.md §Schema).
  • Индексы покрывают hot queries (EXPLAIN ANALYZE проверен, нет Seq Scan на больших таблицах).
  • Partitioning запланирован для таблиц с ожидаемым ростом > 10 млн строк (выкатывается отдельной миграцией до того, как таблица разрастётся).
  • Soft-delete через deleted_at там, где модель требует истории.
  • Cleanup outbox через CronJob в infra-репо, retention >= 7 дней (см. ../patterns/outbox.md §Cleanup).
  • Отдельный DB-user для сервиса, права только на свою схему. См. ../conventions/security.md.

HTTP

  • /healthz — liveness, простой 200 без проверки зависимостей. См. ../conventions/shutdown.md.
  • /readyz — readiness, проверяет pgxpool.Ping, redis.Ping, kafka.Ping. Возвращает 503, если хотя бы одна зависимость не отвечает, и при shutdown.
  • /metrics — Prometheus endpoint, без auth, не роутится gateway'ем наружу.
  • Timeout middleware на handler'е (30s максимум).
  • http.MaxBytesReader на body (обычно 64 KiB).
  • Rate limit на hot endpoints (auth, upload, search).
  • Input validation через validator на handler boundary.
  • mapServiceError централизованно мапит sentinel-ошибки в HTTP-статусы.
  • ReadHeaderTimeout выставлен (10s) на *http.Server.

События (Kafka)

  • Publisher через outbox, не прямой kafka.Publish. Запись в outbox в той же транзакции с бизнес-write (см. ../conventions/events.md, ../patterns/outbox.md).
  • Forwarder запущен как singleton per-service (replicas 1).
  • Consumer middleware stack в правильном порядке: CorrelationID → Recoverer → PoisonQueue → Retry → Deduplicator.
  • Events зарегистрированы в ../event-catalog.md.
  • DLQ-топики создаются автоматически при первом сообщении; алерт на rate(messages_poisoned_total[5m]) > 0 подключён.
  • Handler идемпотентен (UPSERT / unique constraint / Deduplicator). См. ../patterns/idempotent-consumer.md.
  • Partition key выставлен в envelope (Aggregate-Id через forwarder middleware) — гарантирует per-aggregate ordering.
  • Dedup TTL — 24h для обычных events, 7d для critical.

Observability

  • Logs: slog JSON → stdout, LOG_LEVEL=info по умолчанию. См. ../conventions/logging.md.
  • Structured fields: service, request_id, correlation_id, trace_id, user_id (если authenticated), route.
  • PII masking helpers подключены (pkg/pii/), email/phone/ token в открытом виде в логи не попадают.
  • Prometheus metrics: базовый набор (HTTP duration, Go runtime, process) + custom метрики на бизнес-события.
  • OTel traces: otelhttp.NewMiddleware на сервере, otelhttp.NewTransport на HTTP-клиенте, otelpgx.NewTracer на pgxpool, otelMiddleware.Trace на Kafka router. См. ../conventions/observability.md.
  • Grafana dashboard создан в infra-репо (минимум: HTTP QPS, error rate, p99 latency, consumer lag если применимо).
  • SLO определены (минимум baseline: HTTP availability 99.9%, p99 latency < N ms на критичных endpoint'ах).
  • Alert rules в infra-репо для всех critical метрик. См. ../how-to/add-metric-and-alert.md.
  • Runbook-ссылки в alert'ах ведут на актуальные страницы troubleshooting.

Shutdown

  • signal.NotifyContext в main.go на SIGINT/SIGTERM. См. ../conventions/shutdown.md.
  • Порядок shutdown: HTTP → Kafka consumer (router.Close) → background workers → Kafka publisher → DB pool → Redis.
  • Shutdown timeout 30s (context.WithTimeout), os.Exit(1) при превышении.
  • /readyz переключается в 503 при получении сигнала (до начала закрытия HTTP-сервера).
  • /healthz остаётся 200 до самого конца.
  • k8s terminationGracePeriodSeconds: 45 (на 10–15s больше shutdown-таймаута).
  • Все worker'ы смотрят на <-ctx.Done() и имеют финальный drain.
  • Integration-тест shutdown присутствует (in-flight request завершается, readyz=503 сразу).

Тесты

  • Unit-coverage internal/service/** >= 70%. См. ../conventions/testing.md.
  • Integration через testcontainers для repository-слоя.
  • Handler-tests через httptest для всех публичных endpoint'ов.
  • Event-tests через gochannel для всех handler'ов Watermill-router'а.
  • Race detector зелёный в make test и в CI.
  • Load test — провёден 1h sustained на expected QPS, есть отчёт (QPS, p99, error rate, outbox lag, memory).
  • Chaos-test (минимум: убить один pod, убить одну реплику БД, изолировать Kafka на 30 секунд) — сервис восстанавливается автоматически.

Безопасность

  • Auth: сервис валидирует HMAC headers от Gateway (не парсит JWT сам, кроме user-сервиса). См. ../conventions/security.md.
  • CORS не настроен на backend (это задача Gateway).
  • HTTPS only на входе (TLS terminate на Gateway); внутри кластера — mTLS через service-mesh либо plain HTTP под NetworkPolicy.
  • /internal/* защищён InternalToken + subtle.ConstantTimeCompare; NetworkPolicy ограничивает доступ до белого списка сервисов.
  • Rotation plan для HMAC/JWT/internal token задокументирован — см. ../how-to/rotate-jwt-key.md.
  • SQL injection невозможен — все запросы через pgx- плейсхолдеры, никакого fmt.Sprintf с user input.
  • Cookies (если применимо) — SameSite=Strict; Secure; HttpOnly.
  • Argon2id для паролей (user-сервис); нет bcrypt/scrypt/ MD5/SHA-*.
  • crypto/rand для всех случайностей, не math/rand.

Операционная готовность

  • Runbook для типовых инцидентов существует: migration-fails, consumer-stuck, outbox-lag. См. ../troubleshooting/.
  • On-call rotation включает этот сервис, владелец задокументирован в ../onboarding/04-who-owns-what.md.
  • Paging-система (Grafana OnCall / PagerDuty / аналогичный) настроена и получает alerts.
  • Incident-response checklist пройден dry run'ом (тест alert → тест эскалации → тест коммуникации).
  • Backup план: pgBackRest PITR для Postgres, mirror для MinIO (если хранит медиа).
  • DR drill запланирован в первые 60 дней prod-жизни сервиса — минимум восстановление из backup в staging.

Документация

  • README сервиса полный: что делает, как запустить, как тестировать, список env.
  • API описан (OpenAPI в api/openapi.yaml или inline- документация).
  • Сервис зарегистрирован в ../services-catalog.md: имя, owner, порт, публикуемые/потребляемые события, HTTP API.
  • ../onboarding/04-who-owns-what.md обновлён с owner и контактами.
  • Каждый новый паттерн (если есть), не описанный в handbook, — обсуждён с lead-инженером, follow-up task на внесение в conventions/ или patterns/.

Rollout

  • Feature flag (если возможно) для постепенного включения: 1% → 5% → 25% → 100%. Сервис-к-сервису эта схема работает через Gateway-routing или через env-toggle с реплик-процентом.
  • Canary deployment — первая версия идёт в отдельный namespace / deployment и получает subset трафика.
  • Rollback plan задокументирован: какой тег откатывает, какой миграции не нужно откатывать (forward-only), критерии отката (error rate > X% на Y минут → rollback).
  • Communication plan: команда предупреждена, support-канал знает о запуске, incident commander на дежурстве в первые 24 часа после включения.

Как пользоваться чеклистом

Этот список — не формальность, а инструмент: каждый пункт закрывает конкретный класс prod-инцидентов, с которыми мы уже сталкивались в других сервисах.

  • N/A — пиши пояснение. «N/A: сервис не публикует события», «N/A: нет публичного API, только internal». Голая галочка без пояснения читается как «забыл».
  • Чеклист проходится в PR, не в голове. Скопируй список в описание PR о готовности сервиса к prod, отмечай по ходу. Так ревьюер видит прогресс, ты не забываешь пункты.
  • Расхождение = не готов. Один невыполненный пункт без N/A — сервис не готов, даже если «всё остальное работает». Единичные дыры — это как раз то, через что проходит первый prod-инцидент.
  • Обновляй чеклист. Если нашли класс проблем, не покрытый списком — PR в handbook, добавь пункт. Следующий сервис не должен наступать на те же грабли.

Первые 72 часа после включения

Даже с пройденным чеклистом первые сутки — повышенный риск. Рекомендации:

  • Monitoring на экране. Grafana-дашборд сервиса открыт на экране on-call'а первые 24 часа, p99 latency и error rate — в фокусе.
  • Feature flag в готовности к rollback. Одна команда (kubectl set env или внешний flag manager) отключает новый трафик.
  • Канал команды предупреждён. Ответственные инженеры на связи, не в отпуске/вне зоны.
  • Второй DR drill через 7 дней. Восстановление из backup в staging-среде — подтверждает, что backup реально работает, а не просто хранится.
  • Post-launch review через 14 дней. Ретроспектива: что сработало, что сломалось, что добавить в чеклист.

Финальная проверка

  • Все пункты выше — отмечены (checked или N/A с пояснением).
  • PR с новым сервисом прошёл review lead-инженера backend-команды.
  • Infra-PR (alerts, dashboards, CronJobs) смёржен.
  • Runbook'и открыты на общей странице команды.
  • Дата включения зафиксирована; post-launch review назначен на +14 дней.

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