Files
SQS-service/HISTORY/2026-08-14-session-log.md
T

29 KiB
Raw Blame History

Журнал сессии — 2026-08-14 (продолжение миграции shared-sqs)

Рабочая папка: /home/naeel/nubes/SQS-service


Текущее состояние

  • Сервис ЗАДЕПЛОЕН и работает: https://sqs.containerk8s.dev.nubes.ru
  • /healthOK (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):


Выводы (факты)

  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); bySubbyEmail в 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-сессией.


v0.1.28 — email-идентичность, детерминированные креды, credentials-эндпоинт (14.08.2026)

Что сделано:

  1. app/tenant/tenant_store.go: индекс bySubbyEmail; GetByEmail вместо GetBySub; CreateFromJWT(sub, email, maxQueues) — ID/AccessKey/SecretKey детерминированы из email (SHA256, соль shared-sqs:secret-key:v1 для секрета). Рандом остался только для manual-тенантов. Легаси-тенант (старые ключи/ID) при входе полностью пересчитывается: старые индексы и Redis-запись удаляются под мьютексом.
  2. app/auth/jwt.go: TenantIDFromSub (sless) УДАЛЁН — связей с внешними сервисами больше нет.
  3. app/admin/admin.go: jwtAuth требует email (400 без него), НЕ возвращает access/secret key; новый GET /ui/api/credentials отдаёт ключи под JWT-сессией; jwtMiddleware по email.
  4. app/ui/index.html: Access Key убран из таблицы и карточки тенанта; кнопки «Credentials» (дашборд + тенант) открывают модалку с ключами и подсказкой (зачем ключи, export-команды, пример aws cli). Логин-страница: образ v0.1.28.
  5. Makefile: VERSION=v0.1.28.

Проверки: build OK, vet OK, тесты admin/models OK. Локально через мок-gateway: auth без ключей ✓, credentials отдаёт ключи ✓, без токена 401 ✓. Хеши сверены с python: tenant_id t-fec713a719ad0b33, AK SSAK-fec713a719ad0b33c91fa54a, SK e898ed410a….

Деплой: образ v0.1.28 digest sha256:c6b63f4c…, поставлен по digest (set image … app=naeel/shared-sqs@sha256:c6b63f4c…), под containerk8s-86df69bfb-drv9x Ready. /health на проде → {"status":"ok","version":"v0.1.28"}.

⚠ ПРОБЛЕМА (не наш код): TEST-gateway теперь отдаёт 401 на запросы с датацентровых IP (кластер iot-naeel И ВМ 5.172.178.213) — с рабочей станции тот же токен даёт 200. Ранее (~09:00) под успешно валидировался. Похоже на rate-limit/бан DDoS-Guard по egress-IP платформы после сегодняшних частых запросов. Вход в UI будет 403, пока шлюз не отпустит. SQS API, /health, /, /metrics не затронуты.


v0.1.29 — светлый Nubes-дизайн консоли, читаемая модалка Credentials (14.08.2026)

Проблема (жалоба юзера): консоль была на тёмной палитре старого iot-дизайна (чёрный фон, мелкий шрифт), модалка Credentials нечитаемая. Требование: Nubes-дизайн, читаемо.

Что сделано (app/ui/index.html):

  1. Палитра → светлая design-system Nubes: bg #fafafa, поверхности #ffffff, border #d1d5db, primary #2563eb, muted #6b7280, карточки белые с radius 12 + тень.
  2. Логотип без invert-фильтра (тёмный лого на белом), шрифт system-ui 14px, заголовки 20-22px, бейджи/кнопки в светлых тонах, textarea/input белые.
  3. Модалка Credentials переделана: читаемый блок подсказки — зачем ключи + ДВА способа (env-export И файл ~/.aws/credentials) + пример с --profile sqs. Ширина модалки 560px, скролл.

Деплой: v0.1.29 digest sha256:d1ccb53d…, set image по digest, под containerk8s-75f766b9d7-82wl7. Проверки: /health → v0.1.29; /ui отдаёт светлую тему (#fafafa/#ffffff/#2563eb, тёмных цветов нет).

Остаётся открытым: gateway 401 с датацентровых IP (см. раздел v0.1.28) — вход в UI заработает, когда шлюз отпустит egress платформы.


v0.1.30 — локальные логотип и favicon в консоли (14.08.2026)

В консоли /ui были внешние ассеты terra.k8c.ru/.../logo.svg и favicon.png — не грузились (битый лого в шапке, нет фавикона). Заменены на локальные вшитые /static/logo.svg и /static/favicon.svg (embed в образ, раздаются с корня роутером). Деплой: digest sha256:deab2979…, под готов. Проверки: /health → v0.1.30, /ui отдаёт 2× static/logo.svg + 1× static/favicon.svg, оба файла отвечают 200.


v0.1.31 — админский UI /admin + юзерский UI без слова «тенант» (14.08.2026)

Сделано:

  1. Новый app/ui/admin.html — админская консоль: вход по admin-токену, таблица тенантов (имя, ID, Access Key, лимит, статус, создан), создание тенанта с показом ключей в модалке, удаление с подтверждением. Светлый Nubes-дизайн, /static/logo.svg + favicon, версия через {{VERSION}}.
  2. app/ui/embed.go: embed admin.html + AdminHandler(version).
  3. app/router/router.go: GET /admin и /admin/ → админская страница (зарегистрированы ДО admin subrouter, чтобы PathPrefix("/admin") с bearer-auth их не перехватил).
  4. Юзерский UI: из видимого текста убрано слово «тенант» — «Мой аккаунт», «Аккаунт не найден», убрана карточка-счётчик «Тенанты», «креды вашего аккаунта», «Создать аккаунт», ссылка на /admin в инфо-блоке логина.

Проверки: build OK, vet OK, тесты admin OK. Локально: /admin → 200 (страница), /admin/tenants без токена/с неверным токеном → 401, с верным → []. Прод: /health → v0.1.31, /admin → 200 (SQS Admin, v0.1.31), /ui содержит «аккаунт» 3× и не содержит «Тенанты».

Инциденты по пути:

  • Пуш на Docker Hub спотыкался об IPv6 (network unreachable) и 502 — добил ретраями, digest v0.1.31 = sha256:34c3619d….
  • Токен kubeconfig на ВМ истёк в 09:14 MSK (24ч) — юзер обновил, деплой добит: set image …@sha256:34c3619d…, под containerk8s-768668b9d4-stcgv Ready.

v0.1.32 — главная с пошаговым стартом, примеры на /examples (14.08.2026)

Жалобы юзера: главная не объясняет с первого раза, как войти/где креды/что делать; примеры засоряли главную мелким шрифтом; в консоли лишняя хлебная крошка и таблица из одной строки.

Сделано:

  1. app/ui/info.html: блок «Как начать — 3 шага» (1. Открыть консоль → кнопка, 2. Получить ключи через Credentials, 3. Подключить клиент с быстрым примером + ссылка на /examples). Таблица команд и примеры убраны с главной, вместо них — карточка-ссылка на /examples. Слово «тенант» убрано из текста главной.
  2. Новый app/ui/examples.html (GET /examples): крупный шрифт 15px, команды 14px, пошагово: ключи (env И файл ~/.aws/credentials) → создать очередь → отправить → получить/удалить, плюс таблица всех 17 Actions.
  3. app/ui/embed.go: ExamplesHandler; app/router/router.go: маршрут /examples.
  4. app/ui/index.html: enterApp сразу открывает аккаунт (showTenant первого тенанта), дашборд с таблицей — только запасной вариант; хлебные крошки «Мой аккаунт › email» удалены.

Проверки: локально root → «Как начать/Открыть консоль», /examples → 200 (Шаг 1..4, таблица). Прод: /health → v0.1.32, / → 200, /examples → 200. digest sha256:4834b999….


v0.1.33 — строгий нейтральный стиль текстов (14.08.2026)

Жалоба: фамильярный тон на главной («Войди», «Получи», «Подключи свой клиент»). Заменено на нейтральный официальный стиль:

  • «Порядок действий — 3 шага»: «Вход в консоль», «Получение ключей доступа», «Подключение клиента».
  • Примеры (/examples): «Шаг 1. Указание ключей доступа», «Шаг 2. Создание очереди», «Шаг 3. Отправка сообщения», «Шаг 4. Получение и удаление сообщения»; формулировки без повелительных форм. Деплой: v0.1.33 digest sha256:196283c1…, проверено: /health → v0.1.33, корень отдаёт новые заголовки.

Полный цикл подтверждён: email-креды работают на проде (14.08.2026)

Юзер залогинился (gateway отпустил egress платформы), легаси-тенант пересчитан, в модалке Credentials показаны детерминированные ключи:

  • Access Key: SSAK-fec713a719ad0b33c91fa54a
  • Secret Key: e898ed410a20ff166f51a52ba39a2954fb87e2da200b1648c3975772bfe9e2b4

Совпадают с расчётным SHA256(email) — email-идентичность v0.1.28 работает. Smoke-тест: aws sqs list-queues --endpoint-url https://sqs.containerk8s.dev.nubes.ru с этими ключами → 200, {"QueueUrls": []}. SigV4-аутентификация на проде подтверждена.


Q&A с Claude Sonnet — кэш валидации JWT (14.08.2026)

Запрос: файлы только app/auth/jwt.go, app/admin/admin.go; задача — кэш успешной валидации токена (TTL 5 мин) из-за нестабильного шлюза Nubes.

Ответ Sonnet:

  1. Кэш — в auth (новый app/auth/cache.go): PingNubesAPICached, WarmTokenCache, StartTokenCacheCleanup. В admin.go: jwtMiddleware → PingNubesAPICached; jwtAuth → свежая проверка + WarmTokenCache; NewHandler → cleanup.
  2. Ключ hex(sha256(token)); map[string]tokenCacheEntry{validUntil} + sync.RWMutex (не sync.Map — нужна итерация при очистке); уборщик — ticker 5 мин.
  3. ErrTokenRejected (sentinel, %w) — отличать 401/403 от сетевых ошибок; сигнатура PingNubesAPI не меняется.
  4. Таблица: HIT → nil; MISS+2xx → записать; MISS+401/403 → удалить, вернуть ошибку; MISS+сетевая → ошибка; протухший+сетевая → опция grace +2 мин (принято).
  5. Гонки: параллельные MISS безвредны (singleflight избыточен); запись в кэш до проверки ctx; рестарт пода сбрасывает кэш (норма); exp проверяется локально ДО кэша/шлюза.

Решение (принято): всё выше + grace-продление 2 мин при сетевой ошибке для ранее валидного токена. TTL 5 мин; отозванный токен умирает максимум через 5 мин; изоляция тенантов не затрагивается.


v0.1.34 — кэш валидации JWT (14.08.2026)

Проблема: jwtMiddleware валидировал токен через Nubes-шлюз на КАЖДЫЙ запрос консоли; шлюз нестабильно отвечает с IP платформы (то 200, то 401) → консоль периодически падала.

Реализовано (по плану Sonnet):

  1. app/auth/cache.go: кэш успешной валидации в памяти: map[hex(sha256(token))]{validUntil} + sync.RWMutex, TTL 5 мин, grace +2 мин при сетевой ошибке для ранее валидного токена, фоновая очистка (ticker 5 мин, sync.Once).
  2. app/auth/jwt.go: sentinel ErrTokenRejected (%w) для 401/403 — отличие от сетевых ошибок.
  3. app/admin/admin.go: jwtAuth — всегда свежая проверка + WarmTokenCache после успеха; jwtMiddlewarePingNubesAPICached; NewHandler — старт очистки.

Проверено локально (мок-шлюз): логин с живым моком → 200; мок убит → логин 403 (свежо, правильно), CREDS → 200 по кэшу (шлюз не нужен), неизвестный токен → 403. Vet/тесты OK.

Smoke на проде: 11 PASS, 1 FAIL — только логин (шлюз режет IP платформы; по дизайну логин всегда свежо). SQS-цикл create→send→receive→delete→delete-queue — PASS.

Особенность платформы: GET /ui через ingress не закрывает соединение (~150с, curl exit 28), внутри пода мгновенно. В smoke /ui проверяется range-запросом (206, 0.2с). Браузеру не мешает (HTML приходит сразу).

Деплой: v0.1.34 digest sha256:17f9bbdb…, под Ready, /health → v0.1.34.


Программная проверка SQS API — CLI и SDK, локально и с ВМ (14.08.2026)

Добавлены тесты:

  • tests/api_test.sh — aws cli, 22 проверки (полный цикл + FIFO + теги + purge).
  • tests/sdk_test.py — boto3 (как реальное приложение), 15 проверок, таймауты на уровне SDK.

Результаты:

  • Локально, CLI: 22/22 PASS; SDK (boto3 1.43.71): 15/15 PASS.
  • С ВМ, CLI: 22/22 PASS; SDK (boto3 1.34.46): 15/15 PASS.

Единственный разовый FAIL (DeleteMessage после ChangeMessageVisibility) при первом прогоне не воспроизвёлся — повторный прогон и ручная проверка: exit=0. Случайный сбой сети.