# Создано: 2026-04-04 # EMQX MQTT-брокер для IoT-сервиса (namespace: sless). # # Архитектура: # IoT Device → MQTT CONNECT → EMQX (HTTP auth → sless-operator:9090/internal/mqtt/auth) # EMQX → MQTT PUBLISH → sless-iot-bridge (paho subscriber) → RabbitMQ queue iot.{ns}.telemetry # RabbitMQ → event-dispatcher → serverless function # # EMQX 5.x конфиг через emqx.conf (HOCON формат), монтируется как ConfigMap volume. # НЕ используем env vars для конфигурации EMQX 5.x — они не поддерживаются аналогично 4.x. # # Порты: # 1883 — MQTT (plaintext) # 8883 — MQTTS (TLS, для prod надо настроить certSecret) # 8083 — MQTT over WebSocket # 18083 — EMQX Dashboard (admin/public по умолчанию — менять в prod!) # # Применение: kubectl apply -f deployments/k8s/emqx.yaml --- apiVersion: v1 kind: ConfigMap metadata: name: emqx-config namespace: sless data: # emqx.conf — HOCON конфиг для EMQX 5.5.x # Раздел authentication: HTTP Backend для проверки MQTT credentials IoT-устройств. # Наш сервис (sless-operator) ищет Secret iot-{deviceId} и сравнивает пароль. emqx.conf: | ## EMQX 5.x configuration (HOCON format) ## Изменено: 2026-04-04 ## Обязательные поля node — без них EMQX 5.x падает при старте ## node.cookie — секрет кластерного Erlang-соединения, для single-node любая строка ## node.data_dir — директория данных (mnesia, конфиги), должна существовать в контейнере node { name = "emqx@127.0.0.1" cookie = "sless-emqx-cookie-mvp" data_dir = "/opt/emqx/data" } ## HTTP Auth Backend для IoT-устройств ## EMQX посылает POST с {username, password, clientid} → наш сервис отвечает {"result":"allow"|"deny"} authentication = [ { mechanism = password_based backend = http enable = true method = post url = "http://sless-operator.sless.svc:9090/internal/mqtt/auth" body { username = "${username}" password = "${password}" clientid = "${clientid}" } headers { "content-type" = "application/json" } connect_timeout = 5s request_timeout = 5s ## allow_timeout_error = false — если наш сервис не отвечает, deny (безопаснее) pool_size = 8 } ] ## Authorization (ACL) — HTTP backend для изоляции топиков по устройству. ## no_match = deny: если HTTP backend недоступен или не ответил — запрещаем. ## Endpoint /internal/mqtt/acl возвращает allow только для топиков {ns}/{deviceId}/# authorization { no_match = deny deny_action = disconnect cache { enable = true max_size = 32 ttl = 1m } sources = [ { type = http enable = true method = post url = "http://sless-operator.sless.svc:9090/internal/mqtt/acl" body { username = "${username}" clientid = "${clientid}" action = "${action}" topic = "${topic}" } headers { "content-type" = "application/json" } connect_timeout = 5s request_timeout = 5s pool_size = 8 } ] } ## MQTT настройки mqtt { max_packet_size = 1MB max_topic_levels = 10 retain_available = false } ## Listeners — только plaintext MQTT для MVP ## TLS (8883) отключён — настроить при необходимости listeners.tcp.default { bind = "0.0.0.0:1883" max_connections = 1024 } listeners.ws.default { bind = "0.0.0.0:8083" max_connections = 512 } ## Dashboard dashboard { listeners.http { bind = 18083 } } --- apiVersion: apps/v1 kind: Deployment metadata: name: emqx namespace: sless labels: app: emqx spec: replicas: 1 selector: matchLabels: app: emqx template: metadata: labels: app: emqx spec: containers: - name: emqx image: emqx/emqx:5.5.1 ports: - name: mqtt containerPort: 1883 - name: ws containerPort: 8083 - name: dashboard containerPort: 18083 volumeMounts: - name: emqx-conf mountPath: /opt/emqx/etc/emqx.conf subPath: emqx.conf resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" readinessProbe: tcpSocket: port: 1883 initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 5 livenessProbe: tcpSocket: port: 1883 initialDelaySeconds: 40 periodSeconds: 20 volumes: - name: emqx-conf configMap: name: emqx-config --- apiVersion: v1 kind: Service metadata: name: emqx namespace: sless spec: selector: app: emqx ports: - name: mqtt port: 1883 targetPort: 1883 - name: ws port: 8083 targetPort: 8083 - name: dashboard port: 18083 targetPort: 18083