Реализация на отдельных двух машинах с протестированным взаимодействием по проверке сообщений
This commit is contained in:
@@ -1,213 +1,81 @@
|
||||
# module-10-vm1. Runbook развёртывания ВМ1 HAN Chat
|
||||
|
||||
> Статус: целевой runbook репозитория ВМ1.
|
||||
> Общий контракт (VPC/SG, PG, S3, роли `deploy`, TLS процедура, порядок cutover) — [`arch-10-deployment.md`](../../architectory/arch-10-deployment.md).
|
||||
> ВМ2 — [`module-10-deployment-vm2.md`](../../VM2_services/documentation/module-10-deployment-vm2.md). Не переносить команды ВМ2 и не шарить Compose/secrets.
|
||||
|
||||
## 1. Границы
|
||||
|
||||
ВМ1 владеет edge nginx `80/443`, `api-backend`, Keycloak, SMS, `bitrix-local-app`, Redis DB0/DB1, Collector. После Safety cutover local Safety/Redis DB2 отсутствуют; `MESSAGE_SAFETY_URL` — private HTTPS ВМ2.
|
||||
|
||||
`<BACKEND_ROOT>` / `<BACKEND_REPO_URL>` — репозиторий ВМ1. Host ACME — `<PUBLIC_HOST>`.
|
||||
|
||||
## 2. Sizing ВМ1
|
||||
|
||||
Final sizing — D-TBD2. Disk после pull/build ≥30% free. `PUBLIC_DOCKER_PORTS=80,443`.
|
||||
|
||||
## 3. Hardening
|
||||
|
||||
По arch-10 §5 / arch-06. Пример:
|
||||
|
||||
```bash
|
||||
sudo DEPLOY_USER=deploy \
|
||||
DEPLOY_DIR=/opt/han-chat \
|
||||
SSH_PORT=<SSH_PORT> \
|
||||
SWAP_SIZE_GB=4 \
|
||||
PUBLIC_DOCKER_PORTS=80,443 \
|
||||
./deploy/setup-vm-han-chat.sh
|
||||
```
|
||||
|
||||
Gate 2 — arch-10. Break-glass вне VM.
|
||||
|
||||
## 4. Release layout и `.env` ВМ1
|
||||
|
||||
Checkout exact SHA в `/opt/han-chat/backend`. Структура: root Compose, `nginx`, `keycloak`, `redis`, `observability`, frontend artifact.
|
||||
|
||||
Обязательные группы секретов/config ВМ1:
|
||||
|
||||
- `APP_ENV`, release, log level;
|
||||
- private PG host/port/database и TLS CA; runtime DSN в secret backend;
|
||||
- Redis ACL URLs DB0/DB1 только в secret backend; после cutover DB2 нет;
|
||||
- public web/API/auth URLs;
|
||||
- Keycloak realm/audience/hostname/bootstrap/provider secrets;
|
||||
- SMS DB URL, парные Keycloak↔SMS tokens, Direct `TOKEN_1`, callback credentials;
|
||||
- paired service tokens arch-02;
|
||||
- Bitrix local app client/application/encryption secrets (не CRM sync webhook ВМ2);
|
||||
- S3 API credentials (не Safety read-only key ВМ2);
|
||||
- OTEL exporter secrets;
|
||||
- nginx/TLS/rate limits;
|
||||
- frontend public build values;
|
||||
- после cutover: `MESSAGE_SAFETY_URL=https://<private-vm2-name>:8443` и `MESSAGE_SAFETY_CA_HOST_PATH`.
|
||||
|
||||
Пары: `BITRIX_LOCAL_APP_INTERNAL_TOKEN == BITRIX_INTERNAL_API_TOKEN`, `BITRIX_API_FORWARD_TOKEN == BITRIX_API_INBOX_TOKEN`, `KEYCLOAK_SMS_SERVICE_TOKEN == SMS_SERVICE_TOKEN`.
|
||||
|
||||
`FRONTEND_DEV_PROXY_ENABLED=false`. Safety timeout согласован с nginx ВМ1. Validation — arch-10 §8; `validate-env` в этом репозитории.
|
||||
|
||||
## 5. Images и frontend
|
||||
|
||||
Pull или build без production secrets. Frontend:
|
||||
|
||||
```bash
|
||||
cd <FRONTEND_PROJECT_PATH>
|
||||
npm ci
|
||||
npm run test
|
||||
npx expo export --platform web
|
||||
```
|
||||
|
||||
Artifact в versioned `frontend-static`. Build env — только public URL/realm/client id. Secret scanner: нет service tokens/mock OTP/S3 keys.
|
||||
|
||||
Image/frontend gate: images по digest; static без secrets; nginx image с request-id/TLS; disk >30% free.
|
||||
|
||||
## 6. Root Compose ВМ1
|
||||
|
||||
Сервисы: edge `nginx`, `api-backend`, `keycloak`, `sms-service`, `sms-worker`, `bitrix-local-app`, Redis DB0/DB1, local `otel-collector`. После cutover — без `message-safety` и Redis DB2.
|
||||
|
||||
Networks: `public`, `backend`, `egress` (`sms-worker`; Keycloak входит только при `KEYCLOAK_YANDEX_CAPTCHA_ENABLED=true` и только с destination allow-list SmartCaptcha API), `observability`. `sms-service` без egress при отдельном worker.
|
||||
|
||||
Volumes: `redis-data`, ACME, `frontend-static`, `otel-queue` + `otel-queue-init`. Единственные published mappings — nginx 80/443.
|
||||
|
||||
Compose gate — arch-10 применительно к этому Compose.
|
||||
|
||||
## 7. TLS ВМ1
|
||||
|
||||
Arch-10 §9 / arch-08, `-d <PUBLIC_HOST> --cert-name <PUBLIC_HOST>`. Private `8443` на ВМ1 нет.
|
||||
|
||||
## 8. Миграции и seed ВМ1
|
||||
|
||||
Preflight/upgrade:
|
||||
|
||||
```bash
|
||||
cd <BACKEND_ROOT>
|
||||
docker compose run --rm api-backend alembic current
|
||||
docker compose run --rm bitrix-local-app alembic current
|
||||
# PITR marker, затем:
|
||||
docker compose run --rm api-backend alembic upgrade head
|
||||
docker compose run --rm bitrix-local-app alembic upgrade head
|
||||
```
|
||||
|
||||
Shared `han_app.sync_queue` мигрирует api-backend **до** sync cutover на ВМ2, но grants `bitrix_sync_user` — после обеих migrations (см. runbook ВМ2). Seed `app_settings` idempotent из этого репозитория.
|
||||
|
||||
SMS schema/migrations выполняет `sms-service` на ВМ1.
|
||||
|
||||
### Controlled rollout real SMS
|
||||
|
||||
1. seed `otp.phone.*`;
|
||||
2. schema/role `sms`, migrations, seed `sms_setting`/active `auth_otp`;
|
||||
3. test env с mock Direct;
|
||||
4. production Direct `TOKEN_1`, sender, template, callback credentials;
|
||||
5. записать `<IDGTL_STATIC_EGRESS_IP>` из `sms-worker`;
|
||||
6. deploy `sms-service`/worker и callback route nginx ВМ1, `KEYCLOAK_OTP_MOCK_ENABLED=true`;
|
||||
7. Keycloak expand migration/SPI;
|
||||
8. provider smoke на `<IDGTL_TEST_PHONE>`;
|
||||
9. только после evidence — `KEYCLOAK_OTP_MOCK_ENABLED=false`;
|
||||
10. проверить durable order, resend, limits.
|
||||
|
||||
Rollback SMS: вернуть Keycloak в mock; не удалять schema/journal. Production cutover запрещён при placeholder или нестабильном egress IP.
|
||||
|
||||
## 9. Keycloak bootstrap
|
||||
|
||||
```bash
|
||||
cd <BACKEND_ROOT>
|
||||
docker compose up -d keycloak
|
||||
```
|
||||
|
||||
Bootstrap admin только на первый запуск, затем MFA named admin и удаление bootstrap. Realm: public client PKCE S256, issuer `https://<PUBLIC_HOST>/auth/realms/han-chat`, без `--import-realm` на живой production без diff. Keycloak gate — discovery/JWKS HTTPS, OTP fail-closed, settings bridge.
|
||||
|
||||
## 10. Ordered startup ВМ1
|
||||
|
||||
После готовности ВМ2 (arch-10 §10 шаги 1–3):
|
||||
|
||||
1. Redis ВМ1, `otel-queue-init`, Collector;
|
||||
2. API, SMS, Keycloak, local app;
|
||||
3. edge nginx последним; после readiness — `nginx -t -c /tmp/nginx.conf` и HUP.
|
||||
4. `MESSAGE_SAFETY_URL` переключается на ВМ2 **только** после cutover gates runbook ВМ2 и legacy gate §14.
|
||||
|
||||
Не использовать host ports для health curl. Expected: Redis `PONG`; Keycloak ready; Collector health; API core DB/Redis/JWKS/settings ready. S3/Safety/Open Lines могут быть `degraded` без снятия read API из readiness, но send path при недоступном Safety остаётся fail-closed. Local app до install может быть `portal_not_installed`.
|
||||
|
||||
## 11. Bitrix24 local app и Open Lines
|
||||
|
||||
Install/handler/placement URL на `https://<PUBLIC_HOST>/bitrix/...`. Canonical internal path `/internal/openlines/v1/*`, не prototype `/bitrix-internal/*`. Open Lines gate: connector line 8, outbound once, operator reply, duplicate callback безопасен.
|
||||
|
||||
CRM webhook robots **не** настраиваются на ВМ1.
|
||||
|
||||
## 12. Public smoke ВМ1
|
||||
|
||||
```bash
|
||||
curl -I http://<PUBLIC_HOST>/
|
||||
curl -fsS https://<PUBLIC_HOST>/api/v1/public/app-config
|
||||
curl -fsS https://<PUBLIC_HOST>/api/v1/public/content
|
||||
curl -fsS https://<PUBLIC_HOST>/auth/realms/han-chat/.well-known/openid-configuration
|
||||
curl -i https://<PUBLIC_HOST>/internal/safety/v2/messages/check
|
||||
openssl s_client -connect <PUBLIC_HOST>:443 -servername <PUBLIC_HOST>
|
||||
```
|
||||
|
||||
Expected: 308; public 200; discovery 200; internal 404; valid cert.
|
||||
|
||||
Далее: guest content; 401 без JWT; consent → OTP → PKCE; bootstrap без phone body; `ux_session_id`; silent refresh; logout; wrong OTP. Real SMS mode — по §8.
|
||||
|
||||
Safety E2E **со стороны caller** (правила stub до v2 cutover): `ф` → `422 message_blocked`; allow path; timeout 503/504 без duplicate. Статус Safety `stub` не заменяет production AV.
|
||||
|
||||
Files: presigned PUT quarantine, promote/deny, owner-only download, нет URL в logs. Realtime/ownership/idempotency/429 — module-01.
|
||||
|
||||
Сквозной first-send до Open Lines требует готовую ВМ2; system end-to-end gate закрывается по arch-10 после обоих runbook.
|
||||
|
||||
## 13. Observability ВМ1
|
||||
|
||||
[`module-09-observability-vm1.md`](module-09-observability-vm1.md) + arch-07. Сквозной `X-Request-ID` до Safety span — совместно с ВМ2.
|
||||
|
||||
## 14. Legacy gate перед cutover Safety
|
||||
|
||||
Перед `MESSAGE_SAFETY_URL` на ВМ2:
|
||||
|
||||
1. validator принимает только `https://<private-vm2-name>:8443`, требует CA path, запрещает Docker hostname и plaintext;
|
||||
2. internal CA root-owned; read-test UID `api-backend`, negative посторонний UID;
|
||||
3. local `message-safety`, Redis DB2 и local rules-version env удалены из Compose/validator;
|
||||
4. root-owned stack unit; `deploy` не в `docker`;
|
||||
5. `DOCKER-USER` counters через `conntrack --ctorigdstport` после Docker restart и reboot;
|
||||
6. images digest; rollback по compatible digests;
|
||||
7. ordered startup этого runbook, не legacy `docker compose up -d`;
|
||||
8. `han-secrets` и firewall oneshot явно перезапущены; TLS renew success — пустой stderr.
|
||||
|
||||
Ни старый single-VM guide, ни успешный stub Compose не являются evidence. Rollback caller — предыдущий immutable release ВМ1. Уже созданные v2 tasks не down-migrate.
|
||||
|
||||
## 15. Rollback, ops, incidents ВМ1
|
||||
|
||||
Rollback application-only: previous digests, без Alembic downgrade; при SMS incident — mock OTP, сохранить journal. Redis restore — clean instance, прогрев idempotency из PG ([`module-04-redis-vm1.md`](module-04-redis-vm1.md)). Keycloak restore — проверить issuer/JWKS/PKCE/OTP.
|
||||
|
||||
Routine: health, PG/TLS/disk/OTEL/Redis, Keycloak signing, Bitrix connector desired/observed.
|
||||
|
||||
Incident triage — arch-10 команды в `<BACKEND_ROOT>` ВМ1. Типовое: API 503 (DB/Redis/JWKS/Safety circuit); send timeout — не новый idempotency key; Redis loss — polling.
|
||||
|
||||
Потеря ВМ1: provision в той же VPC, restore secrets из vault, existing PG/S3, TLS `<PUBLIC_HOST>`, Bitrix local app/callbacks verify. Не пересоздавать ВМ2.
|
||||
|
||||
## 16. Definition of Done ВМ1
|
||||
|
||||
Дополнительно к arch-10 §12:
|
||||
|
||||
- Compose/nginx/Redis/Collector ВМ1 прошли профильные compose/nginx gates;
|
||||
- Keycloak realm/provider/PKCE/OTP готов;
|
||||
- SMS либо mock с accepted risk, либо real mode после §8;
|
||||
- Bitrix connector line 8 проверен;
|
||||
- auth/text/file/realtime E2E caller-side зелёный;
|
||||
- observability ВМ1 + redaction;
|
||||
- после cutover — legacy gate §14 закрыт.
|
||||
|
||||
## 17. TBD ВМ1
|
||||
|
||||
D-TBD1, D-TBD2 (VM1 sizing/SLO), D-TBD8 CLI, D-TBD9 Keycloak admin VPN.
|
||||
|
||||
## 18. Ссылки
|
||||
|
||||
- Контракт: [`arch-10-deployment.md`](../../architectory/arch-10-deployment.md).
|
||||
- ВМ2: [`module-10-deployment-vm2.md`](../../VM2_services/documentation/module-10-deployment-vm2.md).
|
||||
- Указатель: [`module-10-deployment-runbook.md`](module-10-deployment-runbook.md).
|
||||
# module-10-vm1. Production deployment ВМ1
|
||||
|
||||
> Канонический исполняемый fresh-VM runbook:
|
||||
> [`RUNBOOK.production.ru.md`](../codebase/backend/deployment/RUNBOOK.production.ru.md).
|
||||
> Общие security/deployment контракты:
|
||||
> [`arch-06-service-hosting-security.md`](../../architectory/arch-06-service-hosting-security.md)
|
||||
> и [`arch-10-deployment.md`](../../architectory/arch-10-deployment.md).
|
||||
> ВМ2:
|
||||
> [`module-10-deployment-vm2.md`](../../VM2_services/documentation/module-10-deployment-vm2.md).
|
||||
|
||||
## Граница документа
|
||||
|
||||
Этот модуль фиксирует только связь спецификаций и cutover boundary. Команды
|
||||
bootstrap, release activation, secrets, TLS, migrations, systemd, smoke,
|
||||
reboot, rollback и DR находятся только в production runbook.
|
||||
|
||||
Production ВМ1 разворачивается на fresh Ubuntu 24.04. In-place hardening
|
||||
legacy single-VM/stub инсталляции запрещён. Legacy
|
||||
`deployment/DEPLOYMENT_GUIDE.ru.md`, `RUNBOOK.ru.md` и `RUNBOOK.md` являются
|
||||
только указателями.
|
||||
|
||||
## Host deployment contract
|
||||
|
||||
- разные Ed25519 keys для `deploy` и break-glass `admin`, не совпадающие с
|
||||
root bootstrap key;
|
||||
- `deploy` не входит в `docker`, `sudo`, `lxd`, `adm`,
|
||||
`systemd-journal`; пишет только в `/var/lib/han-deploy/incoming`;
|
||||
- active release, Compose, `.env`, scripts/helpers, units, secret mappings,
|
||||
credentials и sudoers принадлежат root;
|
||||
- activation проверяет approved SHA-256, project root, absolute/`..` paths,
|
||||
symlink/hardlink и executable modes;
|
||||
- lifecycle — `han-secrets@production.service` и
|
||||
`han-stack@production.service` через exact sudoers;
|
||||
- public TLS копируется root hook из `/etc/letsencrypt` в
|
||||
`/var/lib/han-chat/public-tls`; nginx не получает дерево Certbot;
|
||||
- published Docker ports фильтруются по original destination
|
||||
`conntrack --ctorigdstport 80/443`, включая restart/reboot gates.
|
||||
|
||||
## Cutover ВМ1 → ВМ2
|
||||
|
||||
ВМ1 владеет public guest/API/auth/SMS/Bitrix local-app ingress `80/443`.
|
||||
ВМ2 владеет Message Safety v2 на private HTTPS `:8443` и CRM sync public
|
||||
webhooks собственного host. Compose, IAM principal и secret bundle между VM не
|
||||
разделяются.
|
||||
|
||||
До переключения caller на ВМ2:
|
||||
|
||||
1. ВМ2 закрыла TLS SAN/CA, PG migrations, Safety capabilities, S3 read-only,
|
||||
performance, egress и rollback gates своего runbook.
|
||||
2. ВМ1 использует
|
||||
`MESSAGE_SAFETY_URL=https://<VM2_PRIVATE_DNS>:8443` и root-owned
|
||||
`MESSAGE_SAFETY_CA_HOST_PATH`.
|
||||
3. Internal CA читается фактическим UID API и не читается посторонним UID.
|
||||
4. Service token paired, private route/SG разрешают `8443` только от ВМ1/ops.
|
||||
5. Local `message-safety`, Redis DB2, local Safety rules env и stub fallback
|
||||
отсутствуют в production Compose/validator.
|
||||
6. Caller smoke подтверждает allow/deny/pending/timeout, file flow и
|
||||
fail-closed поведение; status `stub` не принимается.
|
||||
7. После cutover фиксируются release/image digests, schema/config versions,
|
||||
firewall counters, traces и rollback approval.
|
||||
|
||||
Rollback caller переключает только на предыдущий schema-compatible immutable
|
||||
release ВМ1. Уже созданные tasks ВМ2 не down-migrate. Потеря ВМ2 не разрешает
|
||||
fail-open или возврат local stub.
|
||||
|
||||
Bitrix sync cutover выполняется отдельно по runbook ВМ2 после expand migrations
|
||||
`han_app` на ВМ1. CRM webhook ВМ2 не маршрутизируется через nginx ВМ1.
|
||||
|
||||
## Связанные модули
|
||||
|
||||
- API caller и Safety v2:
|
||||
[`module-01-api-backend.md`](module-01-api-backend.md).
|
||||
- Public edge:
|
||||
[`module-03-nginx-vm1.md`](module-03-nginx-vm1.md).
|
||||
- Redis DB0/DB1 и удаление legacy DB2:
|
||||
[`module-04-redis-vm1.md`](module-04-redis-vm1.md).
|
||||
- Keycloak: [`module-08-keycloak.md`](module-08-keycloak.md).
|
||||
- Observability:
|
||||
[`module-09-observability-vm1.md`](module-09-observability-vm1.md).
|
||||
- SMS cutover:
|
||||
[`module-11-idgtl-sms.md`](module-11-idgtl-sms.md).
|
||||
|
||||
Reference in New Issue
Block a user