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

9.4 KiB
Raw Blame History

Публичная точка 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 (эталон)

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. Управление

# запуск 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 как источник истины).