Реализация на отдельных двух машинах с протестированным взаимодействием по проверке сообщений
This commit is contained in:
@@ -0,0 +1,477 @@
|
||||
# Fresh production runbook ВМ1 HAN Chat
|
||||
|
||||
Это единственный исполняемый production-runbook ВМ1. Он предназначен только
|
||||
для новой Ubuntu 24.04 VM: in-place преобразование старой single-VM/stub
|
||||
инсталляции запрещено. Команды выполняет оператор; repository automation их не
|
||||
запускает.
|
||||
|
||||
Запрещены: реальные секреты в репозитории, `.env`, argv/history или логах;
|
||||
доступ `deploy` к Docker; mutable image tags; `docker compose down -v`;
|
||||
Alembic downgrade; автоматический fallback Selectel → локальный файл.
|
||||
|
||||
## 0. Участники, переменные и stop conditions
|
||||
|
||||
- локальная машина: создаёт проверенный архив и SHA-256;
|
||||
- `root`: bootstrap, активация release, config/secrets/TLS, миграции и первый
|
||||
запуск;
|
||||
- `deploy`: пишет только в `/var/lib/han-deploy/incoming`, затем использует
|
||||
exact systemd/status/log commands;
|
||||
- `admin`: персональная break-glass роль с отдельным ключом и sudo-паролем.
|
||||
|
||||
До окна работ зафиксируйте: `<GIT_SHA>`, image digests, `<EXPECTED_SHA256>`,
|
||||
DNS/IP ВМ1, private DNS/SAN ВМ2, ops CIDR, PG/S3 inventory, PITR marker,
|
||||
RPO/RTO, on-call, approver и предыдущий совместимый release.
|
||||
|
||||
Stop condition: любой placeholder, незакрытый preflight, несовпавший digest,
|
||||
невалидный TLS, broad SG/sudo, неизвестная Alembic revision, stub/local Safety,
|
||||
неуспешный negative probe или отсутствие rollback evidence.
|
||||
|
||||
## 1. Bootstrap новой ВМ1
|
||||
|
||||
На локальной машине создайте две разные Ed25519 key pairs. Private keys не
|
||||
передаются на VM и не должны совпадать с bootstrap root key:
|
||||
|
||||
```powershell
|
||||
ssh-keygen -t ed25519 -a 100 -f C:\Users\<USER>\.ssh\han_vm1_deploy `
|
||||
-C "han-vm1-deploy"
|
||||
ssh-keygen -t ed25519 -a 100 -f C:\Users\<USER>\.ssh\han_vm1_admin `
|
||||
-C "han-vm1-break-glass-admin"
|
||||
```
|
||||
|
||||
Передайте setup и только public keys во временный root-каталог. В уже открытой
|
||||
bootstrap root-сессии:
|
||||
|
||||
```sh
|
||||
install -d -m 0700 -o root -g root /root/bootstrap
|
||||
install -m 0600 -o root -g root /tmp/han_vm1_deploy.pub /root/bootstrap/deploy.pub
|
||||
install -m 0600 -o root -g root /tmp/han_vm1_admin.pub /root/bootstrap/admin.pub
|
||||
install -m 0700 -o root -g root /tmp/setup-vm.sh /root/setup-vm1.sh
|
||||
DEPLOY_AUTHORIZED_KEY_FILE=/root/bootstrap/deploy.pub \
|
||||
ADMIN_AUTHORIZED_KEY_FILE=/root/bootstrap/admin.pub \
|
||||
/root/setup-vm1.sh
|
||||
passwd admin
|
||||
```
|
||||
|
||||
Setup устанавливает host packages, Docker/Compose, UFW, fail2ban, security
|
||||
updates, `DOCKER-USER`, swap и роли. Он не запускает Compose. Cloud SG должен
|
||||
разрешать `80/443` из интернета. На bootstrap-этапе UFW временно разрешает SSH
|
||||
из любой сети; после настройки WireGuard закройте public SSH и разрешите его
|
||||
только через WireGuard. PG принимает TLS только от SG/private IP ВМ1. `6379`,
|
||||
`4317/4318`, `8000`, `8080`, `9000` наружу запрещены.
|
||||
|
||||
Не закрывая root-сессию, проверьте в двух новых сессиях:
|
||||
|
||||
```powershell
|
||||
ssh -i C:\Users\<USER>\.ssh\han_vm1_deploy deploy@<VM1_IP>
|
||||
ssh -i C:\Users\<USER>\.ssh\han_vm1_admin admin@<VM1_IP>
|
||||
```
|
||||
|
||||
В admin-сессии:
|
||||
|
||||
```sh
|
||||
sudo -v
|
||||
sudo -i
|
||||
id
|
||||
exit
|
||||
```
|
||||
|
||||
Только после успеха обоих SSH-входов и admin sudo повторите в исходной
|
||||
root-сессии:
|
||||
|
||||
```sh
|
||||
DEPLOY_AUTHORIZED_KEY_FILE=/root/bootstrap/deploy.pub \
|
||||
ADMIN_AUTHORIZED_KEY_FILE=/root/bootstrap/admin.pub \
|
||||
HARDEN_SSH=true SKIP_APT_UPGRADE=true \
|
||||
/root/setup-vm1.sh
|
||||
```
|
||||
|
||||
Откройте ещё по одной новой deploy/admin сессии после reload. Подтвердите
|
||||
`PermitRootLogin no`, `AllowUsers deploy admin`, key-only auth и отсутствие
|
||||
forwarding. Только затем закрывайте bootstrap root session. Root-only public
|
||||
key files сохраните до установки helpers из первого release; private keys на
|
||||
VM отсутствуют. Доступ cloud console/recovery остаётся break-glass.
|
||||
|
||||
## 2. Сборка и передача immutable release
|
||||
|
||||
На локальной машине checkout должен быть exact detached `<GIT_SHA>`, tree —
|
||||
clean. Архив содержит один корень `backend`, не содержит `.env`, credentials,
|
||||
caches и private keys:
|
||||
|
||||
```powershell
|
||||
$Release = "<GIT_SHA>"
|
||||
tar --exclude=backend/.env `
|
||||
--exclude='backend/**/__pycache__' `
|
||||
--exclude='backend/**/.pytest_cache' `
|
||||
--exclude='backend/**/.ruff_cache' `
|
||||
-czf "vm1-backend-$Release.tar.gz" `
|
||||
-C .\VM1_app\codebase backend
|
||||
Get-FileHash "vm1-backend-$Release.tar.gz" -Algorithm SHA256
|
||||
scp "vm1-backend-$Release.tar.gz" `
|
||||
deploy@<VM1_IP>:/var/lib/han-deploy/incoming/
|
||||
```
|
||||
|
||||
Под `deploy` разрешены только inventory/checksum:
|
||||
|
||||
```sh
|
||||
RELEASE='<GIT_SHA>'
|
||||
cd /var/lib/han-deploy/incoming
|
||||
sha256sum "vm1-backend-${RELEASE}.tar.gz"
|
||||
tar -tzf "vm1-backend-${RELEASE}.tar.gz"
|
||||
```
|
||||
|
||||
## 3. Root-активация: SHA и anti-traversal
|
||||
|
||||
Под `admin`, затем `sudo -i`. Не распаковывайте недоверенный архив до всех
|
||||
проверок:
|
||||
|
||||
```sh
|
||||
RELEASE='<GIT_SHA>'
|
||||
EXPECTED_SHA256='<SHA256_FROM_APPROVED_WORKSTATION>'
|
||||
ARCHIVE="/var/lib/han-deploy/incoming/vm1-backend-${RELEASE}.tar.gz"
|
||||
printf '%s %s\n' "$EXPECTED_SHA256" "$ARCHIVE" | sha256sum --check -
|
||||
tar -tvzf "$ARCHIVE"
|
||||
if tar -tzf "$ARCHIVE" | grep -Eq '(^/|(^|/)\.\.(/|$)|^backend/\.env$)'; then
|
||||
echo 'unsafe path or .env' >&2; exit 1
|
||||
fi
|
||||
if tar -tzf "$ARCHIVE" | grep -Ev '^backend(/|$)' | grep -q .; then
|
||||
echo 'archive has files outside backend' >&2; exit 1
|
||||
fi
|
||||
if tar -tvzf "$ARCHIVE" | awk '$1 ~ /^[lh]/ {found=1} END {exit !found}'; then
|
||||
echo 'symlink/hardlink is forbidden' >&2; exit 1
|
||||
fi
|
||||
|
||||
TARGET="/opt/han-chat/releases/${RELEASE}"
|
||||
test ! -e "$TARGET"
|
||||
install -d -m 0755 -o root -g root "$TARGET"
|
||||
tar --extract --gzip --file "$ARCHIVE" --directory "$TARGET" \
|
||||
--no-same-owner --no-same-permissions
|
||||
test -f "$TARGET/backend/docker-compose.yml"
|
||||
chmod 0755 \
|
||||
"$TARGET/backend/deployment/scripts/setup-vm.sh" \
|
||||
"$TARGET/backend/deployment/preflight.sh" \
|
||||
"$TARGET/backend/deployment/scripts/tls-deploy-hook.sh" \
|
||||
"$TARGET/backend/deployment/secrets/han-compose" \
|
||||
"$TARGET/backend/deployment/secrets/han-secrets"
|
||||
test -x "$TARGET/backend/deployment/scripts/setup-vm.sh"
|
||||
test -x "$TARGET/backend/deployment/preflight.sh"
|
||||
test -x "$TARGET/backend/deployment/scripts/tls-deploy-hook.sh"
|
||||
test -x "$TARGET/backend/deployment/secrets/han-compose"
|
||||
test -x "$TARGET/backend/deployment/secrets/han-secrets"
|
||||
if find "$TARGET/backend" -type l -print -quit | grep -q .; then exit 1; fi
|
||||
chown -R root:root "$TARGET"
|
||||
chmod -R go-w "$TARGET"
|
||||
ln -s "releases/${RELEASE}" /opt/han-chat/.current-new
|
||||
mv -Tf /opt/han-chat/.current-new /opt/han-chat/current
|
||||
```
|
||||
|
||||
Executable modes поставляет release manifest/git. Нельзя применять blanket
|
||||
`--chmod=F644` или рекурсивный `chmod`, снимающий executable bit. Сохраните
|
||||
SHA-256 архива и release SHA в change record.
|
||||
|
||||
Повторите setup из активного release с теми же public key files. Он
|
||||
установит root-owned launcher, secret unit, stack unit, TLS hook и sudoers, но
|
||||
не запустит приложение:
|
||||
|
||||
```sh
|
||||
DEPLOY_AUTHORIZED_KEY_FILE=/root/bootstrap/deploy.pub \
|
||||
ADMIN_AUTHORIZED_KEY_FILE=/root/bootstrap/admin.pub \
|
||||
HARDEN_SSH=true SKIP_APT_UPGRADE=true \
|
||||
/opt/han-chat/current/backend/deployment/scripts/setup-vm.sh
|
||||
visudo -cf /etc/sudoers.d/deploy
|
||||
sudo -l -U deploy
|
||||
rm -f /root/bootstrap/deploy.pub /root/bootstrap/admin.pub
|
||||
```
|
||||
|
||||
Убедитесь, что в выводе нет wildcard, shell/editor/cp/chmod/docker и что
|
||||
`deploy` не входит в `docker`, `sudo`, `lxd`, `adm`, `systemd-journal`.
|
||||
Для повторного setup после будущего release заново передайте только проверенные
|
||||
public keys в root-only временный каталог и удалите их после выполнения.
|
||||
|
||||
## 4. Несекретный config и secrets
|
||||
|
||||
Под root создайте `/etc/han/vm1.env` из reviewed production template. Config
|
||||
живёт вне immutable release и не меняется при rollback. В нём только
|
||||
несекретные значения и paths; `APP_ENV=production`,
|
||||
`SECRETS_SOURCE=selectel`, все images pinned `@sha256:...`,
|
||||
`MESSAGE_SAFETY_URL=https://<VM2_PRIVATE_DNS>:8443`,
|
||||
`MESSAGE_SAFETY_EXTRA_HOST=<VM2_PRIVATE_DNS>=<VM2_PRIVATE_IP>`,
|
||||
`MESSAGE_SAFETY_CA_HOST_PATH=/etc/han/ca/vm2-internal-ca.pem`,
|
||||
`NGINX_TLS_CERTIFICATE=/run/tls/fullchain.pem`,
|
||||
`NGINX_TLS_CERTIFICATE_KEY=/run/tls/privkey.pem`.
|
||||
|
||||
```sh
|
||||
cd /opt/han-chat/current/backend
|
||||
install -m 0600 -o root -g root .env.example /etc/han/vm1.env
|
||||
editor /etc/han/vm1.env
|
||||
./scripts/validate-env /etc/han/vm1.env
|
||||
install -m 0600 -o root -g root deployment/secrets/config.example.json \
|
||||
/etc/han/secrets/production.selectel.json
|
||||
editor /etc/han/secrets/production.selectel.json
|
||||
```
|
||||
|
||||
Mapping должен содержать только VM1 secrets. Убедитесь, что в нём отсутствуют
|
||||
legacy local `message-safety`/`bitrix-sync` consumers, Redis DB2 и credentials
|
||||
сервисов ВМ2; отдельный IAM principal ВМ1 получает read-only только к
|
||||
перечисленным remote names. Пароли/DSN/token/S3 keys не помещаются в
|
||||
`/etc/han/vm1.env`.
|
||||
|
||||
Создайте encrypted systemd credential без значения в argv/history:
|
||||
|
||||
```sh
|
||||
read -rsp 'Selectel VM1 service-user password: ' SELECTEL_PASSWORD; echo
|
||||
printf '%s' "$SELECTEL_PASSWORD" | systemd-creds encrypt \
|
||||
--name=selectel-service-user-password - \
|
||||
/etc/han/credentials/production.selectel-password.cred
|
||||
unset SELECTEL_PASSWORD
|
||||
chown root:root /etc/han/credentials/production.selectel-password.cred
|
||||
chmod 0600 /etc/han/credentials/production.selectel-password.cred
|
||||
```
|
||||
|
||||
Подтвердите token pairs без печати значений средствами validator. Не делайте
|
||||
`cat` runtime secret files. Emergency file mode — отдельная root-only
|
||||
процедура без automatic fallback.
|
||||
|
||||
## 5. Managed PostgreSQL и S3
|
||||
|
||||
Установите provider CA вне release:
|
||||
|
||||
```sh
|
||||
install -m 0644 -o root -g root /tmp/<PG_CA_FILE> \
|
||||
/etc/han/ca/managed-postgresql-ca.pem
|
||||
openssl x509 -in /etc/han/ca/managed-postgresql-ca.pem \
|
||||
-noout -subject -issuer -dates
|
||||
rm -f /tmp/<PG_CA_FILE>
|
||||
```
|
||||
|
||||
Runtime и migration roles разделены для `han_app`, `bitrix_local`, `sms`,
|
||||
`keycloak`; runtime не имеет DDL/ownership. DSN проверяет hostname и chain.
|
||||
Включены encryption, deletion protection, backup/PITR и alerts. Перед
|
||||
миграциями создаётся provider PITR marker.
|
||||
|
||||
S3: private encrypted buckets documents/attachments/quarantine; exact browser
|
||||
origin CORS; prefix-scoped VM1 key; versioning/lifecycle; public ACL off.
|
||||
Read-only quarantine key Safety принадлежит IAM ВМ2 и не копируется на ВМ1.
|
||||
Проверьте negative access к чужому prefix/bucket.
|
||||
|
||||
## 6. Public TLS и internal VM2 CA
|
||||
|
||||
Установите внутренний CA, которым ВМ1 проверяет SAN ВМ2:
|
||||
|
||||
```sh
|
||||
install -m 0644 -o root -g root /tmp/<VM2_INTERNAL_CA_FILE> \
|
||||
/etc/han/ca/vm2-internal-ca.pem
|
||||
openssl x509 -in /etc/han/ca/vm2-internal-ca.pem \
|
||||
-noout -subject -issuer -dates
|
||||
rm -f /tmp/<VM2_INTERNAL_CA_FILE>
|
||||
openssl s_client -connect <VM2_PRIVATE_IP>:8443 \
|
||||
-servername <VM2_PRIVATE_DNS> -verify_hostname <VM2_PRIVATE_DNS> \
|
||||
-CAfile /etc/han/ca/vm2-internal-ca.pem -verify_return_error </dev/null
|
||||
```
|
||||
|
||||
Для initial public certificate DNS уже указывает на ВМ1, `80` свободен:
|
||||
|
||||
```sh
|
||||
PUBLIC_HOST='<PUBLIC_HOST>'
|
||||
ACME_EMAIL='<OPS_EMAIL>'
|
||||
certbot certonly --standalone --preferred-challenges http --staging \
|
||||
-d "$PUBLIC_HOST" --cert-name "${PUBLIC_HOST}-staging" \
|
||||
--email "$ACME_EMAIL" --agree-tos --no-eff-email --non-interactive
|
||||
certbot delete --cert-name "${PUBLIC_HOST}-staging" --non-interactive
|
||||
certbot certonly --standalone --preferred-challenges http \
|
||||
-d "$PUBLIC_HOST" --cert-name "$PUBLIC_HOST" \
|
||||
--email "$ACME_EMAIL" --agree-tos --no-eff-email --non-interactive
|
||||
```
|
||||
|
||||
При первом копировании nginx ещё не работает, поэтому deploy hook до запуска
|
||||
не вызывается. Скопируйте initial pair теми же root ownership и mode, затем
|
||||
preflight проверит pair:
|
||||
|
||||
```sh
|
||||
install -m 0640 -o root -g han-nginx-tls \
|
||||
"/etc/letsencrypt/live/${PUBLIC_HOST}/fullchain.pem" \
|
||||
/var/lib/han-chat/public-tls/fullchain.pem
|
||||
install -m 0640 -o root -g han-nginx-tls \
|
||||
"/etc/letsencrypt/live/${PUBLIC_HOST}/privkey.pem" \
|
||||
/var/lib/han-chat/public-tls/privkey.pem
|
||||
```
|
||||
|
||||
В production Compose nginx обязан монтировать только
|
||||
`/var/lib/han-chat/public-tls` read-only, не `/etc/letsencrypt`. Если это ещё
|
||||
legacy named volume, preflight/Compose review — блокер, не workaround.
|
||||
|
||||
## 7. Secret sync и static preflight
|
||||
|
||||
```sh
|
||||
systemctl restart han-secrets@production.service
|
||||
systemctl is-active han-secrets@production.service
|
||||
journalctl --no-pager -u han-secrets@production.service
|
||||
test -s /run/han-chat/secrets/manifest
|
||||
cut -d= -f1 /run/han-chat/secrets/manifest | sort
|
||||
|
||||
/opt/han-chat/current/backend/deployment/preflight.sh
|
||||
/usr/local/sbin/han-vm1-compose config --quiet
|
||||
/usr/local/sbin/han-vm1-compose config --services
|
||||
/usr/local/sbin/han-vm1-compose config --images
|
||||
```
|
||||
|
||||
В списке production services после VM2 cutover нет local `message-safety`,
|
||||
`bitrix-sync`, Redis DB2 и test stubs. Только nginx публикует `80/443`; все
|
||||
images immutable. Не сохраняйте resolved Compose с secret paths/metadata в
|
||||
общедоступный файл.
|
||||
|
||||
## 8. Миграции и seed
|
||||
|
||||
После PITR marker под root:
|
||||
|
||||
```sh
|
||||
/usr/local/sbin/han-vm1-compose --profile ops run --rm migrate-api
|
||||
/usr/local/sbin/han-vm1-compose --profile ops run --rm migrate-bitrix-local
|
||||
/usr/local/sbin/han-vm1-compose --profile ops run --rm migrate-sms
|
||||
/usr/local/sbin/han-vm1-compose --profile ops run --rm seed-settings
|
||||
/usr/local/sbin/han-vm1-compose --profile ops run --rm seed-settings
|
||||
```
|
||||
|
||||
Проверьте current/head каждой схемы, идемпотентность seed и negative DDL от
|
||||
runtime roles. Миграции ВМ1 `han_app` precede dependent VM2 sync cutover.
|
||||
Временные cross-schema grants выдаёт owner и отзывает после проверки. Unknown
|
||||
revision, multiple heads или incompatible schema — stop; ручной `stamp` и
|
||||
downgrade запрещены.
|
||||
|
||||
## 9. Упорядоченный первый запуск
|
||||
|
||||
Первый запуск выполняет root через фиксированный launcher:
|
||||
|
||||
```sh
|
||||
/usr/local/sbin/han-vm1-compose up -d redis otel-queue-init otel-collector
|
||||
/usr/local/sbin/han-vm1-compose up -d keycloak sms-service
|
||||
/usr/local/sbin/han-vm1-compose up -d api-backend bitrix-local-app
|
||||
/usr/local/sbin/han-vm1-compose up -d \
|
||||
sms-worker delivery-worker safety-recovery-worker cleanup-worker \
|
||||
notification-expire-worker notification-draft-cleanup-worker
|
||||
/usr/local/sbin/han-vm1-compose up -d frontend-static
|
||||
/usr/local/sbin/han-vm1-compose up -d nginx
|
||||
/usr/local/sbin/han-vm1-compose ps
|
||||
```
|
||||
|
||||
Дождитесь health, затем:
|
||||
|
||||
```sh
|
||||
/usr/local/sbin/han-vm1-compose exec -T nginx nginx -t -c /tmp/nginx.conf
|
||||
NGINX_ID=$(/usr/local/sbin/han-vm1-compose ps --status running --quiet nginx)
|
||||
test -n "$NGINX_ID"
|
||||
docker kill --signal HUP "$NGINX_ID" >/dev/null
|
||||
unset NGINX_ID
|
||||
systemctl enable han-secrets@production.service han-stack@production.service
|
||||
systemctl start han-stack@production.service
|
||||
```
|
||||
|
||||
`han-stack@production` становится единственным routine lifecycle interface.
|
||||
После изменения secret source сначала explicit restart secret unit, затем
|
||||
stack unit. Обновление active/exited oneshot всегда требует `restart`.
|
||||
|
||||
## 10. Smoke, firewall и cutover
|
||||
|
||||
С внешней машины:
|
||||
|
||||
```sh
|
||||
curl -sS -o /dev/null -w '%{http_code}\n' http://<PUBLIC_HOST>/
|
||||
curl -fsS https://<PUBLIC_HOST>/api/v1/public/app-config
|
||||
curl -fsS https://<PUBLIC_HOST>/auth/realms/han-chat/.well-known/openid-configuration
|
||||
curl -sS -o /dev/null -w '%{http_code}\n' \
|
||||
https://<PUBLIC_HOST>/internal/safety/v2/messages/check
|
||||
openssl s_client -connect <PUBLIC_HOST>:443 -servername <PUBLIC_HOST> \
|
||||
-verify_hostname <PUBLIC_HOST> -verify_return_error </dev/null
|
||||
```
|
||||
|
||||
Ожидается `308`, public endpoints `200`, internal route `404`, valid chain.
|
||||
Проверьте guest/auth PKCE/OTP, SMS mode, Open Lines, idempotency, ownership,
|
||||
rate limits, WS reconciliation, S3 quarantine/promote/deny и Safety v2
|
||||
allow/deny/pending/timeout. Safety status `stub` не принимается.
|
||||
|
||||
До переключения `MESSAGE_SAFETY_URL` ВМ2 должна закрыть собственные gates.
|
||||
После переключения подтвердите private CA/SAN, service token, `text|links|files`
|
||||
capabilities и fail-closed timeout. Local Safety/Redis DB2 не оставляются как
|
||||
fallback. Cutover ВМ2 Bitrix sync — отдельное окно.
|
||||
|
||||
Проверьте `DOCKER-USER` live counters внешним positive `80/443` и negative
|
||||
port/source probe:
|
||||
|
||||
```sh
|
||||
iptables -L HAN-CHAT-DOCKER -n -v
|
||||
systemctl restart han-chat-docker-firewall.service
|
||||
systemctl restart docker.service
|
||||
systemctl restart han-chat-docker-firewall.service
|
||||
iptables -S HAN-CHAT-DOCKER
|
||||
```
|
||||
|
||||
Allow rules должны использовать `--ctorigdstport 80/443`; UFW INPUT counter не
|
||||
является доказательством published Docker ports.
|
||||
|
||||
## 11. Certbot, observability и reboot gate
|
||||
|
||||
После запуска nginx переключите renewal на webroot, который Compose монтирует
|
||||
из `/var/lib/han-chat/acme`, затем:
|
||||
|
||||
```sh
|
||||
certbot reconfigure --cert-name '<PUBLIC_HOST>' \
|
||||
--authenticator webroot --webroot-path /var/lib/han-chat/acme
|
||||
certbot renew --dry-run --run-deploy-hooks
|
||||
systemctl enable --now certbot.timer
|
||||
systemctl list-timers certbot.timer
|
||||
```
|
||||
|
||||
Hook должен завершаться `0` с пустым stderr на success, атомарно обновлять
|
||||
staging, выполнять config test и HUP.
|
||||
|
||||
Проверьте logs/metrics/traces, request ID через nginx/API/VM2, alerts для
|
||||
5xx/auth/Safety/PG/Redis/OOM/disk/OTEL queue/TLS. Отправьте только fake canary
|
||||
token/PII markers и докажите их отсутствие в logs/traces.
|
||||
|
||||
Перед reboot проверьте admin SSH и provider console:
|
||||
|
||||
```sh
|
||||
systemctl is-enabled docker.service han-chat-docker-firewall.service \
|
||||
han-secrets@production.service han-stack@production.service certbot.timer
|
||||
systemctl reboot
|
||||
```
|
||||
|
||||
После reconnect повторите status, stack `ps`, public/private TLS, smoke,
|
||||
negative ports и firewall counters. Без reboot gate deployment не завершён.
|
||||
|
||||
## 12. Rollback и disaster recovery
|
||||
|
||||
Application rollback допускается только на предыдущий root-owned release,
|
||||
совместимый с текущей schema. Секреты и `/etc/han/vm1.env` остаются текущими:
|
||||
|
||||
```sh
|
||||
PREVIOUS='<PREVIOUS_COMPATIBLE_GIT_SHA>'
|
||||
test -d "/opt/han-chat/releases/${PREVIOUS}/backend"
|
||||
ln -s "releases/${PREVIOUS}" /opt/han-chat/.current-new
|
||||
mv -Tf /opt/han-chat/.current-new /opt/han-chat/current
|
||||
systemctl daemon-reload
|
||||
systemctl restart han-secrets@production.service
|
||||
/opt/han-chat/current/backend/deployment/preflight.sh
|
||||
systemctl restart han-stack@production.service
|
||||
```
|
||||
|
||||
Повторите smoke и зафиксируйте digests. Не удаляйте current/previous release,
|
||||
active images, evidence или volumes. После incompatible migration используйте
|
||||
forward fix либо согласованный PG PITR + S3/Bitrix reconciliation в maintenance
|
||||
window; Redis восстанавливается пустым и прогревается из PG.
|
||||
|
||||
Потеря ВМ1: создайте новую VM этим runbook, восстановите DNS/SG, root-owned
|
||||
approved release, VM1 IAM secrets, managed PG/S3 и public TLS; ВМ2 не
|
||||
пересоздавайте. До cutover держите traffic закрытым. Ежеквартально делайте
|
||||
isolated restore rehearsal, не направляя production DNS/Bitrix callbacks, и
|
||||
фиксируйте фактические RPO/RTO.
|
||||
|
||||
При компрометации host не «очищайте» VM: изолируйте, сохраните evidence,
|
||||
ротируйте доступные secrets/tokens и reprovision из trusted image.
|
||||
|
||||
## 13. Production acceptance record
|
||||
|
||||
Сохраните без secret values: release/archive SHA-256, image digests, schema
|
||||
heads, realm/config version, PG PITR marker, TLS fingerprints/expiry, systemd
|
||||
status, positive/negative firewall probes, public/private smokes, VM2 cutover
|
||||
approval, observability canary, reboot result, rollback/restore rehearsal и
|
||||
подписи Operations/Security/Product.
|
||||
Reference in New Issue
Block a user