# Архитектура и план миграции shared-sqs → Nubes Managed **Дата:** 2026-08-13 **Источник:** ответ Claude Sonnet на промпт по миграции --- ## 1. Код-ревью (блокеры деплоя) | Файл | Находка | Блокер? | Что делать | |---|---|---|---| | `goaws.yaml` + `tenant_helpers.go` | `Host: goaws.com`, `Port: 4100` bake-in в образ. QueueUrl клиентам возвращается как `http://us-east-1.goaws.com:4100/{tenantID}/{queueName}`. Поскольку текущий k8s работает с тем же расхождением (ingress `qu.kube5s.ru` ≠ `goaws.com`), клиенты используют endpoint_url + только path из QueueUrl — **не подключаются к goaws.com напрямую**. | НЕТ (условно) | Обновить `Host` в `goaws.yaml` после выяснения нового домена, пересобрать образ. Это смена конфига, не Go-кода. | | `redis.go` | Подключение без TLS: `redis.Options{Addr, Username, Password, DB:0}` — нет `TLSConfig`. Если Managed Redis требует TLS на internal DNS — блокер. | **ОТКРЫТЫЙ ВОПРОС** | Уточнить у Nubes: требует ли managed Redis TLS для `*.svc.cluster.local`. Если нет — не блокер. | | `goaws.go` | Порт задаётся флагом `--port` (default 4100), не env `PORT`. ENTRYPOINT в Dockerfile не передаёт `--port` — слушает 4100. | НЕТ | nubes_http должен маршрутизировать на порт 4100 (или платформа сама определяет порт — уточнить). | | `goaws.go` | `ReceiveMessage` с long-polling держит HTTP-соединение до 20 секунд (`WaitTimeSeconds`). В Ingress был `proxy-read-timeout: 30`. | НЕТ (в коде) | nubes_http должен иметь read-timeout ≥ 30 секунд — уточнить у платформы. | | `admin.go` | `NUBES_ENDPOINT` default = `https://deck-api-test.ngcloud.ru/api/v1` (тестовый). Для production нужен production URL. | НЕТ (env-override) | Задать `NUBES_ENDPOINT` production URL в env vars контейнера. | | `deployment.yaml` | `SHARED_SQS_SEED_DEMO=true` — сидирует демо-данные при каждом старте. | НЕТ | Не задавать или `=false` в production деплое. | | `Dockerfile` | `imagePullSecrets: sless-registry-auth` (k8s). Образ `naeel/shared-sqs` на Docker Hub — нужно проверить публичность. | НЕТ (вероятно) | Если образ публичный — секрет не нужен. Уточнить, как nubes_http указывает registry. | **Вывод: жёстких code-level блокеров нет. Код менять не нужно.** Единственное изменение при известном домене — пересборка образа с обновлённым `goaws.yaml` (поле `Host`). --- ## 2. Целевая архитектура ### Схема ```mermaid graph TD Client["AWS SDK/CLI
boto3 / aws-sdk-go
(endpoint_url = новый домен)"] LB["Nubes HTTP Gateway
TLS termination
timeout ≥ 30s"] App["nubes_http
naeel/shared-sqs:v0.1.x
port 4100
RAM 64–256Mi / CPU 50–500m"] Redis["Managed Redis
redisk8s.4ff5678b-b683-4aef-91da-22d89dec8a25
.svc.cluster.local:6379
512MB / 10GB"] PG["Managed PostgreSQL
(опционально, billing)"] NubesAPI["Nubes API
deck-api-*.ngcloud.ru
JWT-валидация UI-сессий"] VM["VictoriaMetrics
scrape /metrics"] Client -->|"HTTPS :443"| LB LB -->|"HTTP :4100
internal"| App App -->|"TCP :6379
internal DNS
без TLS"| Redis App -.->|"TCP :5432
BILLING_PG_HOST"| PG App -->|"HTTPS
JWT validate"| NubesAPI VM -->|"GET /metrics"| App ``` ### Компоненты | Компонент | Ресурс Nubes | Описание | |---|---|---| | SQS-сервис | `nubes_http` | Образ `naeel/shared-sqs:v0.1.x`, порт 4100, stateless | | Persistence | Managed Redis `4ff5678b-...` | Ключи `ssq:tenants`, `ssq:queues`, `ssq:msg:{key}` | | Billing (opt.) | Managed PostgreSQL | Таблица `sqs_usage_records`, auto-migrate при старте | | JWT auth | Nubes API (внешний) | Только для UI-консоли, не для SQS API | ### Переменные окружения для nubes_http ``` SHARED_SQS_ADMIN_TOKEN=<секрет> REDIS_ADDR=redisk8s.4ff5678b-b683-4aef-91da-22d89dec8a25.svc.cluster.local:6379 REDIS_USER=default REDIS_PASSWORD=ZJOke5b2bIr6YPOKrnJG NUBES_ENDPOINT=https://deck-api.ngcloud.ru/api/v1 ← production URL (уточнить) SHARED_SQS_SEED_DEMO=false # Опционально — billing: BILLING_PG_HOST=... BILLING_PG_PORT=5432 BILLING_PG_DATABASE=... BILLING_PG_USER=... BILLING_PG_PASSWORD=... BILLING_PG_SSLMODE=require ``` ### Ключевые инварианты архитектуры - **Один процесс, stateless:** всё состояние в Redis. Рестарт контейнера восстанавливает тенантов и очереди через `LoadAllTenantsRaw` + `LoadAllQueues`. - **Фоновые горутины:** `PeriodicTasks` (visibility timeout, DLQ, dedup) и `StartGaugeUpdater` запускаются в рамках процесса — не требуют отдельных воркеров. - **Long-polling:** до 20 секунд на соединение — платформа должна держать коннект открытым. - **Multi-tenancy:** изоляция по ключу `{accessKey}:{queueName}` в Redis и in-memory; тенанты хранятся в `ssq:tenants` HASH. --- ## 3. План миграции ### Фаза 0 — Ответы на открытые вопросы (блокирует всё остальное) **0.1.** Уточнить у команды Nubes: порт `nubes_http` (видит ли платформа `EXPOSE 4100` или нужно явно указывать), DNS-видимость `*.svc.cluster.local`, TLS на Redis. *Критерий приёмки: письменный ответ по всем 4 пунктам открытых вопросов.* **0.2.** Проверить доступность Managed Redis из realm iot-naeel. Команда (запустить временный pod или контейнер в том же realm): ```bash redis-cli -h redisk8s.4ff5678b-b683-4aef-91da-22d89dec8a25.svc.cluster.local \ -p 6379 -u default -a ZJOke5b2bIr6YPOKrnJG ping ``` *Критерий: ответ `PONG`.* --- ### Фаза 1 — Подготовка (без downtime, k8s работает) **1.1.** Решить судьбу данных: мигрировать из k8s Redis или начать с чистого листа. - **Вариант А (clean start):** нулевое состояние. Требует пересоздания тенантов через Admin API и уведомления клиентов о новых ключах. Риск: потеря очередей с сообщениями. - **Вариант Б (миграция):** экспорт `ssq:tenants`, `ssq:queues`, `ssq:msg:*` из k8s Redis → импорт в Managed Redis. Сохраняет всё состояние, но требует краткого maintenance window. *Критерий: явное решение согласовано.* **1.2 (если Вариант Б).** Экспортировать ключи из k8s Redis: ```bash # Получить все ключи ssq:* kubectl exec -n shared-sqs deploy/shared-sqs-redis -- \ redis-cli -a <пароль> --scan --pattern "ssq:*" | \ xargs -I{} kubectl exec -n shared-sqs deploy/shared-sqs-redis -- \ redis-cli -a <пароль> DUMP {} ``` Либо через `redis-cli --rdb /tmp/dump.rdb`, затем передать в Managed Redis через `RESTORE`. *Критерий: все ключи `ssq:tenants`, `ssq:queues`, `ssq:msg:*` присутствуют в Managed Redis (проверить `HLEN ssq:tenants`, `HLEN ssq:queues`).* **1.3.** Обновить `goaws.yaml` с новым `Host` (финальный домен из ответа на вопрос 0.1.4), пересобрать и запушить образ: ```bash docker build -t naeel/shared-sqs:v0.1.23 . docker push naeel/shared-sqs:v0.1.23 ``` *Критерий: образ доступен в registry.* *Откат: не нужен — k8s ещё работает на v0.1.22.* --- ### Фаза 2 — Деплой nubes_http (параллельно с k8s) **2.1.** Создать ресурс `nubes_http` в realm iot-naeel: - Образ: `naeel/shared-sqs:v0.1.23` - Порт: 4100 (или как определит платформа) - Ресурсы: min 64Mi/50m, max 256Mi/500m - Env vars: полный список из раздела архитектуры выше - Health check: `GET /health` (уже в Dockerfile) *Критерий: контейнер запустился, `/health` отвечает `200 OK`.* *Откат: удалить ресурс nubes_http, трафик остаётся в k8s.* **2.2.** Smoke test нового инстанса (напрямую по URL nubes_http, до переключения трафика): ```bash # Health curl -m 5 https://new-domain.nubes.example/health # Создать тенанта через Admin API curl -m 5 -X POST https://new-domain.nubes.example/admin/tenants \ -H "Authorization: Bearer " \ -d '{"name":"smoke-test","maxQueues":5}' # Получить AccessKey/SecretKey из ответа, создать очередь aws sqs create-queue --queue-name test-q \ --endpoint-url https://new-domain.nubes.example \ --region us-east-1 # Отправить и получить сообщение aws sqs send-message --queue-url ... --message-body "hello" aws sqs receive-message --queue-url ... ``` *Критерий: все 3 операции успешны. Prometheus /metrics содержит метрики sqs_*.* *Откат: исправить env vars, пересобрать образ.* **2.3 (если Вариант Б — миграция данных).** Проверить восстановление: ```bash # Запустить контейнер → смотреть логи старта # Должны увидеть: "persistence: подключились к Redis", "Ошибка загрузки тенантов" НЕ должна быть # Проверить тенантов через Admin API curl https://new-domain.nubes.example/admin/tenants -H "Authorization: Bearer ..." ``` *Критерий: в ответе Admin API видны все тенанты, перенесённые из k8s Redis.* --- ### Фаза 3 — Переключение трафика **3.1.** Договориться с клиентами о maintenance window (только для Варианта Б, чтобы не было записей во время переноса данных). Для Варианта А — без окна. **3.2.** Обновить конфигурацию клиентов: заменить `endpoint_url` c `qu.kube5s.ru` (или текущего) на новый домен Nubes. Домен `qu.kube5s.ru` — не использовать. **3.3.** Мониторить в течение N минут/часов: - `/metrics` → Prometheus: `sqs_messages_sent_total`, `sqs_messages_received_total` - Ошибки в логах контейнера (`log.Error`) - Latency Redis (таймауты в логах `persistence: HSet`) *Критерий: ошибок в логах нет, метрики растут, клиенты работают.* *Откат: перевести клиентов обратно на k8s endpoint (он ещё жив на этом этапе).* --- ### Фаза 4 — Вывод k8s (только после подтверждённой стабильности) **4.1.** После N дней стабильной работы: удалить k8s ресурсы shared-sqs (Deployment, Service, Ingress, HPA если есть). **4.2.** Удалить k8s Redis (Deployment shared-sqs-redis, PVC shared-sqs-redis-pvc). **Необратимо — делать только после полной проверки данных в Managed Redis.** **4.3.** Удалить k8s Secrets (shared-sqs-admin, shared-sqs-redis). Предварительно убедиться, что все значения есть в nubes_http env или Nubes Secrets. *Критерий: namespace shared-sqs в k8s пуст или удалён. Сервис работает только через nubes_http.* --- ## 4. Открытые вопросы и риски | # | Приоритет | Вопрос | Последствие | |---|---|---|---| | 1 | **КРИТИЧНО** | Какой порт принимает nubes_http? Читает ли платформа `EXPOSE 4100` или нужно явно задать в конфиге ресурса? | Если порт не 4100 — нужен флаг `--port N` в ENTRYPOINT или env `PORT` | | 2 | **КРИТИЧНО** | Видит ли контейнер nubes_http DNS `*.svc.cluster.local` в realm iot-naeel? | Если нет — `REDIS_ADDR` с internal hostname не сработает, нужен другой адрес Redis | | 3 | **КРИТИЧНО** | Требует ли Managed Redis TLS для internal соединений? | Если да — код `redis.Options` без TLSConfig — блокер, нужна правка (минимальная: добавить `TLSConfig: &tls.Config{}`) | | 4 | **ВЫСОКИЙ** | Какой внешний URL/домен получит nubes_http ресурс? | Без домена нельзя обновить `goaws.yaml` → QueueUrl клиентам будет содержать `goaws.com` (косметика, но может сломать специфичные клиенты) | | 5 | **ВЫСОКИЙ** | Docker Hub образ `naeel/shared-sqs` публичный или приватный? Как nubes_http указывает registry credentials? | Если приватный — нужна конфигурация pull secret на уровне платформы | | 6 | **ВЫСОКИЙ** | Поддерживает ли nubes_http long-lived HTTP соединения до 20 секунд? (long-polling SQS) | Если платформа имеет timeout < 20s — клиенты с `WaitTimeSeconds>0` будут получать ошибки | | 7 | **СРЕДНИЙ** | Мигрировать данные (тенанты, очереди, сообщения) из k8s Redis или начать чисто? | Clean start: нет риска, но потеря данных. Миграция: сложнее, требует maintenance window | | 8 | **НИЗКИЙ** | Какой production URL для `NUBES_ENDPOINT`? Текущий default `deck-api-test.ngcloud.ru` — тестовый | UI JWT-авторизация сломается на production если endpoint не обновить | | 9 | **НИЗКИЙ** | `SHARED_SQS_SEED_DEMO=true` в k8s — нужен ли демо-режим в production nubes_http? | Если нет — просто не задавать env. Демо-тенант создаётся при каждом старте |