服务卡片 · 自动化
Mosquitto:传感器下面的那条消息总线
二十五兆内存,却是所有「传感器形状」的东西所经过的那一环。Mosquitto 是 MQTT 的参考实现:传感器把读数发布到主题上,Home Assistant 订阅,谁都不需要知道谁的 IP。它同时也是机架上默认配置真正危险、必须在第一台设备接入前就换掉的那个服务。
| 容器镜像 | eclipse-mosquitto:2.0.20 |
|---|---|
| 宿主端口 | :1883/tcp, :9001/tcp |
| 数据目录 | /srv/homelab/mosquitto/data |
| 内存预留 | 25 MB |
| CPU 上限 | 0.10 vCPU (cpus: "0.10") — this is the cheapest service on the rack by a wide margin |
| 更新节奏 | yearly. The protocol is stable and the 2.0 configuration file is not going to change again soon |
| ARM 兼容 | arm64 native; one of the few services here that has no meaningful architecture dependency at all |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :1883 | 1883 | tcp | 仅局域网 | MQTT for the sensors and the dashboards that read them |
| :9001 | 9001 | tcp | 仅局域网 | WebSocket listener for browser dashboards |
部署步骤
用 mosquitto_passwd 生成密码文件,一设备一用户
文件必须对容器用户可读。这里出权限错误的表现,是「所有设备突然都用错密码了」。
run docker run --rm -it -v /srv/homelab/mosquitto/config:/cfg \ eclipse-mosquitto:2.0.20 mosquitto_passwd -c -b /cfg/passwd kitchen-sensor 'LONG-SECRET' docker run --rm -it -v /srv/homelab/mosquitto/config:/cfg \ eclipse-mosquitto:2.0.20 mosquitto_passwd -b /cfg/passwd homeassistant 'OTHER-SECRET'在第一台设备接入之前就写好 ACL 文件
等设备跑起来之后再补 ACL,意味着那台设备已经拥有过它不该有的权限。先写文件,再接设备。
run cat > /srv/homelab/mosquitto/config/acl <<'EOF' user kitchen-sensor topic write home/kitchen/# topic read home/kitchen/cmd user homeassistant topic read # topic write home/# EOF用一个没有凭据的客户端验证「拒绝」这条路
接受匿名客户端的 broker,就是一个会泄露整栋房子状态的 broker。要显式测一次拒绝,而不是相信配置文件。
run docker compose up -d mosquitto mosquitto_sub -h 192.168.10.20 -p 1883 -t '#' -C 1 # 预期:Connection error: Connection Refused: not authorised. docker exec mosquitto tail -5 /mosquitto/log/mosquitto.log
默认监听是匿名的,这一点必须先改
Mosquitto 2.0 在 allow_anonymous 没有显式设置时,会拒绝用 1.x 那套宽松默认值启动,这是进步——但教程仍然从 allow_anonymous true 开始。在这个设置下,任何能连到 1883 端口的人都能读到所有主题,包括那些会暴露家里有没有人的主题。
这里的配置是明确的:不允许匿名客户端,有密码文件,还有一个 ACL 文件,给每台设备恰好它需要的那些主题。
# /srv/homelab/mosquitto/config/mosquitto.conf
listener 1883 0.0.0.0
allow_anonymous false
password_file /mosquitto/config/passwd
acl_file /mosquitto/config/acl
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.logACL 才是拦住被攻陷传感器的东西
一个密码泄露的 ESP 传感器,不应该能订阅「家里现在有没有人」那个主题。主题级 ACL 由 broker 强制执行,配置起来不花任何代价。
这里的模式是一设备一用户,用设备名命名,只有对自己那棵子树的写权限,以及只对控制自己的那条命令主题的读权限。
# /srv/homelab/mosquitto/config/acl
user kitchen-sensor
topic write home/kitchen/#
topic read home/kitchen/cmd
user homeassistant
topic read #
topic write home/#WebSocket 监听只为一个理由存在
浏览器开不了原始的 TCP MQTT 连接,所以 9001 上的 WebSocket 监听是给那台直接订阅主题的壁挂仪表盘用的。家里其它一切都用 1883 上的普通 MQTT。
WebSocket 监听的名声不好,是因为浏览器客户端最容易泄露凭据。仪表盘持有的是一个只读账号,而这是唯一重要的缓解措施。
compose 文件
整份文件放在 /srv/homelab/mosquitto/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
mosquitto:
image: eclipse-mosquitto:2.0.20
container_name: mosquitto
restart: unless-stopped
ports:
- "1883:1883/tcp"
- "9001:9001/tcp"
environment:
TZ: Asia/Shanghai
volumes:
- /srv/homelab/mosquitto/config:/mosquitto/config
- /srv/homelab/mosquitto/data:/mosquitto/data
- /srv/homelab/mosquitto/log:/mosquitto/log
networks: [rack]
networks:
rack:
external: true加固清单
- 关闭匿名访问,每个客户端都用用户名密码认证
- 按客户端配置 ACL:ESP 传感器只能发布到自己的主题分支,订阅不了别人的
- 监听绑定在局域网上、不做转发;远程传感器通过 WireGuard 连进来
- 局域网监听上配置了 TLS 并使用本地信任的证书,即使在家里,凭据也不会明文传输
备份方案
broker 的持久化数据库里存着排队消息和保留值;丢了它意味着传感器会在一个上报周期内重新发布状态,而保留主题会空到下一次读数为止。真正值得复制的只有配置和密码文件,两个都很小。
上线后验证
- 不带凭据的客户端被拒绝,且拒绝记录出现在 broker 日志里
- 厨房传感器能发布到自己主题上,尝试读取「有人在家」主题时被拒绝
- 往某个主题发布的保留消息,会立刻投递给新订阅者
- broker 用同一个密码文件重启后,所有设备无需重配即可重连
踩过的坑
- 密码文件要用 mosquitto_passwd 生成,不能手写;而且文件必须对容器用户可读。这里出权限错误的表现,是「所有设备突然都用错密码了」。
- 保留消息会跨 broker 重启存在,并投递给每一个新订阅者。来自一台已被移除的传感器的过期保留读数,会永远留在仪表盘上,直到有人往那个主题发一个空载荷把它清掉。
- 持久化数据库会随着离线客户端的排队消息增长。一台以 QoS 1 订阅的设备离线后,broker 会为它把一切都排起队;给每客户端设置最大排队消息数能避免慢性膨胀。
- 配置错误的客户端发出的一个通配订阅,就能拉走整棵主题树。拦住它的地方是 ACL 文件,不是客户端。
硬件与选型问答
- 只有 Zigbee 设备的话还需要 MQTT 吗?
- 不一定。Home Assistant 能直接和 Zigbee 协调器通信,加一个没人往里发布消息的 broker,就是白白多一个要维护的服务。当家里有基于 ESP 的传感器或者原生讲 MQTT 的 DIY 设备时,它才值这个位置——而大多数有意思的项目最后都会走到那里。
- 为什么它的内存占用比这里其它一切都低这么多?
- 因为 broker 就是一张路由表加一堆订阅,而不是一个应用。二十五兆主要是进程本身的开销,而一个房子里的消息量微不足道。它被放进这台机架,恰恰因为它几乎不花钱,却能省掉大量点对点配置。
- 远程传感器要不要把 broker 暴露到公网?
- 不要。远程传感器应该待在 WireGuard 隧道里,亲戚家车库里的那一个就是这么连的。公开暴露 1883 只会招来 MQTT broker 常见的那种暴力破解流量,而隧道能提供的能力它一样提供不了。