29 KiB
Журнал сессии — 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
Что сделано сегодня
-
Добавлен инфо-блок «Информация о сервисе» на страницу входа
/ui(app/ui/index.html). Коммит032ca66. Образ пересобран, запушен (digest 4bb4f20d). -
Проблема: вход в UI с реальным JWT →
Ошибка: token rejected by cloud API (HTTP 403). Причина: контейнер валидировал JWT наdeck-api-test.ngcloud.ru/api/v1(default вapp/admin/admin.go), а production JWT туда не подходит. -
Поиск правильного endpoint (в
~/tf_provider, по указанию пользователя):deck-api*.ngcloud.ru— ЛЕГАСИ (пользователь явно запретил этот формат)- Правильный: API Gateway
lk-api-gateway.ngcloud.ru
-
Найдено в
~/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") -
Тест токенов против 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
Выводы (факты)
- Правильный
NUBES_ENDPOINT=https://lk-api-gateway.ngcloud.ru/api/v1/svc - КРИТИЧНО: наш код
PingNubesAPI(app/auth/jwt.go) НЕ шлёт User-Agent → gateway возвращает 403 → токен всегда отклоняется. Нужна правка кода: добавитьUser-Agent: Mozilla/5.0.
Предстоящие правки (СОГЛАСОВАНЫ? — нет, ждут «делай»)
Dockerfile:ENV NUBES_ENDPOINT=https://lk-api-gateway.ngcloud.ru/api/v1/svcapp/auth/jwt.go: вPingNubesAPIдобавитьUser-Agent: Mozilla/5.0app/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 подтягивал старый образ. Плюс запрос: выводить версию на экран.
Что сделано:
app/models/constants.go:var Version = "dev"(переопределяется через ldflags).app/router/router.go: публичный/healthтеперь отдаёт JSON{"status":"ok","version":"v0.1.26"}.app/admin/admin.go:adminHealthDetailдополнен полемversion(в /admin/health и /ui/api/health).app/ui/index.html:- блок «Информация о сервисе» на странице входа: строка «Версия» (fetch публичного /health);
- образ в инфо-блоке теперь
naeel/shared-sqs:v0.1.26; - дашборд: карточка «Версия» (health.version).
Makefile:VERSION=v0.1.26,LDFLAGS=-X shared-sqs/app/models.Version=$(VERSION),docker-buildтегает$(VERSION)+latest; исправлена цельbuild(./app/cmd/вместо одного файла goaws.go).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в namespacef1ffb134-7d16-45bd-8bef-69f6ec8ab33c(ns = UUID инстанса), образnaeel/shared-sqs:latest, pullPolicy=Always. - Старый апрельский Helm-релиз
shared-sqs(nsshared-sqs, v0.1.24, 122 дня) — ЛЕГАСИ, не имел отношения к живому сервису. УДАЛЁН по команде пользователя:helm uninstall shared-sqskubectl delete namespace shared-sqs(кластер подтвердил NotFound).
- Причина «redeploy не помогает»: под с pullPolicy=Always получал из кэша платформенного
зеркала
docker.io(в kube-system всё на mirror.k8s.ngcloud.ru) старый digestbe2108fd…, хотя в Docker Hublatest=b4d1dccd…(v0.1.26). Кэш зеркала по тегуlatestне протухал из-за частых пушей.
Что сделано:
kubectl -n f1ffb134-… set image deployment/containerk8s app=naeel/shared-sqs:v0.1.26(внимание: контейнер в поде называетсяapp, а неcontainerk8s). Уникальный тег обошёл кэш зеркала → подcontainerk8s-cc5849c-8rx6lс digestb4d1dccd….- Проверка:
GET /health→{"status":"ok","version":"v0.1.26"}. - Вход в 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).
Что сделано:
app/ui/info.html— публичная страница: топбар с лого, hero с бейджем «тестирование», карточка «Подключение» (endpoint, SigV4, регион us-east-1, /ui, /health, /metrics, Redis), таблица всех 17 Actions, примеры AWS CLI. Версия подставляется через{{VERSION}}.app/ui/static/{logo, favicon}.svg— SVG-файлы из design-системы (лого ТОЛЬКО файлом, не inline).app/ui/embed.go:InfoHandler(version),StaticHandler(); embed расширен (info.html static).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 (выводы):
- Сломается:
TenantIDFromSub()в jwt.go; вызов вjwtAuth(admin.go ~236);access_key/secret_keyв ответе jwtAuth;GetBySubвjwtMiddleware(~288);bySub→byEmailв TenantStore (NewTenantStore, CreateFromJWT, Delete, LoadTenant, GetBySub). - Пересчёт легаси при 0 очередях безопасен, но ОБЯЗАТЕЛЬНО: удалить старый тенант из Redis
(
ssq:tenantsпо старому ID) и сохранить новый — иначе после рестарта старые рандомные ключи оживут. Весь пересчёт — подs.mu.Lock(). - Детерминированные функции — в jwt.go; byEmail-индекс — в tenant_store.go; LoadTenant индексирует
по Email только при
Email != ""(защита от коллизииbyEmail[""]с демо-тенантом CreateFixed). - Дыра — orphan AccessKey в byAccessKey: при пересчёте удалять из byAccessKey ДО добавления нового.
- Критичное неучтённое: (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)
Что сделано:
app/tenant/tenant_store.go: индексbySub→byEmail;GetByEmailвместоGetBySub;CreateFromJWT(sub, email, maxQueues)— ID/AccessKey/SecretKey детерминированы из email (SHA256, сольshared-sqs:secret-key:v1для секрета). Рандом остался только для manual-тенантов. Легаси-тенант (старые ключи/ID) при входе полностью пересчитывается: старые индексы и Redis-запись удаляются под мьютексом.app/auth/jwt.go:TenantIDFromSub(sless) УДАЛЁН — связей с внешними сервисами больше нет.app/admin/admin.go:jwtAuthтребует email (400 без него), НЕ возвращает access/secret key; новыйGET /ui/api/credentialsотдаёт ключи под JWT-сессией; jwtMiddleware по email.app/ui/index.html: Access Key убран из таблицы и карточки тенанта; кнопки «Credentials» (дашборд + тенант) открывают модалку с ключами и подсказкой (зачем ключи, export-команды, пример aws cli). Логин-страница: образ v0.1.28.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):
- Палитра → светлая design-system Nubes: bg #fafafa, поверхности #ffffff, border #d1d5db, primary #2563eb, muted #6b7280, карточки белые с radius 12 + тень.
- Логотип без invert-фильтра (тёмный лого на белом), шрифт system-ui 14px, заголовки 20-22px, бейджи/кнопки в светлых тонах, textarea/input белые.
- Модалка 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)
Сделано:
- Новый
app/ui/admin.html— админская консоль: вход по admin-токену, таблица тенантов (имя, ID, Access Key, лимит, статус, создан), создание тенанта с показом ключей в модалке, удаление с подтверждением. Светлый Nubes-дизайн, /static/logo.svg + favicon, версия через {{VERSION}}. app/ui/embed.go: embed admin.html +AdminHandler(version).app/router/router.go: GET /admin и /admin/ → админская страница (зарегистрированы ДО admin subrouter, чтобы PathPrefix("/admin") с bearer-auth их не перехватил).- Юзерский 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-stcgvReady.
v0.1.32 — главная с пошаговым стартом, примеры на /examples (14.08.2026)
Жалобы юзера: главная не объясняет с первого раза, как войти/где креды/что делать; примеры засоряли главную мелким шрифтом; в консоли лишняя хлебная крошка и таблица из одной строки.
Сделано:
app/ui/info.html: блок «Как начать — 3 шага» (1. Открыть консоль → кнопка, 2. Получить ключи через Credentials, 3. Подключить клиент с быстрым примером + ссылка на /examples). Таблица команд и примеры убраны с главной, вместо них — карточка-ссылка на /examples. Слово «тенант» убрано из текста главной.- Новый
app/ui/examples.html(GET /examples): крупный шрифт 15px, команды 14px, пошагово: ключи (env И файл ~/.aws/credentials) → создать очередь → отправить → получить/удалить, плюс таблица всех 17 Actions. app/ui/embed.go: ExamplesHandler;app/router/router.go: маршрут /examples.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:
- Кэш — в auth (новый
app/auth/cache.go):PingNubesAPICached,WarmTokenCache,StartTokenCacheCleanup. В admin.go: jwtMiddleware → PingNubesAPICached; jwtAuth → свежая проверка + WarmTokenCache; NewHandler → cleanup. - Ключ
hex(sha256(token));map[string]tokenCacheEntry{validUntil}+sync.RWMutex(не sync.Map — нужна итерация при очистке); уборщик — ticker 5 мин. ErrTokenRejected(sentinel,%w) — отличать 401/403 от сетевых ошибок; сигнатура PingNubesAPI не меняется.- Таблица: HIT → nil; MISS+2xx → записать; MISS+401/403 → удалить, вернуть ошибку; MISS+сетевая → ошибка; протухший+сетевая → опция grace +2 мин (принято).
- Гонки: параллельные MISS безвредны (singleflight избыточен); запись в кэш до проверки ctx; рестарт пода сбрасывает кэш (норма); exp проверяется локально ДО кэша/шлюза.
Решение (принято): всё выше + grace-продление 2 мин при сетевой ошибке для ранее валидного токена. TTL 5 мин; отозванный токен умирает максимум через 5 мин; изоляция тенантов не затрагивается.
v0.1.34 — кэш валидации JWT (14.08.2026)
Проблема: jwtMiddleware валидировал токен через Nubes-шлюз на КАЖДЫЙ запрос консоли; шлюз нестабильно отвечает с IP платформы (то 200, то 401) → консоль периодически падала.
Реализовано (по плану Sonnet):
app/auth/cache.go: кэш успешной валидации в памяти:map[hex(sha256(token))]{validUntil}+sync.RWMutex, TTL 5 мин, grace +2 мин при сетевой ошибке для ранее валидного токена, фоновая очистка (ticker 5 мин, sync.Once).app/auth/jwt.go: sentinelErrTokenRejected(%w) для 401/403 — отличие от сетевых ошибок.app/admin/admin.go:jwtAuth— всегда свежая проверка +WarmTokenCacheпосле успеха;jwtMiddleware—PingNubesAPICached;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. Случайный сбой сети.