Дача дышала в домашний брокер на малине исправно: теплица публиковала температуру, реле подчинялось командам. Захотелось смотреть это из города — и первая мысль была правильной по направлению и неправильной по исполнению: пробросить порт 1883 на роутере. Через два дня я заглянул в лог брокера и похолодел: сотни попыток подключения в час от клиентов с именами admin, test, backup — интернет сканирует открывшийся 1883 постоянно, не спрашивая, чей это дом.
Вторая попытка кончилась раньше первой: на дачном тарифе оказался серый адрес от провайдера (CGNAT), и пробрасывать было вообще нечего — у дома нет внешнего порта. Выручило перевёрнутое мышление: не пускать город к дому, а дать дому самому дойти до города. Мост, bridge — режим, в котором ваш локальный mosquitto сам подключается наружу к облачному брокеру как обычный клиент и держит линию.
Исходящее соединение NAT не мешает: инициатива всегда за той стороной, что за NAT, — и она же держит связь пингами. Облако видит ваши данные у себя, команды едут назад по подписке — и дом при этом закрыт наглухо: ни одного входящего порта.
Механика проста: mosquitto с настроенным мостом становится MQTT-клиентом удалённого брокера. Он открывает соединение, подписывается на заданные ветки в заданных направлениях и пересылает сообщения через границу двух брокеров. Со стороны облака это выглядит как ещё один клиент с паспортом и подписками — ничего особенного; ровно та модель издателей и подписчиков, что разобрана в уроке про основы pub-sub: мост публикует свои ветки наружу и подписывается на чужие. Вся конфигурация живёт на вашей стороне, в файле conf.d рядом с настройками из урока про установку брокера.
В конфиге три главных слова. connection — имя моста: оно всплывает в логах и в служебных топиках состояния. address — куда подключаться: хост и порт удалённого брокера. topic — правило пересылки: какая ветка, в каком направлении и с каким качеством обслуживания. Направлений ровно три: out — из дома наружу, in — изнаружи в дом, both — в обе стороны.
Мостов может быть несколько: один к облачному брокеру для дашборда, второй — к брокеру соседа для общего проекта, у каждого своё имя и свои правила веток. Держать их в одном файле удобно, но логически они независимы: падение одного не тянет второй.
Конфиг: connection, address, topic
# /etc/mosquitto/conf.d/bridge.confconnection dacha-cloudaddress cloud.example.com:8883remote_username dacharemote_password tihij-les-42bridge_cafile /etc/ssl/certs/ca-certificates.crt topic dacha/# out 1 # телеметрия дома уезжает в облакоtopic cmd/dacha/# in 1 # команды из облака приезжают в домtopic stat/# both 1 # служебная ветка, обмен в обе стороны notifications true # состояние моста видно в служебном топикеcleansession false # облако копит команды, пока мост лежитrestart_timeout 30 # пауза между попытками reconnectРазбор направлений с моими границами. dacha/# наружу — телеметрия уезжает в облако, откуда её читает дашборд. cmd/dacha/# внутрь — команды с облачного брокера приезжают в домашний, а дальше всё как обычно: подписка, callback — конвейер приёма разобран в уроке про subscribe и callback, и финальный щелчок реле тот же, что в уроке про модуль реле 220 В: команда до пина, с проверкой payload. both я ставлю осознанно и только на отдельные ветки: двусторонняя пересылка одного и того же топика двумя разными мостами — прямой путь к дублям, об этом ниже.
Тонкость с адресами: в облаке ваша ветка может столкнуться с чужой — не все владельцы брокеров аккуратны с именами. У слова topic есть необязательные суффиксы local-prefix и remote-prefix: приставка подставляется к имени с каждой стороны. Я предпочитаю решать вопрос уникальным префиксом ещё на этапе имён — привычка из урока про публичные брокеры — но если имена уже заняты, приставки спасают без переименования всего дома.
Пара cleansession false — моя любимая строка в конфиге моста. Пока мост отключён — интернет у малины моргнул, провайдер рестартнул оборудование — облачный брокер копит входящие для моста и отдаёт их при восстановлении, если сообщения поедут с QoS 1. С чистой сессией команда, отправленная в момент обрыва, просто исчезнет: насос не включится, а вы будете гадать, кто виноват. restart_timeout 30 задаёт паузу между попытками reconnect — брокер поднимает мост сам, без скриптов и костылей.
| Направление | Куда летит | Кто инициатор | Мой случай |
|---|---|---|---|
| out | локальный топик уезжает на удалённый брокер | мост шлёт наружу | телеметрия теплицы в облачный дашборд |
| in | удалённый топик приезжает в локальный брокер | мост забирает сам | команды с дашборда в дом, к реле |
| both | в обе стороны по одной ветке | мост | служебные ветки статуса, где нужен ответ |
| без моста | никуда: нужен входящий порт снаружи | город звонит в дом | то, от чего я ушёл после логов с ботами |
Почему мост проходит там, где проброс порта не прошёл. Соединение устанавливает тот, кто за NAT: малина звонит в облако, роутер пропускает исходящий поток, и дальше обе стороны общаются по уже открытому каналу. Облачному брокеру не нужен адрес вашего дома — ему нужен clientId моста. Пусть провайдер меняет внешний адрес хоть каждые сутки: после обрыва мост сам переподключится через restart_timeout, и все подписки восстановятся — та же дисциплина reconnect, что у плат, разобранная в уроке про автопереподключение Wi-Fi, только на стороне брокера. Пока мост жив, линию держит сам протокол: раз в несколько секунд идут пинги туда-обратно, и NAT не рвёт канал, потому что трафик по нему идёт постоянно.
Фраза возврат в дом из моей записки объясняется этой механикой. Город не может дозвониться до дачи — и не пытается: дача стоит на линии, и город говорит по ней. Дашборд шлёт команду на облачный брокер, тот раздаёт её подписчикам, мост — один из подписчиков — забирает команду в локальный брокер, и дальше она едет по домашним веткам как родная. Для дома всё выглядит так, будто команда пришла из соседней комнаты.
Граница применимости, чтобы не ждать от моста лишнего. Он пересылает сообщения и не умеет ничего кроме: не трансформирует payload, не хранит историю, не агрегирует показания. Если нужна логика вида среднее за час или перекладка данных из формата в формат — это задача скетча или серверного скрипта, а не конфига брокера. Мост — труба с фильтром веток, и это ровно та роль, в которой он непобедим.
Петли и ограничения
Петли. Один мост между двумя брокерами не зациклится: мост подписывается на удалённые ветки с флагом no_local, поэтому собственные сообщения, которые он сам же туда доставил, обратно не возвращаются. Ломается это на конфигурациях сложнее пары: три брокера, соединённые мостами в кольцо A-B, B-C, C-A, уже могут гонять одно сообщение по кругу — каждый проход добавляет дубль, а счёт подписчиков у сообщения не ведётся. Моё правило без исключений: топология — звезда или иерархия, кольца не строить.
Дубли дают и две параллельные дороги для одного топика: два моста в both на одну ветку — каждое сообщение умножается на два при каждом проходе. Симптом узнаваем мгновенно: дашборд показывает значения пачками по два-четыре. Первым делом я считаю, сколько мостов смотрит на эту ветку, — и всегда находил лишний.
Ограничение, о котором узнают поздно: мост настраивается только на стороне Mosquitto. Облачный брокер — просто брокер: приём подключений, логин, пароль, возможно TLS-сертификат. Никакой панели настроить мост у чужого брокера нет — многие облака предлагают собственные агенты и SDK, но это другие механизмы с другими словами и другой логикой. Если вторая сторона тоже mosquitto, мост можно строить с любого конца — и это вся имеющаяся свобода.
- Наберите 1, затем 0, затем 7: первые две команды щёлкают реле, третья уходит в IGNOR — так фильтр направлений и белый список payload работают в одной связке.
- Поменяйте период локальной публикации с 4000 на 8000 и посчитайте, сколько сообщений в сутки уедет в облако: 10800. Сверьте с арифметикой периодов из урока про публикацию показаний.
- Допишите третье правило для ветки log/# с направлением out: одна строка topic в конфиге моста и одна ветка в switch симулятора. Правила должны совпадать в обеих картинках мира.
- Начертите схему своего дома: брокер, ветки, стрелки out, in, both. Когда схема нарисована, конфиг пишется за пять минут — и сразу видно, где случайно намечается кольцо.
- Мост — mosquitto-клиент удалённого брокера: connection, address, topic; вся конфигурация живёт на вашей стороне
- out — дом в облако, in — облако в дом, both — в обе стороны; телеметрия и команды едут по разным веткам
- Мост решает NAT: соединение устанавливает дом, исходящий поток роутер пропускает, restart_timeout поднимает связь после обрыва
- cleansession false копит QoS 1 сообщения на облачной стороне, пока мост лежит; no_local не возвращает мосту его же сообщения
- Кольцо из трёх брокеров и два моста на одну ветку дают петли и дубли: звезда вместо кольца; мост настраивается только на стороне Mosquitto
- 1. У дачи серый адрес провайдера, CGNAT. Что свяжет домашний брокер с облачным?
- 2. Что делает строка topic cmd/dacha/# in 1 в конфиге моста?
- 3. Команда, отправленная в момент обрыва интернета у малины, не доехала. Какая пара настроек спасает?
- 4. Три брокера соединены мостами в кольцо A-B, B-C, C-A, направления both. Что вы увидите?
- 5. Облачная платформа предлагает настроить мост в своей панели. Что из этого следует?
Частые вопросы по теме урока
- У дачи серый адрес провайдера, CGNAT. Что свяжет домашний брокер с облачным?
- Входящее соединение в CGNAT не построить — внешнего порта нет. Исходящее — без проблем: инициатор всегда дом, и NAT исходящий поток пропускает. Мост: локальный mosquitto сам подключается наружу к облаку
- Что делает строка topic cmd/dacha/# in 1 в конфиге моста?
- in — направление в дом: мост-клиент подписан на облачную ветку и доставляет её сообщения локально, единица — QoS пересылки. Забирает сообщения облачной ветки cmd/dacha/# в локальный брокер с QoS 1
- Команда, отправленная в момент обрыва интернета у малины, не доехала. Какая пара настроек спасает?
- С чистой сессией облачный брокер ничего не хранит для отключённого моста. cleansession false плюс QoS 1 заставляет облако копить команды и отдать их после восстановления. cleansession false и QoS 1 у topic in
- Три брокера соединены мостами в кольцо A-B, B-C, C-A, направления both. Что вы увидите?
- no_local защищает отдельный мост от его же сообщений, но кольцо возвращает сообщение соседним маршрутом — и дальше по кругу. Лечение: звезда вместо кольца. Сообщение циркулирует и плодит дубли по кругу
- Облачная платформа предлагает настроить мост в своей панели. Что из этого следует?
- Мост — клиент удалённого брокера: той стороне нужен только приём подключений, логин и возможно TLS. Панели облаков — про их собственные агенты, это другие механизмы. Мост на второй стороне не настраивается: вся логика живёт в конфиге вашего Mosquitto