183 lines
15 KiB
Markdown
183 lines
15 KiB
Markdown
# История правок shared-sqs — 2026-10-10 (публичная точка на ВМ, TLS, замеры)
|
||
|
||
> Запись по требованию «документировать всё, включая собственные ошибки» (приказ 2026-09-24).
|
||
> Предыдущие: `HISTORY/2026-10-07-session-log.md`, `doc/thinking/*-2026-10-08.md`.
|
||
> Сессия велась двумя моделями: сначала DeepSeek Flash, затем DeepSeek Pro (архитектор) —
|
||
> Pro перепроверял выводы Flash, часть из них оказалась неверной (см. раздел 4).
|
||
|
||
## 1. Задача
|
||
|
||
Вынести публичную точку SQS на ВМ `5.172.178.213` (обход больного managed-шлюза) и
|
||
**проверить гипотезу** из `MANUAL-NEXT-2026-10-08.md` (Шаг 1): нет ли на пути
|
||
«интернет → ВМ» той же сетевой болезни (зависания ~51 с, таймауты 31–33 с).
|
||
Требования владельца по пути: домен — только свой (`sinefo.ru`); `kube5s` из проекта убрать.
|
||
|
||
## 2. Хронология (что делалось и что получалось)
|
||
|
||
### 2.1. Сборка и запуск на ВМ
|
||
- `make build` → `shared-sqs` v0.1.35 (статический, x86_64, 17 МБ).
|
||
- `scp` → `naeel@5.172.178.213:~/sqs-test/shared-sqs`.
|
||
- Запуск memory-only: `SHARED_SQS_SEED_DEMO=true ./shared-sqs --admin-token=dev-token-123 --port=4100`.
|
||
- Локально на ВМ: `/health` → `200 {"status":"ok","version":"v0.1.35"}`;
|
||
`ListQueues` без подписи → `403 MissingAuthenticationToken` (корректно).
|
||
- ⚠️ Предупреждение `Failure to find default config file` — `goaws.yaml` на ВМ нет,
|
||
приложение работает на встроенных дефолтах (см. 5.1).
|
||
|
||
### 2.2. Разведка ВМ
|
||
- Публичный адрес — `5.172.178.213` (NAT); внутренний `10.10.102.21` (`ens192`), hostname `naeel-vm`.
|
||
- Снаружи прилетают только 80/443. Проверка портов:
|
||
- `8443` — занят DNAT: `iptables -t nat`: `--dport 8443 -j DNAT --to-destination 95.179.252.111:443`
|
||
(сервис `vpn-transit-dnat`, `enabled/active`) → весь трафик на 8443 уходит на Vultr **до** ВМ.
|
||
- `8888`/`9443` — `/dev/tcp` показывает «OPEN», но локально **никто не слушает** и HTTP не отвечает
|
||
(curl timeout) → это SYN-proxy провайдерского firewall, порты непригодны.
|
||
- `/etc/hosts` на ВМ содержит внутренние имена: `gitea.mgmt.nubes.ru`, `sqs.containerk8s.dev.nubes.ru`
|
||
→ `185.247.187.151`, `tf-registry.containerk8s.services.ngcloud.ru`, `s3.msk-1.ngcloud.ru`.
|
||
Резолвер ВМ — публичный (`8.8.8.8`, `1.1.1.1`), поэтому имена `*.services.ngcloud.ru` не резолвятся.
|
||
|
||
### 2.3. Блокер nginx (возник 10-08, снят 10-10)
|
||
- 10-08: `sudo nginx -t` **падал**: `host not found in upstream "contractor.pythonk8s.services.ngcloud.ru"
|
||
in sites-enabled/check.kube5s.ru:44` — внутреннее имя не резолвится.
|
||
- 10-10 (после ребута ВМ): конфиг `check.kube5s.ru` оказался **изменён** (catch-all `location /`
|
||
на contractor убран, добавлен `location /rf`) → `nginx -t` **проходит**, nginx перезагружаем.
|
||
- Следствие: переход к рабочему пути упростился (сначала была построена схема со вторым nginx, см. 2.4).
|
||
|
||
### 2.4. Второй nginx на 10443 (сделан, затем убран)
|
||
- Владелец пробросил порт: firewall `[22, 80, 443, 4443, 10443]`.
|
||
- Проверено: `10443` реально доходит до ВМ (`/health` 200), `4443` — нет.
|
||
- Поднят **отдельный** nginx: `/etc/nginx-sqs/nginx.conf`, pid `/run/nginx-sqs.pid`,
|
||
`listen 10443` → `proxy_pass http://127.0.0.1:4100`. Запуск `sudo nginx -c /etc/nginx-sqs/nginx.conf`.
|
||
|
||
### 2.5. Замеры (гипотеза ПОДТВЕРЖДЕНА)
|
||
|
||
| Тест | Инструмент | Результат |
|
||
|---|---|---|
|
||
| HTTP, 30 запросов 1КБ/10КБ/100КБ/256КБ | `curl --aws-sigv4` | 30/30 **200**, max **0.99 с** |
|
||
| HTTPS, 10 запросов 26КБ…260КБ | `curl --aws-sigv4` | 10/10 **200**, 0.7–3.2 с |
|
||
| Long-poll `ReceiveMessage` `WaitTimeSeconds=20` | `curl` | **20.3 с, 200** (корректно) |
|
||
|
||
Ни зависаний ~51 с, ни таймаутов 31–33 с. **На пути «интернет → ВМ 213» болезни нет.**
|
||
|
||
### 2.6. DNS
|
||
- `sinefo.ru` делегирован в реестре .ru на `ns3.ngcloud.ru` / `ns4.ngcloud.ru` (проверено через `a.dns.ripn.net`).
|
||
- `sqs.sinefo.ru A 5.172.178.213` — публично резолвится. Ранее запись была `185.247.187.83`,
|
||
что = **IP самого DNS-сервера** `ns3.ngcloud.ru`, а не ВМ (исправлено владельцем).
|
||
|
||
### 2.7. TLS (Let's Encrypt)
|
||
- `sudo certbot certonly --webroot -w /var/www/acme -d sqs.sinefo.ru --non-interactive --agree-tos`
|
||
(аккаунт LE на ВМ уже существовал → без нового e-mail) → `/etc/letsencrypt/live/sqs.sinefo.ru/`.
|
||
- Срок: до 2027-01-08, автопродление настроено.
|
||
- ACME-челлендж обслуживается главным nginx (vhost 80 с `location /.well-known/acme-challenge/`).
|
||
|
||
### 2.8. Публичная точка (итог)
|
||
- Главный nginx, `/etc/nginx/conf.d/sqs-sinefo.conf`:
|
||
- **80**: ACME-челлендж + редирект на https;
|
||
- **443 ssl**: сертификат `sqs.sinefo.ru` + `proxy_pass http://127.0.0.1:4100`, `Host $http_host`.
|
||
- Проверено снаружи: `https://sqs.sinefo.ru/health` → 200 (cert valid), SigV4, 256 КБ за 1.05 с.
|
||
|
||
### 2.9. Уборка
|
||
- Второй nginx (10443) остановлен, `/etc/nginx-sqs` удалён; порт 10443 свободен.
|
||
- `conf.d/sqs-sinefo.conf` — заменён на нормальный vhost (было: HTTP-only левак от 10-08).
|
||
- Из **актуального** дока `doc/2026-08-13-migration-to-managed.md` убран `kube5s.ru`
|
||
(квинтет: `iot.kube5s.ru`→`iot.nubes.ru`, `qu.kube5s.ru`×3→`sqs.containerk8s.dev.nubes.ru`),
|
||
коммит `a47743f`. В `doc/legacy/` (история) не трогал.
|
||
|
||
### 2.10. Фикс QueueUrl (кодинг)
|
||
- Проблема: `QueueUrl` = `http://region.host:port/...` (плейсхолдеры) — клиенты без `endpoint_url` сломаны.
|
||
- Реализовано: `Environment.ExternalURLBase` + env `SHARED_SQS_EXTERNAL_URL_BASE`.
|
||
- **Ошибка по пути №1:** env-override сначала поместил после `models.CurrentEnvironment = envs[env]`
|
||
— не сработал: `LoadYamlConfig` делает ранний `return` без `goaws.yaml`. Перенёс в
|
||
`applyEnvironmentDefaults` (вызывается через defer → всегда).
|
||
- **Ошибка по пути №2:** после переноса всё равно не работало — демо-seed (`app/cmd/seed.go`) собирал
|
||
URL **своей копией** логики, а не через `tenantQueueURL`. Вынес `gosqs.TenantQueueURL`, seed переведён на него.
|
||
- Коммиты `469132b`, `ca0372b`; тест `app/gosqs/tenant_helpers_test.go`. Проверено снаружи:
|
||
`list-queues` → `https://sqs.sinefo.ru/t-.../orders`.
|
||
|
||
### 2.11. Устойчивость: systemd + локальный Redis
|
||
- Установлен `redis-server` (7.0.15) на 213, включён AOF (`appendonly yes`).
|
||
- systemd-юнит `shared-sqs.service` (`Restart=always`, `enable`) + `/etc/shared-sqs.env`.
|
||
- Проверено: сообщение пережило рестарт; `systemctl is-enabled/active` → enabled/active.
|
||
- **Дефекты seed (обнаружены и исправлены, коммит `c0aa618`):** демо-очереди не персистились;
|
||
каждый старт плодил нового демо-тенанта (утечка при `Restart=always`). Фикс: `SaveQueue`+
|
||
`SaveMessages` + идемпотентность через `store.List()`.
|
||
- Управление: `sudo systemctl {status,restart,stop} shared-sqs`.
|
||
|
||
### 2.12. Прод-хардненинг
|
||
- `SHARED_SQS_SEED_DEMO=false`; admin-token сменён на случайный 64-hex (в `/etc/shared-sqs.env`);
|
||
logrotate для `sqs.log`.
|
||
- Billing/JWT не настроены. Проверено: сервис работает на существующем тенанте из Redis.
|
||
|
||
### 2.13. Шаг 2 — разбиение data-plane / control-plane
|
||
- `router.NewDataPlane` (публичный SQS API) + `router.NewControlPlane` (админка/метрики/UI).
|
||
- `main`: два листенера — `0.0.0.0:4100` (публично) и `127.0.0.1:4101` (control-plane, только локально).
|
||
- Проверено: публично `/admin`/`/metrics` → 403 (наружу не доступны), SQS API работает; локально
|
||
`4101/admin` → 200. Коммит `c1520c0`.
|
||
- Примечание: пока один процесс с двумя листенерами (общее in-memory состояние); два процесса —
|
||
после Шага 3.
|
||
|
||
### 2.14. Шаг 3 — начало: дизайн + тенанты read-through Redis
|
||
- Дизайн: `doc/thinking/step3-state-extraction-design-2026-10-10.md` (варианты A/B/C, рекомендация
|
||
C→A, что замерить).
|
||
- Код (коммит `779a71d`): Redis-индекс `ssq:tenant_ak` (accessKey → JSON тенанта) + бэкфилл при старте;
|
||
auth-middleware читает тенанта из Redis при промахе локальной памяти.
|
||
- Нужно для ЛЮБОГО multi-instance варианта; в одном процессе — страховка.
|
||
- Проверено: индекс заполнен (`hlen ssq:tenant_ak` = 1), регрессий нет.
|
||
|
||
## 3. Грабли (для повторного использования)
|
||
|
||
1. **`proxy_set_header Host $host` теряет порт** → SigV4 `SignatureDoesNotMatch`.
|
||
Нужен `$http_host`. (nginx `$host` — имя без порта; `$http_host` — заголовок целиком.)
|
||
2. **`~/.aws/config [default]` содержит `endpoint_url = https://s3.msk-1.ngcloud.ru`** →
|
||
`aws sqs ...` без явного `--endpoint-url` уходит в S3 и получает
|
||
`405 MethodNotAllowed` (Ceph RGW, `HostId=...-dc1-msk-1`). **Всегда задавать `--endpoint-url`.**
|
||
3. **Креды демо-тенанта рандомны при каждом старте** — брать из `~/sqs-test/sqs.log`
|
||
(`seed: создан тенант ... AccessKey=...`). При «address already in use» в логе креды **мёртвого** инстанса.
|
||
4. `pkill -f "http.server 10443"` по SSH **самоматчится** на строку ssh-команды и убивает сессию —
|
||
использовать `pkill -x <имя>`.
|
||
5. Порт `8443` на 213 **нельзя занимать** — там DNAT транзита на Vultr.
|
||
6. **Managed Redis недоступен с 213**: его endpoint — внутренний k8s DNS
|
||
(`redisk8s.<uid>.svc.cluster.local:6379`), резолвится только изнутри кластера. С ВМ — только
|
||
локальный Redis или внешний endpoint от NUBES.
|
||
7. Локальный Redis на 213 **не спасает от падения ВМ** (SQS и Redis — на одной машине, SPOF).
|
||
|
||
## 4. ОШИБКИ (сводка, включая ошибки Flash)
|
||
|
||
| # | Что утверждалось/сделано | Что на самом деле | Исправление |
|
||
|---|---|---|---|
|
||
| 1 | «При ребуте ВМ nginx не поднимется» (из-за битого upstream) | После ребута nginx `active`, 80/443 слушают | Неверный вывод; снят при перепроверке |
|
||
| 2 | «`.83` — мёртвый IP» | `185.247.187.83` = живой DNS `ns3.ngcloud.ru` (порт 53); проверял только 80/443 | Признано при перепроверке |
|
||
| 3 | Запуск второго инстанса при живом первом | `fatal: bind: address already in use`; лог перезаписан кредами мёртвого → `InvalidClientTokenId` | `pkill -x shared-sqs`, один инстанс, креды из лога |
|
||
| 4 | Второй nginx: `Host $host` | SigV4 `SignatureDoesNotMatch` | `Host $http_host` |
|
||
| 5 | Прямые DNS-выводы по регексу `location / ` | Первый `grep` не нашёл (паттерн с пробелом) — чуть не сделал ложный вывод | Перечитал файл целиком (`cat`) |
|
||
| 6 | «Только 80/443 открыты» | 8888/9443 «принимают» TCP (SYN-proxy), но пусты | Уточнено: рабочие — только 80/443 |
|
||
|
||
## 5. Состояние и воспроизведение
|
||
|
||
### 5.1. Итоговое состояние
|
||
- **Работает:** `https://sqs.sinefo.ru` (443, TLS) → shared-sqs на ВМ 213 (memory-only, `127.0.0.1:4100`).
|
||
- SQS: `~/sqs-test/shared-sqs`, лог `~/sqs-test/sqs.log`, pid меняется. Демо-тенант + 5 очередей.
|
||
- **QueueUrl — ИСПРАВЛЕНО** (см. 2.10): `https://sqs.sinefo.ru/<tenantID>/<queue>`.
|
||
|
||
### 5.2. Как поднять заново
|
||
```bash
|
||
# на ВМ 213
|
||
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 &
|
||
# креды демо-тенанта — в sqs.log
|
||
grep "seed: создан тенант" ~/sqs-test/sqs.log | tail -1
|
||
# проверка снаружи
|
||
curl -sS https://sqs.sinefo.ru/health
|
||
```
|
||
|
||
### 5.3. Откат
|
||
```bash
|
||
sudo rm /etc/nginx/conf.d/sqs-sinefo.conf && sudo nginx -t && sudo systemctl reload nginx
|
||
pkill -x shared-sqs
|
||
# бэкапы nginx на ВМ: /etc/nginx/conf.d.bak-*
|
||
```
|
||
|
||
## 6. Открытые вопросы
|
||
|
||
1. Клиенты без явного `endpoint_url` — есть ли такие (вопрос №5 мануала). `QueueUrl` уже корректный.
|
||
2. Egress ВМ: `curl https://api.ipify.org` с ВМ вернул пусто — влияет на billing (PG) и JWT-API.
|
||
3. Дальше по мануалу: Шаг 2 (разбиение data-plane/control-plane), Шаг 3 (вынос состояния).
|