203 lines
14 KiB
Markdown
203 lines
14 KiB
Markdown
# Журнал сессии — 2026-08-14 (продолжение миграции shared-sqs)
|
||
|
||
**Рабочая папка:** /home/naeel/nubes/SQS-service
|
||
|
||
---
|
||
|
||
## Текущее состояние
|
||
|
||
- Сервис ЗАДЕПЛОЕН и работает: `https://sqs.containerk8s.dev.nubes.ru`
|
||
- `/health` → `OK` (200)
|
||
- Образ: Docker Hub `naeel/shared-sqs:latest`
|
||
- Репа: Gitea `gitea.services.ngcloud.ru/Nail/SQS-service`
|
||
|
||
---
|
||
|
||
## Что сделано сегодня
|
||
|
||
1. Добавлен инфо-блок «Информация о сервисе» на страницу входа `/ui` (`app/ui/index.html`).
|
||
Коммит `032ca66`. Образ пересобран, запушен (`digest 4bb4f20d`).
|
||
|
||
2. **Проблема:** вход в UI с реальным JWT → `Ошибка: token rejected by cloud API (HTTP 403)`.
|
||
Причина: контейнер валидировал JWT на `deck-api-test.ngcloud.ru/api/v1`
|
||
(default в `app/admin/admin.go`), а production JWT туда не подходит.
|
||
|
||
3. **Поиск правильного endpoint** (в `~/tf_provider`, по указанию пользователя):
|
||
- `deck-api*.ngcloud.ru` — **ЛЕГАСИ** (пользователь явно запретил этот формат)
|
||
- Правильный: **API Gateway** `lk-api-gateway.ngcloud.ru`
|
||
|
||
4. Найдено в `~/tf_provider/TOOLS/config/prod/profile.env`:
|
||
```
|
||
NUBES_API_ENDPOINT="https://lk-api-gateway.ngcloud.ru/api/v1/svc"
|
||
```
|
||
(test: `...-test...`, dev: `...-dev...`)
|
||
Также default в `TOOLS/yaml-generator/internal/config/config.go`:
|
||
`getenvDefault("NUBES_API_ENDPOINT", "https://lk-api-gateway.ngcloud.ru/api/v1/svc")`
|
||
|
||
5. **Тест токенов против gateway** (токены из `/home/naeel/nubes/tokens_all.txt`):
|
||
- БЕЗ User-Agent → **все 403** (DDoS-Guard блокирует)
|
||
- С `User-Agent: Mozilla/5.0`:
|
||
| Токен | gateway-PROD | gateway-TEST |
|
||
|---|---|---|
|
||
| tazetdinovn@gmail.com (TEST) | 401 | **200** |
|
||
| tazet@narod.ru (PROD) | **200** | 401 |
|
||
| ntazetdinov@nubes.ru (PROD) | **200** | 401 |
|
||
|
||
---
|
||
|
||
## Выводы (факты)
|
||
|
||
1. Правильный `NUBES_ENDPOINT` = **`https://lk-api-gateway.ngcloud.ru/api/v1/svc`**
|
||
2. **КРИТИЧНО:** наш код `PingNubesAPI` (`app/auth/jwt.go`) **НЕ шлёт User-Agent** →
|
||
gateway возвращает 403 → токен всегда отклоняется.
|
||
Нужна правка кода: добавить `User-Agent: Mozilla/5.0`.
|
||
|
||
---
|
||
|
||
## Предстоящие правки (СОГЛАСОВАНЫ? — нет, ждут «делай»)
|
||
|
||
1. `Dockerfile`: `ENV NUBES_ENDPOINT=https://lk-api-gateway.ngcloud.ru/api/v1/svc`
|
||
2. `app/auth/jwt.go`: в `PingNubesAPI` добавить `User-Agent: Mozilla/5.0`
|
||
3. `app/admin/admin.go`: сменить default endpoint на gateway PROD
|
||
|
||
После правок: пересборка → push Docker Hub → redeploy → проверка входа в UI.
|
||
|
||
---
|
||
|
||
## Правила, зафиксированные в этой сессии
|
||
|
||
- Код менять ТОЛЬКО после согласования с пользователем.
|
||
- deck-api*.ngcloud.ru — ЛЕГАСИ, НЕ использовать (только lk-api-gateway).
|
||
- ngcloud за DDoS-Guard: curl-запросы всегда с `User-Agent: Mozilla/5.0`.
|
||
- Всё документировать в HISTORY.
|
||
|
||
---
|
||
|
||
## v0.1.26 — вывод версии на экран + фикс схемы тегов (14.08.2026, утро)
|
||
|
||
**Проблема**: пользователь заметил, что версии не повышаются — всё время пушился только `latest`,
|
||
под мог кэшировать `latest` и redeploy подтягивал старый образ. Плюс запрос: выводить версию на экран.
|
||
|
||
**Что сделано**:
|
||
1. `app/models/constants.go`: `var Version = "dev"` (переопределяется через ldflags).
|
||
2. `app/router/router.go`: публичный `/health` теперь отдаёт JSON `{"status":"ok","version":"v0.1.26"}`.
|
||
3. `app/admin/admin.go`: `adminHealthDetail` дополнен полем `version` (в /admin/health и /ui/api/health).
|
||
4. `app/ui/index.html`:
|
||
- блок «Информация о сервисе» на странице входа: строка «Версия» (fetch публичного /health);
|
||
- образ в инфо-блоке теперь `naeel/shared-sqs:v0.1.26`;
|
||
- дашборд: карточка «Версия» (health.version).
|
||
5. `Makefile`: `VERSION=v0.1.26`, `LDFLAGS=-X shared-sqs/app/models.Version=$(VERSION)`,
|
||
`docker-build` тегает `$(VERSION)` + `latest`; исправлена цель `build` (`./app/cmd/` вместо одного файла goaws.go).
|
||
6. `Dockerfile`: `ARG VERSION=dev`, ldflags при сборке.
|
||
|
||
**Проверки**: make build OK; `/health` → `{"status":"ok","version":"v0.1.26"}`; go test router/admin/models — ok.
|
||
|
||
**Git**: коммит `3a105de` «v0.1.26: …», пуш в Gitea (`166baa9..3a105de`).
|
||
|
||
**Образ**: `naeel/shared-sqs:v0.1.26` собран и запушен на Docker Hub,
|
||
digest `sha256:b4d1dccda75a67e1a5db3887576e1112993d6485504e683d0fa82a2374bca99b`,
|
||
`latest` указывает на него же.
|
||
|
||
**Следующий шаг**: в deck указать путь образа `naeel/shared-sqs:v0.1.26` и сделать redeploy —
|
||
тогда под гарантированно стянет новый образ (код с перебором стендов NUBES_ENDPOINT + версия на экране).
|
||
|
||
---
|
||
|
||
## РАЗВЯЗКА: образ обновлён через kubectl, вход в UI работает (14.08.2026, утро)
|
||
|
||
**Как выяснилось**:
|
||
- Наш deck-контейнер — это deployment `containerk8s` в namespace `f1ffb134-7d16-45bd-8bef-69f6ec8ab33c`
|
||
(ns = UUID инстанса), образ `naeel/shared-sqs:latest`, pullPolicy=Always.
|
||
- Старый апрельский Helm-релиз `shared-sqs` (ns `shared-sqs`, v0.1.24, 122 дня) — ЛЕГАСИ,
|
||
не имел отношения к живому сервису. УДАЛЁН по команде пользователя: `helm uninstall shared-sqs`
|
||
+ `kubectl delete namespace shared-sqs` (кластер подтвердил NotFound).
|
||
- Причина «redeploy не помогает»: под с pullPolicy=Always получал из кэша платформенного
|
||
зеркала `docker.io` (в kube-system всё на mirror.k8s.ngcloud.ru) старый digest `be2108fd…`,
|
||
хотя в Docker Hub `latest` = `b4d1dccd…` (v0.1.26). Кэш зеркала по тегу `latest` не протухал
|
||
из-за частых пушей.
|
||
|
||
**Что сделано**:
|
||
1. `kubectl -n f1ffb134-… set image deployment/containerk8s app=naeel/shared-sqs:v0.1.26`
|
||
(внимание: контейнер в поде называется `app`, а не `containerk8s`).
|
||
Уникальный тег обошёл кэш зеркала → под `containerk8s-cc5849c-8rx6l` с digest `b4d1dccd…`.
|
||
2. Проверка: `GET /health` → `{"status":"ok","version":"v0.1.26"}`.
|
||
3. Вход в UI с JWT `tazetdinovn@gmail.com` — УСПЕШНО: тенант auto-provisioned
|
||
(`SSAK-0f00384dc3cdc9ca18a50e3a10`), на дашборде карточка «Версия: v0.1.26».
|
||
|
||
**Наблюдения по gateway (важно на будущее)**:
|
||
- TEST-токен: с рабочей станции (python urllib) TEST-стенд = 200; с ВМ (curl) = 401;
|
||
из пода (wget) = 401. При этом Go-клиент сервиса (crypto/tls) проходит и получает 200 —
|
||
DDoS-Guard/шлюз различает клиентов по TLS-фингерпринту/IP. Тесты curl/wget из пода
|
||
НЕ показательны для Go-клиента сервиса.
|
||
- Перебор стендов PROD→TEST→DEV в `PingNubesAPI` работает как задумано.
|
||
|
||
**Правила этой сессии** (дополнено):
|
||
- Deck-контейнер «Простой HTTP контейнер» = deployment `containerk8s`, контейнер `app`,
|
||
ns = UUID инстанса. Правки образа через kubectl НЕ откатываются (ownerReferences нет).
|
||
- `latest` в этом кластере кэшируется зеркалом → обновлять только уникальным тегом
|
||
через `kubectl set image`.
|
||
|
||
---
|
||
|
||
## v0.1.27 — страница-описание сервиса на корне `/` (14.08.2026)
|
||
|
||
**Задача**: на `https://sqs.containerk8s.dev.nubes.ru/` разместить описание сервиса и список команд
|
||
в дизайне Nubes (`~/nubes/design/nubes-design-system.md`).
|
||
|
||
**Что сделано**:
|
||
1. `app/ui/info.html` — публичная страница: топбар с лого, hero с бейджем «тестирование»,
|
||
карточка «Подключение» (endpoint, SigV4, регион us-east-1, /ui, /health, /metrics, Redis),
|
||
таблица всех 17 Actions, примеры AWS CLI. Версия подставляется через `{{VERSION}}`.
|
||
2. `app/ui/static/{logo, favicon}.svg` — SVG-файлы из design-системы (лого ТОЛЬКО файлом, не inline).
|
||
3. `app/ui/embed.go`: `InfoHandler(version)`, `StaticHandler()`; embed расширен (`info.html static`).
|
||
4. `app/router/router.go`: `GET /` без Action → info-страница; POST/GET с Action → SQS API как раньше.
|
||
Маршрут `/static` — публично. SQS API не задет (проверено: POST Action=ListQueues → 403 без сигнатуры, как было).
|
||
|
||
**Инцидент rsync (важно!)**: rsync с ДВУМЯ источниками (`app/` + `Makefile`) в dest `~/terra/SQS-service/`
|
||
высыпал содержимое `app/` в КОРЕНЬ репо на ВМ (`~/terra/SQS-service/ui/`, `router/` и т.д.), а настоящий
|
||
`app/` остался старым → первый образ v0.1.27 (digest e69807fe) собрался из СТАРОГО кода (v0.1.26).
|
||
Исправлено: повторный rsync с ОДНИМ источником `app/` → `~/terra/SQS-service/app/`, пересборка.
|
||
|
||
**Итог**: правильный образ v0.1.27, digest `sha256:51c94894…`. В кластер поставлен ПО DIGEST
|
||
(`set image app=naeel/shared-sqs@sha256:51c94894…`) — digest обходит кэш зеркала по тегу.
|
||
Под `containerk8s-7985cd4985-gwkc6`, Ready. Проверки на проде: `/health` → v0.1.27,
|
||
`GET /` → 200 text/html (страница), `/static/logo.svg` → 200, SQS POST → 403 (норма без сигнатуры).
|
||
|
||
**Незакрытый хвост**: на ВМ в корне `~/terra/SQS-service/` остался мусор от ошибочного rsync
|
||
(топ-уровневые `ui/`, `router/` и др. с новым содержимым). На сборку не влияет (Dockerfile берёт
|
||
`./app/`). Удаление — по команде пользователя.
|
||
|
||
## Зачистка мусора на ВМ (14.08.2026)
|
||
По команде пользователя удалены untracked топ-уровневые каталоги в `~/terra/SQS-service/`,
|
||
оставшиеся от ошибочного rsync (дубли содержимого `app/`):
|
||
`admin auth billing conf gosqs interfaces metrics models persistence router tenant ui utils`.
|
||
`app/` не тронут (проверено: rootHandler на месте, app/ui/static и info.html целы).
|
||
Оставлено: app, cmd, doc, Dockerfile, go.mod, go.sum, HISTORY, Makefile, README.md, secrets.
|
||
|
||
---
|
||
|
||
## Архитектурный разбор (Claude Sonnet) — переход идентичности на email (14.08.2026)
|
||
|
||
**Запрос к Sonnet**: файлы только `app/auth/jwt.go`, `app/tenant/tenant_store.go`, `app/admin/admin.go`,
|
||
`app/gosqs/tenant_helpers.go`, `app/persistence/redis.go`, `app/cmd/goaws.go`,
|
||
`app/auth/auth_middleware.go`, `app/ui/index.html`.
|
||
|
||
**Ответ Sonnet (выводы)**:
|
||
1. Сломается: `TenantIDFromSub()` в jwt.go; вызов в `jwtAuth` (admin.go ~236); `access_key/secret_key`
|
||
в ответе jwtAuth; `GetBySub` в `jwtMiddleware` (~288); `bySub` → `byEmail` в TenantStore
|
||
(NewTenantStore, CreateFromJWT, Delete, LoadTenant, GetBySub).
|
||
2. Пересчёт легаси при 0 очередях безопасен, но ОБЯЗАТЕЛЬНО: удалить старый тенант из Redis
|
||
(`ssq:tenants` по старому ID) и сохранить новый — иначе после рестарта старые рандомные ключи оживут.
|
||
Весь пересчёт — под `s.mu.Lock()`.
|
||
3. Детерминированные функции — в jwt.go; byEmail-индекс — в tenant_store.go; LoadTenant индексирует
|
||
по Email только при `Email != ""` (защита от коллизии `byEmail[""]` с демо-тенантом CreateFixed).
|
||
4. Дыра — orphan AccessKey в byAccessKey: при пересчёте удалять из byAccessKey ДО добавления нового.
|
||
5. Критичное неучтённое: (a) email может быть пустым → проверка в jwtAuth (400), не в ParseJWTClaims;
|
||
(b) QueueUrl содержит TenantID — при смене ID URL меняется, у клиентов захардкоженные URL сломаются
|
||
(сейчас очередей нет — не критично); (c) демо-тенант без Email не должен попадать в byEmail.
|
||
|
||
**Решение (принято)**: все пункты включаем в v0.1.28. Токен — только аутентификация;
|
||
email из JWT детерминированно задаёт TenantID/AccessKey/SecretKey; рандома нет;
|
||
ключи на экране не светятся — только по кнопке в модалке с подсказкой;
|
||
`POST /ui/api/auth` перестаёт отдавать ключи; новый `GET /ui/api/credentials` под JWT-сессией.
|