From d4f7c5abc8b93ece28bc000022a591de3e5e893a Mon Sep 17 00:00:00 2001 From: Naeel Date: Sun, 12 Apr 2026 16:16:46 +0300 Subject: [PATCH] =?UTF-8?q?doc:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=E2=80=94=20=D0=B4=D0=BE=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=20=D0=B0?= =?UTF-8?q?=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B5,?= =?UTF-8?q?=20=D0=B4=D0=B5=D0=BF=D0=BB=D0=BE=D1=8E,=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B3=D1=80=D0=B5=D1=81=D1=81=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - architecture/overview.md: добавлена актуальная схема (SQS, managed PG) - deployment.md: добавлена процедура деплоя в iot-naeel - progress.md: добавлен блок 2026-04-12 (Kafka→SQS, деплой) - api/endpoints.md: добавлен актуальный формат /iot-admin/stats - thinking/2026-04-12.md: дописан ход реализации и деплоя - copilot-instructions.md: добавлено правило «документацию не стирать» Старая документация (Kafka, RabbitMQ) сохранена как база знаний. --- .github/copilot-instructions.md | 13 +++++ doc/api/endpoints.md | 22 ++++++++ doc/architecture/overview.md | 84 ++++++++++++++++++++++++++++ doc/deployment.md | 98 +++++++++++++++++++++++++++++++++ doc/progress.md | 50 +++++++++++++++++ doc/thinking/2026-04-12.md | 65 ++++++++++++++++++++++ 6 files changed, 332 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2381e45..ef68ac9 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -27,6 +27,19 @@ --- +## 🚫 ДОКУМЕНТАЦИЮ НЕ СТИРАТЬ — ТОЛЬКО ДОПОЛНЯТЬ + +**СТРОГОЕ ПРАВИЛО: любую документацию в `doc/` ЗАПРЕЩЕНО удалять или перезаписывать.** + +- Старый текст — это база знаний. Даже если он устарел (Kafka, RabbitMQ, и т.д.) — он остаётся. +- Новую информацию **дописывать** в конец файла или добавлять новые секции с датой. +- Если архитектура изменилась — добавить секцию `## Актуальная архитектура (ГГГГ-ММ-ДД)`, НЕ удаляя старую. +- Если решение отменено — не стирать, а добавить пометку `> ⚠️ Отменено ГГГГ-ММ-ДД: причина`. + +Это касается ВСЕХ файлов в `doc/`, включая `progress.md`, `decisions/`, `thinking/`, `api/`, `architecture/`. + +--- + ## ⚠️ ВЫПОЛНЕНИЕ КОМАНД — ТОЛЬКО НА УДАЛЁННОЙ МАШИНЕ - Все команды (git, go, docker, kubectl, make и т.д.) выполнять **ТОЛЬКО на удалённой машине** через SSH. diff --git a/doc/api/endpoints.md b/doc/api/endpoints.md index ca5c07a..b64d1c4 100644 --- a/doc/api/endpoints.md +++ b/doc/api/endpoints.md @@ -30,3 +30,25 @@ | Метод | Путь | Описание | |-------|------|----------| | GET | `/iot-admin/stats` | JSON статистика (PG, Kafka lag, pods) | + +--- + +## Обновление 2026-04-12: admin stats → SQS + +> Kafka lag заменён на SQS queue stats. Endpoint тот же, формат ответа изменён. + +### `/iot-admin/stats` — актуальный формат ответа + +```json +{ + "sqs": { + "queue_url": "http://us-east-1.goaws.com:4100/t-96afe7e9f781f6ca/iot-telemetry", + "approximate_messages": "0", + "approximate_messages_not_visible": "0" + }, + "sqs_consumer_pods": [...], + "pg_tenants": [...] +} +``` + +Вместо `kafka_lag` теперь `sqs.approximate_messages` — количество сообщений в очереди, ожидающих обработки. diff --git a/doc/architecture/overview.md b/doc/architecture/overview.md index 74bde7b..c9124cd 100644 --- a/doc/architecture/overview.md +++ b/doc/architecture/overview.md @@ -64,3 +64,87 @@ REST API (iot-operator:9090) - `naeel/iot-operator` (Docker Hub) - Все 3 бинарника в одном образе - `command: ["/iot-operator"]` или `["/mqtt-bridge"]` или `["/kafka-consumer"]` + +--- + +## Актуальная архитектура (2026-04-12) + +> Kafka заменён на shared-SQS. Postgres заменён на managed. Новый кластер iot-naeel. + +### Текущая схема + +``` +IoT Device (MQTT CONNECT) + │ username="{ns}_{deviceId}", password=hex + ▼ +EMQX 5.5.1 (sless/emqx) + │ POST /internal/mqtt/auth → iot-operator:9090 + │ POST /internal/mqtt/acl → iot-operator:9090 + ▼ + │ MQTT PUBLISH → topic: "{ns}/telemetry/{deviceId}" + ▼ +iot-mqtt-bridge (cmd/mqtt-bridge) + │ подписка на "+/telemetry/+" + │ → AWS SDK SQS SendMessage → shared-SQS (https://qu.kube5s.ru) + │ очередь: "iot-telemetry" (tenant: iot-service, id: t-96afe7e9f781f6ca) + ▼ +shared-SQS (namespace shared-sqs) + │ AWS SQS-совместимый, multi-tenant + ▼ +iot-sqs-consumer (cmd/sqs-consumer) + │ ReceiveMessage (WaitTimeSeconds=20, long polling) + │ DeleteMessage после успешной записи (at-least-once) + │ → per-tenant Postgres DB + ▼ +Managed PostgreSQL 17 (namespace dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5) + │ Host: postgresqlk8s-master.dc5db45d-....svc.cluster.local + │ User: super, DB: sqsdb + │ Per-tenant DB: tenant_{namespace}, Table: iot_telemetry + ▼ +REST API (iot-operator:9090) + │ GET /v1/namespaces/{ns}/iot/telemetry + ▼ +Пользователь (Terraform / UI Console) + │ wss://iot.kube5s.ru/mqtt (WebSocket через Ingress) + │ https://iot.kube5s.ru/console (UI) +``` + +### Компоненты (актуальные) + +| Компонент | Бинарник | Порт | Назначение | +|-----------|----------|------|------------| +| iot-operator | cmd/iot-operator | :9090 (API), :8080 (metrics), :8081 (health) | Controller-manager + REST API | +| mqtt-bridge | cmd/mqtt-bridge | — | MQTT → shared-SQS bridge | +| sqs-consumer | cmd/sqs-consumer | — | shared-SQS → Postgres pipeline | + +### Инфраструктура + +| Сервис | Тип | Namespace | Описание | +|--------|-----|-----------|----------| +| EMQX 5.5.1 | В кластере | sless | MQTT-брокер, HTTP auth/acl → iot-operator | +| shared-SQS | В кластере | shared-sqs | AWS SQS-совместимая очередь, endpoint https://qu.kube5s.ru | +| PostgreSQL 17 | Managed (оператор) | dc5db45d-... | per-tenant DB, тот же инстанс что и SQS billing | +| cert-manager | В кластере | cert-manager | TLS сертификаты для iot.kube5s.ru | +| nginx-ingress | В кластере | ingress | WSS/HTTPS проксирование | + +### Секреты в namespace sless + +| Secret | Ключи | Источник | +|--------|-------|----------| +| iot-sqs-credentials | SQS_ENDPOINT, SQS_ACCESS_KEY, SQS_SECRET_KEY | shared-SQS tenant iot-service | +| iot-postgres-secret | POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, IOT_PG_DSN | Managed PG | +| iot-bridge-credentials | MQTT_USERNAME, MQTT_PASSWORD | Для mqtt-bridge → EMQX | + +### Docker образ (актуальный) + +- `naeel/iot-operator:v0.2.0` (Docker Hub) +- Все 3 бинарника в одном образе +- `command: ["/iot-operator"]` или `["/mqtt-bridge"]` или `["/sqs-consumer"]` +- Базовый: `gcr.io/distroless/static:nonroot` + +### Кластер + +- Имя: `iot-naeel` +- API: `https://185.247.187.149:6443` +- Ingress IP: `185.247.187.151` +- DNS: `iot.kube5s.ru → 185.247.187.147` diff --git a/doc/deployment.md b/doc/deployment.md index daf482a..6b2dc5a 100644 --- a/doc/deployment.md +++ b/doc/deployment.md @@ -304,3 +304,101 @@ node { |--------|------|-----------| | v0.1.50 | 2026-04-04 | IoT controller + IoT API + iot-mqtt-bridge бинарь | | v0.1.49 | ранее | До IoT | + +--- + +## Актуальный деплой (2026-04-12) + +> Кластер: iot-naeel (новый). Kafka удалён. Postgres — managed. SQS вместо Kafka. + +### Кластер iot-naeel + +- API: `https://185.247.187.149:6443` +- Ingress IP: `185.247.187.151` +- DNS: `iot.kube5s.ru → 185.247.187.147` +- kubeconfig: `~/.kube/config` на ВМ (токен с TTL 24ч, обновляется через auth.k8s.ngcloud.ru) + +### Namespace sless — содержимое + +| Ресурс | Имя | Описание | +|--------|-----|----------| +| Deployment | emqx | MQTT-брокер EMQX 5.5.1 | +| Deployment | iot-operator | Controller-manager + REST API v0.2.0 | +| Deployment | iot-mqtt-bridge | MQTT → shared-SQS bridge v0.2.0 | +| Deployment | iot-sqs-consumer | shared-SQS → Postgres consumer v0.2.0 | +| Service | emqx | :1883 (MQTT), :8083 (WS), :18083 (dashboard) | +| Service | emqx-ws | :8083 (для Ingress) | +| Service | iot-operator | :9090 (REST API) | +| Ingress | emqx-mqtt-websocket | iot.kube5s.ru → /mqtt (WS), /console (UI) | +| Secret | iot-sqs-credentials | SQS_ENDPOINT, SQS_ACCESS_KEY, SQS_SECRET_KEY | +| Secret | iot-postgres-secret | POSTGRES_USER, POSTGRES_PASSWORD, IOT_PG_DSN | +| Secret | iot-bridge-credentials | MQTT_USERNAME, MQTT_PASSWORD | +| CRD | iotdevices.iot.kube5s.ru | IoTDevice v1alpha1 | +| ServiceAccount | iot-operator | + ClusterRole + ClusterRoleBinding | + +### Процедура деплоя (актуальная) + +```bash +# 0. SSH на ВМ (все команды оттуда) +ssh -i ~/.ssh/id_ed25519 naeel@5.172.178.213 + +# 1. Сборка и push образа +cd /home/naeel/terra/IoT +docker build -t naeel/iot-operator:v0.2.0 -t naeel/iot-operator:latest . +docker push naeel/iot-operator:v0.2.0 +docker push naeel/iot-operator:latest + +# 2. CRD (один раз) +kubectl apply -f config/crd/bases/iot.kube5s.ru_iotdevices.yaml + +# 3. Postgres Secret (содержит DSN managed PG) +kubectl apply -f deployments/k8s/iot-postgres.yaml + +# 4. EMQX +kubectl apply -f deployments/k8s/emqx.yaml +kubectl apply -f deployments/k8s/emqx-ws-ingress.yaml + +# 5. Operator +kubectl apply -f deployments/k8s/iot-operator.yaml + +# 6. SQS credentials (уже создан через kubectl create secret) +# kubectl get secret iot-sqs-credentials -n sless + +# 7. MQTT bridge credentials (уже создан) +# kubectl get secret iot-bridge-credentials -n sless + +# 8. Bridge + Consumer +kubectl apply -f deployments/k8s/iot-mqtt-bridge.yaml +kubectl apply -f deployments/k8s/iot-sqs-consumer.yaml + +# 9. Проверка +kubectl get pods -n sless +kubectl logs -n sless deployment/iot-operator --tail=10 +kubectl logs -n sless deployment/iot-mqtt-bridge --tail=10 +kubectl logs -n sless deployment/iot-sqs-consumer --tail=10 +``` + +### Managed PostgreSQL + +- Namespace: `dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5` +- Pod: `postgresqlk8s-0` +- Host: `postgresqlk8s-master.dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5.svc.cluster.local` +- User: `super`, DB: `sqsdb`, PG 17 +- Тот же инстанс что использует shared-SQS для billing +- Credentials: см. `/home/naeel/terra/SQS-service/secrets/iot_pg.md` + +### shared-SQS (очередь вместо Kafka) + +- Namespace: `shared-sqs` +- Endpoint: `https://qu.kube5s.ru` +- Tenant ID: `t-96afe7e9f781f6ca` (iot-service) +- Queue: `iot-telemetry` +- Access Key: хранится в Secret `iot-sqs-credentials` namespace `sless` +- Admin token: хранится в Secret `shared-sqs-admin` namespace `shared-sqs` + +### Версии образов (актуальные) + +| Версия | Дата | Registry | Изменения | +|--------|------|----------|-----------| +| v0.2.0 | 2026-04-12 | Docker Hub naeel/iot-operator | Kafka→SQS, managed PG, новый кластер | +| v0.1.50 | 2026-04-04 | pearlharbor (Harbor) | IoT controller + API + mqtt-bridge (Kafka) | diff --git a/doc/progress.md b/doc/progress.md index cb00af0..599c55b 100644 --- a/doc/progress.md +++ b/doc/progress.md @@ -35,3 +35,53 @@ - [ ] Деплой в кластер - [ ] E2E тест: создание устройства → MQTT → Kafka → Postgres → API - [ ] Убрать IoT-код из sless (опционально, после подтверждения что всё работает) + +--- + +## 2026-04-12: Замена Kafka → shared-SQS + деплой в новый кластер + +### Выполнено +- [x] Создана ветка `feature/replace-kafka-with-sqs` +- [x] Документировано решение: `doc/decisions/2026-04-12-replace-kafka-with-sqs.md` +- [x] **mqtt-bridge** (`cmd/mqtt-bridge/main.go`): Kafka Writer → AWS SDK SQS SendMessage +- [x] **sqs-consumer** (`cmd/sqs-consumer/main.go`): новый — заменяет kafka-consumer, SQS ReceiveMessage → Postgres +- [x] **kafka-consumer** (`cmd/kafka-consumer/`): удалён из кода +- [x] **admin stats** (`internal/api/handler/iot_admin_stats_handler.go`): Kafka lag → SQS GetQueueAttributes +- [x] **handler.go**: убрано поле KafkaBrokers +- [x] **iot-operator/main.go**: убрана передача KAFKA_BROKERS +- [x] go.mod: убран `segmentio/kafka-go`, добавлен `aws-sdk-go-v2` (v1.41.5 + sqs v1.42.25) +- [x] Dockerfile: kafka-consumer → sqs-consumer +- [x] Makefile: build-consumer → sqs-consumer +- [x] .gitignore: исправлен баг (паттерны без `/` игнорировали `cmd/` директории) +- [x] `go build ./...` проходит на ВМ +- [x] Коммит и пуш в ветку + +### Деплой в кластер iot-naeel (новый кластер) +- [x] shared-SQS: создан tenant `iot-service` (t-96afe7e9f781f6ca), очередь `iot-telemetry` +- [x] Namespace `sless` создан +- [x] Secret `iot-sqs-credentials` создан +- [x] Secret `iot-bridge-credentials` создан +- [x] Postgres: переключён на managed PG17 (тот же что SQS billing) +- [x] Secret `iot-postgres-secret` создан (DSN → managed PG) +- [x] Docker: собран и запушен `naeel/iot-operator:v0.2.0` в Docker Hub +- [x] Deployment YAML: обновлены image на Docker Hub, убраны imagePullSecrets +- [x] EMQX: задеплоен (emqx.yaml + emqx-ws-ingress.yaml, адаптирован из sless) +- [x] iot-operator: задеплоен (новый iot-operator.yaml с RBAC) +- [x] CRD `iotdevices.iot.kube5s.ru` установлен +- [x] iot-mqtt-bridge: задеплоен, Running +- [x] iot-sqs-consumer: задеплоен, Running — подключился к PG и SQS +- [x] cert-manager: выпускает TLS для iot.kube5s.ru +- [x] Коммит и пуш + +### Что работает +- Все 4 пода Running 1/1 +- iot-operator: controller стартовал, MQTT auth/acl обрабатывает запросы от EMQX +- sqs-consumer: подключился к Postgres и SQS, polling loop запущен +- mqtt-bridge: подключён к EMQX и SQS, SQS queue resolved +- EMQX: MQTT :1883, WS :8083, dashboard :18083 + +### Следующие шаги +- [ ] E2E тест: создание устройства → MQTT → SQS → Postgres → API +- [ ] Нагрузочный тест +- [ ] Мерж ветки в master +- [ ] Убрать IoT-код из sless (опционально) diff --git a/doc/thinking/2026-04-12.md b/doc/thinking/2026-04-12.md index a623ec0..630d1a8 100644 --- a/doc/thinking/2026-04-12.md +++ b/doc/thinking/2026-04-12.md @@ -57,3 +57,68 @@ **Плюс:** 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.Writer` → `sqs.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-consumer` → `iot-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:9090` → `iot-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.