Files
IoT/doc/thinking/2026-04-12.md
T

14 KiB
Raw Blame History

ЛЕГАСИ (2026-08-16) — СТАРЫЙ IoT (k8s-деплой). НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ. Актуальное: HISTORY/2026-08-16-session-log.md

2026-04-12 — GitHub Copilot (Claude Opus 4.6)

Задача

Пользователь просит:

  1. Просмотреть всё в репе IoT
  2. Оценить замену Kafka и RabbitMQ на собственный SQS-service (из ~/terra/SQS-service)
  3. Контекст: проект переехал из ~/terra/sless в отдельную репу ~/remote_dev/IoT

План

  1. Изучить текущую структуру IoT репы — что есть, как используется Kafka
  2. Посмотреть SQS-service — что он из себя представляет
  3. Посмотреть старую репу sless — что оттуда переехало, что осталось
  4. Дать краткую оценку замены Kafka/RabbitMQ → SQS

Ход работы

Анализ: что нашёл

IoT — текущее использование Kafka:

  • cmd/mqtt-bridge/main.go — MQTT → Kafka writer (топик iot.telemetry, библиотека segmentio/kafka-go)
  • cmd/kafka-consumer/main.go — Kafka reader → Postgres (consumer group iot-pg-consumer)
  • internal/api/handler/iot_admin_stats_handler.go — admin UI: показывает Kafka consumer lag
  • cmd/iot-operator/main.go — передаёт KAFKA_BROKERS в handler
  • deployments/k8s/kafka.yaml — деплоймент Kafka в кластере
  • В документации (deployment.md, iot-mvp-plan.md) упоминается RabbitMQ как изначальный план, но в коде RabbitMQ нет — в реальности уже Kafka

SQS-service (shared-sqs):

  • AWS SQS-совместимый сервис на Go (модуль shared-sqs)
  • HTTP API с SQS-протоколом: CreateQueue, SendMessage, ReceiveMessage, DeleteMessage и т.д.
  • Multi-tenant: TenantStore с auth
  • Persistence: Redis (write-through) + in-memory
  • Billing через PostgreSQL
  • Порт 4100, gorilla/mux
  • Не Kafka-совместимый — это pull-based очередь (SQS-like), не streaming platform

RabbitMQ в IoT: только в документах (plan). В коде не используется.

shared-SQS (из Gitea README):

  • AWS SQS-совместимый managed сервис — 17 операций SQS
  • Уже задеплоен: endpoint https://qu.kube5s.ru, Web UI https://qu.kube5s.ru/ui/
  • Multi-tenant с auth (JWT nubes → auto-create tenant → Access Key / Secret Key)
  • Можно использовать через стандартные AWS SDK (Go, Python boto3, Node.js @aws-sdk/client-sqs)
  • Persistence: Redis + in-memory
  • Billing: PostgreSQL
  • Benchmark: конкурентоспособен с Yandex MQ

Выводы по замене Kafka → shared-SQS

Что реально нужно сделать в IoT:

  1. mqtt-bridge: заменить kafka.Writer → AWS SDK SQS SendMessage (endpoint = qu.kube5s.ru или внутрикластерный)
  2. kafka-consumer → переименовать в sqs-consumer: заменить kafka.Reader → SQS ReceiveMessage + DeleteMessage polling loop
  3. admin stats handler: вместо Kafka lag → SQS GetQueueAttributes (ApproximateNumberOfMessages)
  4. Удалить deployments/k8s/kafka.yaml
  5. Убрать segmentio/kafka-go из go.mod, добавить aws-sdk-go-v2
  6. Обновить env vars: KAFKA_BROKERS → SQS_ENDPOINT + SQS credentials
  7. Обновить документацию

Плюс: AWS SDK for Go — стандартная библиотека, код станет проще. shared-SQS уже живой. Минус: polling latency (ReceiveMessage WaitTimeSeconds до 20s) vs Kafka push. Для IoT телеметрии — приемлемо.


Продолжение — GitHub Copilot (Claude Opus 4.6)

Реализация замены Kafka → SQS

Что сделано:

  1. mqtt-bridge — полностью переписан:

    • Убран segmentio/kafka-go, добавлен aws-sdk-go-v2 (sqs, config, credentials)
    • kafka.Writersqs.Client.SendMessage
    • При старте: GetQueueUrl для резолва URL очереди "iot-telemetry"
    • Env vars: SQS_ENDPOINT, SQS_ACCESS_KEY, SQS_SECRET_KEY, SQS_QUEUE_NAME, SQS_REGION
    • Формат сообщения (MessageBody JSON) не изменился: {namespace, device_id, topic, payload, received_at}
  2. sqs-consumer — создан с нуля (заменяет kafka-consumer):

    • Long polling: ReceiveMessage(WaitTimeSeconds=20) — минимизирует запросы при пустой очереди
    • At-least-once: DeleteMessage только после успешной записи в Postgres
    • Использует iotpg.IoTPostgresStore — тот же механизм per-tenant DB что и kafka-consumer
  3. admin stats handler — переписан:

    • Вместо Kafka consumer lag → GetQueueAttributes(ApproximateNumberOfMessages, ApproximateNumberOfMessagesNotVisible)
    • Pod labels для consumer: iot-kafka-consumeriot-sqs-consumer
  4. Баг .gitignore: паттерны mqtt-bridge и kafka-consumer без / игнорировали cmd/mqtt-bridge/ и cmd/kafka-consumer/. Исправлено добавлением / префикса.

  5. Баг router: при удалении KafkaBrokers из handler init случайно удалилась строка router := iotapi.NewRouter(h, log). Восстановлена.

Деплой в новый кластер iot-naeel

Обнаружения при деплое:

  1. Кластер полностью новый — namespace sless не существовал, ничего не задеплоено.

  2. Postgres — пользователь указал использовать managed PG17, тот же инстанс что SQS billing. Credentials в /home/naeel/terra/SQS-service/secrets/iot_pg.md. Самодеплоенный postgres:16-alpine из YAML заменён на DSN к managed PG.

  3. EMQX — пришлось создать deployment для IoT-репы заново, адаптировав из sless. Ключевое изменение: auth URL sless-operator.sless.svc:9090iot-operator.sless.svc:9090.

  4. iot-operator deployment — его не было в IoT-репе! Создан новый:

    • ServiceAccount + ClusterRole (iotdevices CRD, secrets, events, namespaces, leases)
    • ClusterRoleBinding
    • Deployment + Service :9090
  5. kubectl токен истекал за 24 часа — пользователь обновлял вручную.

  6. Docker Hub вместо pearlharbor (Harbor) — убраны imagePullSecrets, image naeel/iot-operator:v0.2.0.

  7. shared-SQS tenant создан через API:

    • Tenant: iot-service, ID: t-96afe7e9f781f6ca
    • Queue: iot-telemetry
    • Admin token из Secret shared-sqs-admin в namespace shared-sqs

Результат

Все 4 пода Running 1/1:

  • iot-operator — controller работает, MQTT auth/acl обрабатывает запросы
  • emqx — MQTT брокер, подключает IoT устройства
  • iot-mqtt-bridge — подписан на EMQX, SQS queue resolved
  • iot-sqs-consumer — подключён к PG и SQS, polling loop активен

TLS сертификат для iot.kube5s.ru выпускается cert-manager.


Сессия 2 — Агент: GitHub Copilot (Claude Opus 4.6)

Контекст

Продолжение деплоя после замены Kafka→SQS. Новый кластер iot-naeel, namespace sless пустой.

Что обнаружил

  1. kubeconfig токен истёк (JWT TTL=24ч) — пользователь обновил вручную
  2. Kafka отсутствует в кластере — уже нет, удалять нечего
  3. Namespace sless создан в предыдущей сессии, но пуст (только Secret iot-sqs-credentials)
  4. IoT Postgres: пользователь указал использовать managed PG из SQS-service (не self-hosted)
    • Креды в /home/naeel/terra/SQS-service/secrets/iot_pg.md
    • Host: postgresqlk8s-master.dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5.svc.cluster.local
    • PG17, managed через оператор
  5. EMQX: deployment-файла не было в IoT-репе, адаптировал из sless
    • Ключевое: auth URL sless-operatoriot-operator
  6. iot-operator deployment: не было, создал с нуля (RBAC, ServiceAccount, ClusterRole)

Что сделал

  1. Обновил image во всех deployments: pearlharbor → Docker Hub naeel/iot-operator:v0.2.0
  2. Убрал imagePullSecrets (Docker Hub публичный)
  3. Собрал Docker образ на ВМ, запушил в Docker Hub (v0.2.0 + latest)
  4. Обновил iot-postgres.yaml: убрал self-hosted Deployment/Service, оставил только Secret с managed PG DSN
  5. Создал emqx.yaml (EMQX 5.5.1 + ConfigMap + Service, auth→iot-operator)
  6. Создал emqx-ws-ingress.yaml (ws Ingress + TLS cert-manager)
  7. Создал iot-operator.yaml (ServiceAccount + ClusterRole + ClusterRoleBinding + Deployment + Service)
  8. Установил CRD iotdevices.iot.kube5s.ru
  9. Создал секреты: iot-postgres-secret, iot-bridge-credentials
  10. Задеплоил всё: EMQX → operator → mqtt-bridge → sqs-consumer
  11. Все 4 пода Running 1/1, логи чистые

Ошибки по ходу

  • Пытался редактировать файлы локально через VS Code tools вместо SSH на ВМ — нарушение правил
  • Пытался копировать файлы через scp — не нужно, ~/remote_dev/ = ~/terra/ (одна ФС)
  • Правило записано в copilot-instructions.md и в memory

Результат

Полный IoT стек развёрнут в кластере iot-naeel:

  • EMQX (MQTT :1883, WS :8083) → iot-operator (auth/acl, API :9090)
  • mqtt-bridge (MQTT→SQS) → sqs-consumer (SQS→Postgres)
  • Managed PG17, shared-SQS, cert-manager TLS
  • Ingress: wss://iot.kube5s.ru/mqtt, https://iot.kube5s.ru/console

Следующий шаг

E2E тест: создать устройство → MQTT publish → проверить SQS → Postgres → API.


Сессия: Документация v0.2.3 (продолжение)

Агент: GitHub Copilot (Claude Opus 4.6)

Что сделано

  1. architecture/current-v0.2.3.md — полная архитектура:

    • Data flow diagram (Device → EMQX → Bridge → SQS → Consumer → PG → API)
    • Все 4 компонента с параметрами
    • CRD spec/status/reconcile logic
    • Auth: REST JWT, MQTT auth/acl, bridge auth
    • Storage: PG per-tenant, SQS
    • Docker image, сетевая схема, секреты, эволюция архитектуры
  2. api/endpoints-v0.2.3.md — документация API:

    • Изучил router.go, handler.go, iot_device_handler.go, iot_telemetry_handler.go, iot_admin_stats_handler.go
    • Задокументировал ВСЕ endpoints: CRUD, telemetry, MQTT auth/acl, admin stats, UI
    • Request/response примеры с точными JSON форматами
    • Curl примеры для всех операций
  3. run-and-test.md — руководство по тестированию:

    • Сборка, деплой, E2E тест по шагам
    • Проверка компонентов, отладка
    • WebSocket и port-forward варианты MQTT
  4. progress.md — дополнена секция документации

Проблемы

  • Скрипт /tmp/write_arch.py был хардкодом на один файл — случайно перезаписал architecture doc при тесте
  • Создал универсальный /tmp/write_file.py с аргументом пути — больше проблем нет

Осталось

  • git commit + push

Агент: GitHub Copilot (Claude Opus 4.6) — E2E тестирование и баг-фиксы

Баг 1: MQTTAuth ищет IoTDevice по Name=deviceID

Проблема: MQTTAuth хэндлер вызывал h.K8s.Get(client.ObjectKey{Name: deviceID}), но имя K8s объекта IoTDevice (e2e-test-device) не равно Spec.DeviceID (e2e-test-01). Результат — deny.

Диагностика:

  1. Auth endpoint вернул {"result":"deny"} при прямом вызове curl
  2. Secret iot-e2e-test-01 существует и пароль совпадает → проблема НЕ в пароле
  3. IoTDevice объект называется e2e-test-device, а MQTTAuth ищет по Name: "e2e-test-01" → NotFound → deny

Фикс: Заменил Get на List + фильтр по Spec.DeviceID == deviceID. Это O(n) по количеству устройств в namespace, но для MVP приемлемо. При необходимости можно добавить label-index.

Баг 2: PG15+ требует GRANT перед CREATE DATABASE ... OWNER

Проблема: EnsureTenantDB делал CREATE DATABASE tenant_sless OWNER tenant_sless, но PG17 (PG15+) требует SET ROLE privileges для target owner. Ошибка: pq: must be able to SET ROLE "tenant_sless" (42501).

Фикс: Добавил GRANT {userName} TO CURRENT_USER перед CREATE DATABASE ... OWNER.

E2E тест v0.2.5 — полный пайплайн

  1. POST /v1/namespaces/sless/iot/devices → 201, device created
  2. GET /devices/e2e-test-device → phase=Active, credentials получены
  3. mosquitto_pub → CONNACK(0), PUBLISH OK
  4. mqtt-bridge → forwarded IoT telemetry to SQS
  5. sqs-consumer → created tenant DB + telemetry saved to Postgres
  6. GET /v1/namespaces/sless/iot/telemetry?device_id=e2e-test-01 → 2 записи с temperature/humidity

Все компоненты работают end-to-end.