Files
SQS-service/doc/thinking/vm-public-entrypoint-2026-10-10.md
T

176 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Публичная точка SQS на ВМ 213 — как устроено (справочник, 2026-10-10)
> Текущее состояние и конфигурация. Хронология и ошибки — в `HISTORY/2026-10-10-session-log.md`.
> Проверено снаружи: `https://sqs.sinefo.ru/health` → 200.
## 1. Схема
```
клиент (AWS SDK, SigV4)
│ https://sqs.sinefo.ru (443, TLS Let's Encrypt)
▼
[ ВМ 213: главный nginx ] conf.d/sqs-sinefo.conf
│ proxy_pass http://127.0.0.1:4100 ; Host $http_host
▼
[ shared-sqs ] ~/sqs-test/shared-sqs (Go, memory-only)
```
ВМ: публичный `5.172.178.213` (NAT), внутренний `10.10.102.21`, hostname `naeel-vm`.
## 2. Файлы
| Что | Где |
|---|---|
| Vhost nginx (80 ACME+редирект, 443 TLS) | `/etc/nginx/conf.d/sqs-sinefo.conf` (ВМ) |
| Сертификат | `/etc/letsencrypt/live/sqs.sinefo.ru/{fullchain,privkey}.pem` |
| ACME-webroot | `/var/www/acme/.well-known/acme-challenge/` |
| Бинарник/лог/креды | `~/sqs-test/shared-sqs`, `~/sqs-test/sqs.log` |
| Бэкапы nginx | `/etc/nginx/conf.d.bak-*` |
## 3. Конфиг vhost (эталон)
```nginx
server {
listen 80;
server_name sqs.sinefo.ru;
location /.well-known/acme-challenge/ { root /var/www/acme; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
server_name sqs.sinefo.ru;
ssl_certificate /etc/letsencrypt/live/sqs.sinefo.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sqs.sinefo.ru/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
client_max_body_size 10m;
proxy_request_buffering off;
location / {
proxy_pass http://127.0.0.1:4100;
proxy_http_version 1.1;
proxy_set_header Host $http_host; # ВАЖНО: не $host — иначе SigV4 не сойдётся
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}
```
## 4. Критичные условия (иначе не работает)
1. **`Host $http_host`** — SigV4 подписывает заголовок `Host` целиком (с портом).
`$host` порт отбрасывает → `SignatureDoesNotMatch`. Сервис берёт хост из запроса:
`app/auth/auth_middleware.go:315-317`.
2. **DNS:** `sqs.sinefo.ru A 5.172.178.213`; делегирование `sinefo.ru` → `ns3/ns4.ngcloud.ru`.
Сертификат выдаётся только когда домен резолвится (HTTP-01 через главный nginx, порт 80).
3. **Порт 8443 на 213 занят** DNAT-ом транзита (`→ 95.179.252.111:443`) — не использовать.
4. Проксирование тел — `proxy_request_buffering off`; лимит SQS 256 КБ.
## 5. Замеры (2026-10-10) — болезнь managed-шлюза НЕ воспроизводится
- HTTP 30× (1КБ–256КБ): 30/30 200, max 0.99 с.
- HTTPS 10× (26КБ–260КБ): 10/10 200, 0.7–3.2 с.
- Long-poll `WaitTimeSeconds=20` → 20.3 с, 200.
Гипотеза мануала (Шаг 1) подтверждена: 0 зависаний ~51 с, 0 таймаутов 31–33 с.
## 6. Управление
```bash
# запуск SQS на ВМ
cd ~/sqs-test && setsid nohup env SHARED_SQS_SEED_DEMO=true \
./shared-sqs --admin-token=dev-token-123 --port=4100 --loglevel=info > sqs.log 2>&1 </dev/null &
# креды демо-тенанта
grep "seed: создан тенант" ~/sqs-test/sqs.log | tail -1
# проверка
curl -sS https://sqs.sinefo.ru/health
```
## 7. QueueUrl — ИСПРАВЛЕНО (2026-10-10)
**Было:** `QueueUrl` = `http://region.host:port/<tenantID>/<queue>` (плейсхолдеры — на ВМ нет `goaws.yaml`).
**Стало:** база URL задаётся через `ExternalURLBase` (config `goaws.yaml` **или** env
`SHARED_SQS_EXTERNAL_URL_BASE`). На ВМ запускается с `SHARED_SQS_EXTERNAL_URL_BASE=https://sqs.sinefo.ru` →
`QueueUrl` = `https://sqs.sinefo.ru/<tenantID>/<queue>` (проверено снаружи).
Изменения (коммиты `469132b`, `ca0372b`):
- `app/models/configuration.go` — поле `Environment.ExternalURLBase`;
- `app/gosqs/tenant_helpers.go` — `tenantQueueURL` учитывает `ExternalURLBase`; добавлена
экспортированная `TenantQueueURL`;
- `app/conf/config.go` — env-override `SHARED_SQS_EXTERNAL_URL_BASE` в `applyEnvironmentDefaults`
(срабатывает и при отсутствии `goaws.yaml` — важная тонкость: `LoadYamlConfig` делает ранний
`return` без конфига, поэтому override там не срабатывал);
- `app/cmd/seed.go` — демо-очереди формируют URL через `gosqs.TenantQueueURL` (убрано
дублирование логики — оно и было причиной, что фикс «не применялся»);
- тест `app/gosqs/tenant_helpers_test.go`.
## 8. Устойчивость: systemd + локальный Redis (2026-10-10)
- **Redis** установлен на 213 (`apt install redis-server` 7.0.15), слушает `127.0.0.1:6379`,
включён **AOF** (`/etc/redis/redis.conf`: `appendonly yes`) — состояние переживает рестарт.
- **systemd-юнит** `/etc/systemd/system/shared-sqs.service`: `Restart=always`, `RestartSec=2`,
`WantedBy=multi-user.target` (автостарт при ребуте), `Requires=redis-server.service`,
`User=naeel`, `WorkingDirectory=/home/naeel/sqs-test`, `StandardOutput=append:.../sqs.log`.
- **env** `/etc/shared-sqs.env` (600, root): `SHARED_SQS_ADMIN_TOKEN`, `SHARED_SQS_SEED_DEMO`,
`SHARED_SQS_EXTERNAL_URL_BASE=https://sqs.sinefo.ru`, `REDIS_ADDR=127.0.0.1:6379`.
- Управление: `sudo systemctl {status,restart,stop,start} shared-sqs`.
**Закрыто:** крах процесса (авторестарт) и ребут ВМ (автостарт + Redis с диска из AOF; проверено —
сообщение пережило рестарт).
**НЕ закрыто:** падение самой ВМ — SQS и Redis лежат на одной машине (single point of failure).
Настоящая отказоустойчивость — после Шага 3 (Redis на отдельном хосте + несколько инстансов).
### Дефекты seed, обнаруженные при включении Redis (исправлены, коммит `c0aa618`)
1. **Демо-очереди не персистились** — после рестарта у демо-тенанта не было очередей.
Фикс: `persistence.SaveQueue` + `SaveMessages` в `app/cmd/seed.go`.
2. **Дубли тенантов** — каждый старт создавал нового демо-тенанта (новые креды). При
`Restart=always` это утечка. Фикс: идемпотентность в `seedDemoData` (проверка по имени
через `store.List()` → «сид пропущен»).
### Для прода
`SHARED_SQS_SEED_DEMO=false` (демо-данные не нужны), тенанты/очереди создаются через API
(они персистятся штатно).
## 9. Прод-хардненинг (2026-10-10, выполнено)
- `SHARED_SQS_SEED_DEMO=false` — демо-данные отключены (существующий тенант подхватывается из Redis).
- **admin-token сменён** на случайный 64-hex (хранится в `/etc/shared-sqs.env`, 600). Прежний
`dev-token-123` был слабым, а сервис теперь публичный. Прочитать: `sudo sed -n '1p' /etc/shared-sqs.env`.
- **logrotate** `/etc/logrotate.d/shared-sqs`: daily, rotate 7, compress, delaycompress,
copytruncate, `su naeel naeel` (copytruncate — процесс держит файл открытым).
- Billing (PostgreSQL) и JWT-валидация — **не настроены** (сервис работает без них).
## 10. Разбиение data-plane / control-plane (Шаг 2, 2026-10-10, коммит `c1520c0`)
- `router.NewDataPlane(store)` — публичный SQS API: `/health`, `/static`, `/examples`, SQS actions.
- `router.NewControlPlane(store, adminToken)` — админка: `/admin` (UI+API), `/metrics`, `/ui`, `/static`.
- `main`: два HTTP-сервера — data-plane на `0.0.0.0:<--port>` (4100, публично), control-plane
на `127.0.0.1:<--control-port>` (4101, только localhost).
- Проверено: публично `/health` → 200; SQS API работает; `/admin` и `/metrics` публично → **403**
(падают в SQS-auth — админка наружу **НЕ доступна**). Локально: `127.0.0.1:4101/admin` → 200,
`4101/metrics` без токена → 401.
**Почему пока два листенера в одном процессе, а не два процесса:** состояние (очереди/тенанты) —
in-memory; два процесса разъедутся (админка создаст тенанта в своей памяти — data-plane его не увидит).
Полноценный процесс-split — после Шага 3 (Redis как источник истины).
## 11. Надёжность записи (durability) и billing (2026-10-10)
### 11.1. Синхронная запись критичных операций
- `SendMessage` — sync (`SaveMessageSync`, фикс P1.1) ✅
- `DeleteMessage` / `DeleteMessageBatch` — sync (`DeleteMessagePersist`/`DeleteMessagesPersist`) ✅
- **`SendMessageBatch` — ИСПРАВЛЕНО (коммит `3559414`):** было `SaveMessages` (async) → стало
`SaveMessagesSync` (ответ 200 только после записи всех сообщений; при ошибке — откат append).
Заодно: не персистим дубли (раньше в Redis попадали дубликаты, которых нет в очереди).
- Проверено: batch из 2 сообщений в пустую очередь **пережил рестарт** сервиса.
- Остаётся async: `SaveQueue` (метаданные очереди: FIFO-состояние, duplicates) и метаданные
в Create/Delete/SetAttributes/Purge/Tags — потеря возможна только в узком окне при крахе;
сами сообщения защищены.
### 11.2. Billing — предусмотрен, но НЕ включён
- Модуль есть (`app/billing`, PG-воркер + буфер), включается через `BILLING_PG_HOST` (сейчас не задан).
- Решение: **оставить выключенным**; включать, когда появятся реальные клиенты/тарификация.
- Учёт операций уже рассчитан под биллинг — при включении заработает без изменений ядра.