Подготовить безопасный production rollout

This commit is contained in:
Zuev
2026-07-29 23:13:17 +03:00
parent a78a93f2f0
commit 8bb7fe97eb
16 changed files with 494 additions and 324 deletions

View File

@@ -58,53 +58,95 @@
Полный локальный сброс БД выполняется командой `docker compose down -v`, но она удалит
все локальные данные. Обычная остановка без удаления данных: `docker compose down`.
## 2. Первый запуск в production
## 2. Обновление существующего production
1. Подготовьте K3s/Kubernetes-кластер и убедитесь, что `kubectl get nodes` работает.
PostgreSQL в текущие манифесты не входит: заранее создайте доступную из кластера БД для
каждого университета. Пользователь БД должен иметь права на создание и изменение схемы —
Flyway применит `V1__init.sql` автоматически.
Простой push в `main` для первого обновления **недостаточен**. Он выполняет проверки,
собирает и сканирует образы, но job `deploy-to-k8s` запускается только вручную через
`workflow_dispatch`. Новые файлы `../k8s` CI не применяет. Gitea 1.25 игнорирует
`environment` и `concurrency`, поэтому они не используются как production-защита.
2. Настройте DNS для `magistr.zuev.company` и остальных tenant-доменов на публичный
reverse proxy/Ingress. В текущем `../k8s/ingress.yaml` нет секции TLS: для HTTPS нужно
настроить внешний Caddy с сертификатом либо добавить cert-manager/TLS в кластер.
1. Подготовьте секреты по [`docs/SECURITY_RUNBOOK.md`](docs/SECURITY_RUNBOOK.md):
3. Создайте namespace и четыре обязательных Secret по
[`docs/SECURITY_RUNBOOK.md`](docs/SECURITY_RUNBOOK.md):
- `app-secret`: новый случайный `JWT_SECRET` не короче 32 байт, `POSTGRES_USER`,
`POSTGRES_PASSWORD`;
- `tenants-secret`: новый `tenants.json` с актуальными JDBC URL и ротированными паролями;
- `otel-postgres-secret`: восемь обязательных параметров подключений Collector;
- `gitea-registry`: доступ K3s к registry.
- `app-secret`: `JWT_SECRET`, `POSTGRES_USER`, `POSTGRES_PASSWORD`;
- `tenants-secret`: файл `tenants.json`;
- `otel-postgres-secret`: подключения Collector к БД;
- `gitea-registry`: доступ к приватным Docker-образам.
Для `magistr.zuev.company` нужен tenant с `"domain": "magistr"`, для
`n8n.zuev.company` — `"domain": "n8n"`. Секреты не сохраняйте в Git или CI-логах.
Для `magistr.zuev.company` запись в `tenants.json` должна иметь `"domain": "magistr"`
и реальный JDBC URL. Не сохраняйте значения Secret в Git или логах.
2. Выполните локальные проверки, затем отправьте изменения в `main`:
4. В Gitea Actions добавьте секреты `ZUEV_TOKEN` (доступ к registry) и
`KUBECONFIG_DATA` (kubeconfig в Base64). Push в `main` соберёт, проверит и опубликует
образы. При самом первом запуске автоматический deploy может ещё не найти Deployment —
возьмите два опубликованных digest из CI/registry и выполните bootstrap вручную:
```bash
K8S_DIR=../k8s bash scripts/check-production-secrets.sh
K8S_DIR=../k8s bash scripts/check-artifact-pinning.sh
bash scripts/test-deploy-images.sh
kubectl kustomize ../k8s >/dev/null
```
Дождитесь успешных checks, сборки и Trivy scan. Скопируйте точные backend/frontend
ссылки вида `image@sha256:...` из CI/registry.
3. Объявите техническое окно и временно закройте публичный доступ во внешнем Caddy. Затем
остановите backend:
```bash
kubectl scale deployment/backend -n magistr --replicas=0
```
Полностью пересоздайте **каждую** PostgreSQL tenant-БД, указанную в новом
`tenants.json`. Недостаточно удалить `flyway_schema_history` или отдельные таблицы:
миграции V2–V7 объединены в новую `V1__init.sql`, поэтому старая БД получит checksum
error, а непустая БД без истории может быть ошибочно принята Flyway за baseline.
Пользователь БД должен уметь создавать схему, функции, триггеры и расширения
`pgcrypto`/`btree_gist`.
4. С рабочей станции с актуальным каталогом `../k8s` выполните первый rollout вручную:
```bash
export BACKEND_IMAGE_REF='gitea.zuev.company/zuev/magistr-backend@sha256:<digest>'
export FRONTEND_IMAGE_REF='gitea.zuev.company/zuev/magistr-frontend@sha256:<digest>'
K8S_DIR=../k8s bash scripts/check-production-secrets.sh
K8S_DIR=../k8s bash scripts/check-artifact-pinning.sh
kubectl kustomize ../k8s >/dev/null
bash ../k8s/deploy.sh apply
```
5. Проверьте результат:
Скрипт применит ConfigMap, RBAC, Secret mount, probes, OTel и Ingress, поднимет две
реплики backend и дождётся Flyway/readiness. CI deploy после push не ожидает
подтверждения: он будет пропущен. Следующие image-only обновления можно запускать
вручную кнопкой `Run workflow`.
5. Перед открытием публичного доступа проверьте:
```bash
bash ../k8s/deploy.sh status
kubectl rollout status deployment/backend -n magistr --timeout=300s
kubectl rollout status deployment/frontend -n magistr --timeout=120s
kubectl get pods -n magistr
kubectl logs -n magistr -l app=backend --tail=200
curl -fsS https://magistr.zuev.company/
curl -fsSI https://magistr.zuev.company/
```
После первого входа немедленно смените/отключите все тестовые пароли из `V1__init.sql`.
Следующие push в `main` CI/CD сможет развёртывать автоматически.
Проверьте в браузере login → reload → refresh → logout для каждого tenant-домена.
Refresh-cookie должна иметь `HttpOnly`, `Secure`, `SameSite=Lax`, path `/api/auth`;
access JWT не должен находиться в Local/Session Storage. До снятия технического окна
смените или отключите все демонстрационные учётные записи из `V1__init.sql`.
6. После проверки удалите старый `ConfigMap/tenants-config`, отзовите старые DB/OTel
пароли и верните обычный доступ через Caddy. В дальнейшем image-only изменения можно
разворачивать ручным запуском workflow, но любые изменения `../k8s` по-прежнему нужно
применять отдельно или переносить этот каталог в управляемый CI репозиторий.
### Важные ограничения rollback
- Смена `JWT_SECRET` и сброс БД завершат все старые пользовательские сессии.
- Readiness требует доступности и успешной миграции всех tenant-БД; одна старая или
недоступная БД остановит rollout.
- Автоматический rollback возвращает только два image reference. Он не откатывает БД,
Secret, ConfigMap или RBAC; после применения новой V1 безопаснее исправлять проблему
вперёд либо снова пересоздавать БД под старую версию.
- CI не запускает Testcontainers integration tests: полный PostgreSQL-прогон нужно
повторить вручную, если после зафиксированных 300 успешных тестов код ещё менялся.
- `TRUSTED_PROXY_CIDRS=10.42.0.0/16` соответствует текущей pod-сети K3s. При смене
cluster CIDR его нужно обновить, иначе rate limit увидит неверные клиентские IP.
## 3. Что могу выполнить я