27 KiB
arch-10. Контракт развёртывания
Канонический контракт rollout для всех application VM. OS-роли, SSH/sudo, secrets delivery и hardening —
arch-06-service-hosting-security.md; этот документ их не ослабляет.
Процедуры конкретной машины —module-10-deployment-vm1.mdиmodule-10-deployment-vm2.md.
Nginx TLS —arch-08-nginx.md. Redis —arch-09-redis.md. Telemetry —arch-07-observability.md. Compose —arch-03-docker-compose-blueprint.md.
Назначение
Документ фиксирует то, что должно совпасть между ВМ1 и ВМ2 и то, что связывает их как систему: независимый deploy, VPC/SG, managed PG, S3, роли deploy/admin, секреты, TLS/ACME процедура, порядок cutover Safety, backup/rollback/DR.
Команды сервисов конкретной машины и её Compose не дублируются здесь. Агент репозитория VM читает этот контракт плюс свой runbook.
Прямые docker compose команды выполняются admin только при bootstrap/recovery либо инкапсулируются в утверждённые root-owned systemd-units. Они не являются основанием выдавать deploy доступ к Docker daemon. Значения в <УГЛОВЫХ_СКОБКАХ> — placeholders.
1. Неподвижные правила
- Один root Compose project описывается в
<BACKEND_ROOT>своей VM; в steady state его запускает root-owned systemd-unit/helper, а не пользователь из группыdocker. - На каждой VM ровно один nginx; ВМ1 и ВМ2 имеют независимые public DNS/TLS ingress и deployment lifecycle.
- Application containers не имеют public host ports. Nginx ВМ1 публикует свой
80/443; nginx ВМ2 — отдельный80/443только для exact CRM webhook и private8443для Message Safety/internal access. /internal/*не маршрутизируется публично.- Managed PostgreSQL находится вне Compose, в той же VPC, без public IP.
- S3 — внешний Selectel-compatible storage; клиент получает только presigned URL.
- Секреты не коммитятся, не вставляются в команды shell history и не выводятся в отчёты.
- Миграции выполняются отдельными one-shot steps до новой версии приложения.
- Production Message Safety работает только на ВМ2; local stub/fallback на ВМ1 запрещён. Caller ВМ1 использует private HTTPS, service token и проверку internal CA, при недоступности — fail closed.
bitrix-syncработает только на ВМ2 и вводится после preflight/cutover gates module-07; до подтверждённого cutover public webhook остаётся закрыт на edge.- На ВМ1 и ВМ2 отдельные root Compose projects/systemd units; deploy/rollback выполняются независимо.
- ВМ2 — самостоятельная service VM с минимальным public webhook ingress, allow-listed egress, отдельным IAM principal и service-specific secret files.
- OS-роли, SSH/sudo, secrets delivery, container hardening и private-VM lockdown подчиняются arch-06.
2. Роли и обозначения
- Cloud admin: VPC, VM, PG, S3, DNS/security groups.
- Deploy operator / OS user
deploy: запуск утверждённых release/rollback/migration systemd-units; без группыdocker, записи в production compose/unit/scripts/config и общего sudo. - Break-glass OS user
admin: bootstrap и аварийное восстановление; не используется для штатного деплоя. - OS user
tunnel: только allow-listed local TCP forwarding к private endpoints; без sudo/shell operations. - Bitrix admin: local app, connector, Open Line 8, callbacks.
- Security owner: secrets, Keycloak admin MFA, firewall, retention.
- Safety Service Owner: API/data contract, capacity result и v2 cutover/rollback sign-off.
- Rule Pack Owner: rules bundle, corpus, monitor report и version release.
- Product Owner: mnemonic
safety.chat.blockedи business acceptance chat flow. - Operations Owner: VM2 alerts, host KESL/broker и database updates, incident/reprovision/restore rehearsal.
<PUBLIC_HOST> например chat.example.ru
<VM_PUBLIC_IP> публичный IPv4 ВМ1
<PROCESSING_PUBLIC_HOST> отдельный public host ВМ2, например processing.example.ru
<VM2_PUBLIC_IP> публичный IPv4/LB address ВМ2
<VM_PRIVATE_IP> приватный IPv4 VM
<VPC_CIDR> например 10.20.0.0/24
<PG_PRIVATE_HOST> private FQDN/IP managed PG
<PG_PORT> 5433 для Selectel PgBouncer; иной порт — только фактическое значение другого provider/endpoint
<PG_DATABASE> han_chat
<BACKEND_REPO_URL> URL репозитория этой VM
<BACKEND_ROOT> /opt/han-chat/backend
<RELEASE> immutable tag/git SHA
<ACME_EMAIL> адрес ops, не placeholder в реальном запуске
<BITRIX_PORTAL> разрешённый портал
<IDGTL_SENDER_NAME> согласованное в Direct имя отправителя
<IDGTL_STATIC_EGRESS_IP> фактический статический egress IP `sms-worker`
<IDGTL_TEST_PHONE> контролируемый номер для provider smoke
<BACKEND_ROOT> и <BACKEND_REPO_URL> разные у ВМ1 и ВМ2.
3. Stage 0 — решения до provisioning
Зафиксировать: region/AZ и VPC; hostnames и TTL DNS; VM image Ubuntu 24.04 LTS; sizing каждой VM; PG plan/storage/backups/PITR; S3 region/endpoint/bucket names; container registry и immutable image tags/digests; remote observability backend; RPO/RTO и maintenance window; ответственных за alerts/Bitrix/Keycloak; Safety Service/Rule Pack/Product/Security/Operations owners и approvals cutover.
Sizing и load gates конкретной машины — профильный runbook. Monthly availability SLO для MVP не задаётся; это не отменяет latency/load gates и alerts.
Gate 0
- Владельцы и maintenance window назначены.
- RPO/RTO приняты хотя бы временно: ориентир RPO PG ≤15 минут/PITR, RTO ≤4 часа.
- Решено: images pull из registry или build на VM.
- Remote telemetry backend выбран либо явно принят ограниченный debug-only режим.
- Риск mock OTP до SMS cutover и Safety stub письменно принят; real SMS не включается без gates module-11.
Ожидаемый результат: есть release checklist с конкретными values; не создано ни одной публичной БД/Redis.
4. Stage 1 — VPC, DNS и security groups
Создать private subnet для ВМ1, ВМ2, SigNoz и managed PG. ВМ1 и ВМ2 имеют отдельные public IP/LB только для своих nginx; service-to-service и PostgreSQL traffic остаётся private. SSH к обеим VM — только ops VPN/bastion.
| Source | Destination | Port | Rule |
|---|---|---|---|
| trusted ops CIDR/VPN | ВМ1, ВМ2 | SSH <SSH_PORT> |
allow |
| internet | ВМ1 | TCP 80/443 | allow edge redirect/ACME/application |
| internet | nginx ВМ2 | TCP 80/443 | allow ACME/redirect + exact CRM webhook |
| ВМ1 SG | ВМ2 | TCP 8443 | private TLS only |
| ВМ1/ВМ2 service SG | managed PG | <PG_PORT> |
allow по нужным DB roles |
| ВМ2 collector | private SigNoz | TCP 4317 | allow |
| ВМ2 workers | S3 endpoints | TCP 443 | allow |
ВМ2 bitrix-sync |
approved Bitrix portal | TCP 443 | allow |
| host KESL ВМ2 | approved update sources по operator KESL runbook | vendor-required destinations/ports | allow |
| ВМ2 | trusted DNS/NTP | UDP/TCP 53, UDP 123 | allow |
| internet | managed PG | any | deny |
| internet | ВМ2 | any кроме nginx 80/443 | deny ingress |
| internet | обе VM | 6379, 4317, 4318, 8000, 8080, 8443, 9000 | deny public |
ВМ2 использует default-deny egress. Registry/OS repositories открываются только в bootstrap/controlled window и затем снова закрываются. Если provider SG не умеет destination allow-list, применяется host firewall/proxy/NAT policy; постоянный open egress для ВМ2 не является допустимым production состоянием.
DNS: A <PUBLIC_HOST> → <VM1_PUBLIC_IP>, A <PROCESSING_PUBLIC_HOST> → <VM2_PUBLIC_IP>, private DNS processing.internal → <VM2_PRIVATE_IP>. Public host ВМ2 используется только CRM webhook; private name не публикуется во внешнем DNS.
Gate 1
- PG не имеет public endpoint.
- SSH доступен только trusted source.
- Снаружи открыты только 80/443/ограниченный SSH.
- DNS стабильно разрешается с нескольких resolver.
- Каждая VM достигает private PG и своих внешних HTTPS endpoints.
Ожидаемый результат: nc -vz <PG_PRIVATE_HOST> <PG_PORT> с VM успешен; с внешней машины PG недоступен.
5. Stage 2 — hardening Ubuntu
Процедура первичная для каждой application VM и выполняется только по её профильному production runbook. Перед production: review версии; не передавать IP/ключи в git; проверить unattended upgrades; deploy не в группе docker; AllowTcpForwarding no по умолчанию; отдельный tunnel при необходимости; root-owned systemd-units и /etc/sudoers.d/deploy без wildcard.
PUBLIC_DOCKER_PORTS задаёт профильный runbook (ВМ1: 80,443; ВМ2 — свои public 80/443, private 8443 не internet).
Проверки: sshd -t, UFW, fail2ban, Docker/Compose versions, iptables -L HAN-CHAT-DOCKER, timedatectl, disk/swap. Второй SSH session как deploy, затем отключить root/password login.
Gate 2
- SSH key login
deployпроверен во втором сеансе. - Root/password auth выключены.
deployне состоит в группеdocker;sudo -lсодержит только утверждённые конкретные systemd-команды.- Production compose, units, deploy scripts и secret mappings принадлежат root и недоступны
deployна запись. - UFW и DOCKER-USER активны после restart Docker.
- Для published Docker ports allow rules сопоставляют original host destination через
conntrack --ctorigdstport; positive/negative probes увеличивают counters нужных allow/deny rules после restart Docker и reboot. - После обновления firewall helper active
oneshot RemainAfterExitunit явно перезапущен;enable --nowне считается применением новой версии. - Docker Engine/Compose plugin закреплены поддерживаемой версией.
- NTP active; disk/swap соответствуют sizing.
- Break-glass процедура сохранена вне VM.
Ожидаемый результат: reboot VM не теряет SSH, firewall и Docker service.
5.1. Lockdown private/no-egress VM
Для SigNoz и другой VM, которая после раскатки не должна иметь internet ingress/egress, bootstrap выполняется по lifecycle arch-06. Checklist закрытия public IP/SSH/egress и запрет постоянного open egress — arch-06; повторное открытие только break-glass с повтором lockdown.
6. Stage 3 — managed PostgreSQL
До создания схем включить daily backup, PITR, encryption at rest, TLS sslmode=verify-full + provider CA, alerts (disk/connections/CPU/IO/replication/backup/CA), deletion protection.
CA: /opt/han-chat/secrets/pg/ca.pem, root:deploy 0440 (либо 0400). Режимы disable/allow/prefer/require без проверки CA для production запрещены.
Публичные HTTPS-сертификаты <PUBLIC_HOST> и <PROCESSING_PUBLIC_HOST> выпускаются отдельно через Let's Encrypt на Stage 9 nginx соответствующей VM, не в PostgreSQL.
Целевая модель ролей: admin/bootstrap; migration role каждого schema с DDL; runtime без DDL. Прототип init-managed-postgres.py слишком широк для production runtime.
Шесть schemas: han_app, bitrix_local, bitrix_sync, keycloak, message_safety, sms. Schema owner = migration role; runtime: USAGE, DML и sequence grants только на свои objects; bitrix_sync_user — только grants module-07 §13.
Ownership миграций:
api-backendAlembic —han_app;bitrix-local-app—bitrix_local;message-safetystub не создаёт PG tables до production implementation;bitrix-sync— schemabitrix_sync; api-backend отдельно мигрирует sharedhan_app.sync_queue;- Keycloak — standard tables; custom provider — свои migrations;
sms-service— schemasms; runtimesms_userбез доступа кhan_app/keycloak.
Только expand/migrate/contract. Destructive migration — отдельный backup, approval и release. Downgrade data migrations не обещается.
Gate 3
- Backups/PITR/TLS/deletion protection включены.
- Шесть schemas/roles созданы, включая
sms/sms_user. - Runtime roles не имеют DDL/чужого доступа.
- Migration credentials отделены от runtime.
- Empty/previous-version migration test успешен.
- PITR restore point создан перед первым release.
7. Stage 4 — S3
Три приватных bucket: <PREFIX>-quarantine, <PREFIX>-attachments, <PREFIX>-documents. Public ACL/listing выключены. Versioning для data buckets по policy; SSE включить.
IAM: API role — exact prefixes, presign PUT quarantine, Head/copy/delete quarantine, write/read data; Safety role — read-only quarantine; backup/ops отдельно; frontend — никаких permanent credentials. Отдельный IAM principal ВМ2.
CORS quarantine: AllowedOrigins только https://<PUBLIC_HOST>; PUT; headers Content-Type, If-None-Match, checksum; не * origin с credentials.
Lifecycle: quarantine failed/orphan expire 48 ч только при отсутствии active safety_tasks; incomplete multipart abort 1 день; attachments/documents без auto-delete до legal retention.
Gate 4
- Все buckets private.
- API key не может list/write вне exact scope.
- Safety key не может write/delete.
- Browser test origin выполняет presigned PUT с checksum и
If-None-Match: *; повтор того же key получает412. - Complete фиксирует authoritative
version_id, ETag и checksum; Safety читает только эту version. - Wrong version/ETag и изменённый source дают deny/error и не promote-ятся.
- Conditional promote mismatch не создаёт delivery outbox/Bitrix call.
- Quarantine lifecycle не удалит active
safety_tasks. - Data lifecycle соответствует retention.
8. Общий layout релиза и секретов
На каждой VM:
/opt/han-chat/
backend/ # checkout текущего release
releases/<RELEASE>/ # optional immutable release dirs
secrets/ # не в git
backups/ # только metadata/short-lived encrypted artifacts
Рекомендуемый rollout — immutable images из registry. Deploy из mutable branch без recorded SHA запрещён. latest отсутствует.
.env — только несекретный config и SECRETS_SOURCE=file|selectel. Секреты выдаёт deployment/secrets/han-secrets. docker compose config может раскрыть secrets; stdout не публиковать.
Validation: нет change-me, paired tokens equal, PG TLS, public HTTPS, FRONTEND_DEV_PROXY_ENABLED=false на production-like.
Состав .env/secret groups — профильный runbook.
9. TLS/ACME процедура
Канонические bootstrap, renewal, validation и safe reload задаёт
arch-08-nginx.md. Rollout каждой VM применяет эту модель
независимо, без compose down и без named volume сертификатов:
- root-only ACME state:
/etc/letsencrypt; - read-only для nginx host staging:
/var/lib/han-chat/public-tls; - host ACME webroot:
/var/lib/han-chat/acme; - root-owned systemd timer/hook дважды в сутки с
flock; пользовательdeployможет запускать только утверждённый unit и не получает доступ к ACME state; - staging CA rehearsal предшествует production issuance; после renewal root
hook проверяет certificate/key, атомарно обновляет staging, выполняет
container
nginx -tи только затем HUP; - success path возвращает
0с пустым stderr; ошибка сохраняет действующий certificate/config и поднимает alert (<21 дней, page <7 дней).
Host ВМ1 — <PUBLIC_HOST>, ВМ2 — <PROCESSING_PUBLIC_HOST>; private 8443
использует internal CA, не Let's Encrypt. Ни ACME state, ни staging между VM не
разделяются.
10. Сквозной порядок startup и cutover
- На ВМ2 оператор выполняет KESL runbook: KESL 12.4 standalone, database update/canary.
- Затем enable/start root-owned broker socket/service и проверяет status/permissions
/run/han-kesl/scan.sock. - Unit ВМ2 поднимает Redis Safety и local Collector, затем Safety API/worker и
bitrix-sync. - Последним на ВМ2 — nginx public
80/443и private8443. - На ВМ1 — Redis/Collector, API, SMS, Keycloak, local app, edge nginx.
- Только private
MESSAGE_SAFETY_URLВМ1 переключается на ВМ2 после Safety gates. Public CRM webhook DNS/routes ВМ2 не требуют изменения ВМ1.
Broker — custom integration: до cutover target VM2 обязана подтвердить точный
формат/exit semantics kesl-control --scan-file --action Inform, socket
permissions/cleanup и throughput. Только clean допускает allow; infected
даёт deny; scanner error или stale database остаются retryable и завершаются
503, не allow. Runtime фиксирует scanner_engine=kesl и
signatures_version=hash(KESL version + database date); KESL update выполняется
ежечасно.
Legacy single-VM docker compose up не является evidence готовности target ВМ2. Один message_id нельзя одновременно отправлять в v1 и v2. После cutover ВМ1 не содержит local Safety/Redis DB2.
При потере ВМ2 fail-open запрещён. Reprovision ВМ2 из immutable image/config; Redis пустой; gates повторяются. RTO ≤4 ч; restore rehearsal минимум дважды в год.
11. Observability, opening traffic, backup, rollback
Gate 5–14 не дублируются в этом общем контракте: это профильные release gates конкретной VM, описанные в module-10-deployment-vm1.md / module-10-deployment-vm2.md и executable runbook соответствующего репозитория. Нумерация внутри VM2 runbook локальна его технической приёмке; системное открытие трафика всегда требует подписанных результатов обоих контуров.
Gate 15 — observability: arch-07 и профильный module-09 VM spec; сквозной request_id через nginx ВМ1 → API → Safety ВМ2 обязателен до объявления production-ready.
Gate 16 — opening traffic: снять maintenance, включить HSTS после TLS test, сохранить digests/revisions, restore point и on-call, затем наблюдать 60 минут. До открытия должны быть подписаны Gate 0–4 этого документа, применимые Gate 5–14 профильных runbook обеих VM и Gate 15.
Backup: PG daily+PITR — business restore; S3 versioning; Redis не business backup; Keycloak в PG. Restore rehearsal в isolated VPC без production DNS/Bitrix callbacks.
Rollback приложения: previous immutable digests, без Alembic downgrade, SCHEMA_BACKWARD_COMPATIBLE_CONFIRMED=true. Backward-incompatible migration — только forward-fix или PITR. TLS: оставить старый config/cert при failure.
Запрещены: docker compose down -v, docker system prune -a --volumes, DROP DATABASE / DROP SCHEMA, recursive S3 delete, certbot delete active cert, unbounded logs, Redis KEYS/FLUSH*, ad-hoc DB DELETE.
Routine (автомат ежедневно): health/synthetics, PG backup/PITR, TLS expiry, disk/OTEL queue, Redis AOF/memory, DLQ/quarantine age. Еженедельно: image CVE, login/rate-limit trend, S3 inventory. Ежемесячно: OS patch, secret review, capacity, runbook drill.
cd <BACKEND_ROOT>
docker compose ps
docker compose logs --since=15m <SERVICE>
docker stats --no-stream
docker system df
docker compose exec -T nginx nginx -t
Incident triage:
cd <BACKEND_ROOT>
date -Is
docker compose ps
docker stats --no-stream
docker compose logs --since=10m --tail=500 <SERVICE>
df -h
free -h
sudo ss -lntp
sudo iptables -L HAN-CHAT-DOCKER -n -v
PG: readonly select now(), count(*) from pg_stat_activity. Redis — только ops ACL PING. Secret literal не вставлять в ticket. Не replay message/DLQ до idempotency и ambiguous Bitrix outcome.
Типовые сценарии: API 503 — DB/Redis/JWKS/Safety circuits; send timeout — checkpoint, не новый key; Bitrix down — OAuth/circuit/DLQ; Redis loss — clean restart + polling; PG outage — не restart storm; disk full — known cache/old images после inventory; cert expiry — webroot/staging; secret leak — rotate, telemetry deletion.
Upgrades: notes → compatibility → PITR → staging → expand migration → one service at a time → E2E → contract later. Keycloak не пропускать unsupported majors.
DR потеря VM: новая Ubuntu в VPC, hardening, DNS, secrets из vault не со старого disk, exact images, Redis можно clean, existing PG/S3, TLS, ordered startup. Потеря PG: PITR в new instance, остановить writes, reconcile S3 orphans. Потеря S3: без versioning полное восстановление невозможно; отключить file ops. Compromise: isolate, forensic snapshot, rotate all, clean deploy, notify.
Перед teardown: inventory без secrets, revoke Bitrix/credentials, legal hold, DNS drain, deletion protection — отдельное approval, shared VPC/PG/S3 проверить.
12. Общий Definition of Done
- VM/VPC/DNS/SG/hardening соответствуют Gate 1–2;
- managed PG private/TLS/backups/least privilege/migrations работают;
- S3 private/IAM/CORS/lifecycle проверены;
deployне имеет Docker/root-equivalent доступа;- независимые public 80/443; ВМ2 дополнительно private 8443;
- после Safety cutover ВМ1 вызывает ВМ2 только по private HTTPS с проверенным CA bind;
- container hardening — arch-06;
- для каждой private/no-egress VM завершён lockdown;
- backup restore и rollback rehearsed;
- ops/incident/upgrade/DR owners назначены;
- все assumptions/TBD приняты до открытия traffic.
Профильный DoD VM дополняет свои сервисы, Compose и cutover steps.
13. Допущения, TBD и конфликты
Допущения: D-A1 отдельные public hosts; D-A2 обязательный минимум — local Collector; D-A3 managed PG private/TLS/PITR; D-A4 Bitrix portal/connector/line — значения architecture; D-A5 mock OTP до SMS cutover как documented risk.
TBD: D-TBD1 domains/Expo redirect/Bitrix frame ancestors; D-TBD2 final VM1/PG sizing, public SLO, RPO/RTO; D-TBD3 legal retention; D-TBD4 secret manager; D-TBD5 Safety v2 cutover; D-TBD6 bitrix-sync cutover; D-TBD7 pinned versions; D-TBD8 exact CLI после реализации; D-TBD9 Keycloak admin VPN/MFA; D-TBD10 CSP/CORS/IAM; D-TBD11 SigNoz auth/retention/alert route.
Конфликты: module-07 documentation ≠ cutover; v1 stub ≠ production Safety; prototype PG init слишком широк; prototype TLS downtime; prototype /bitrix-internal/* запрещён; Compose/env могут ещё не содержать VM2/SMS artifacts.
14. Ссылки
- ВМ1:
module-10-deployment-vm1.md. - ВМ2:
module-10-deployment-vm2.md. - Исполняемые процедуры:
RUNBOOK.production.ru.mdдля ВМ1 иRUNBOOK.ru.mdдля ВМ2.