ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

群晖NAS部署Mosquitto MQTT服务:从Docker到外网访问全攻略

群晖NAS部署Mosquitto MQTT服务:从Docker到外网访问全攻略 1. 部署前想清楚的三件事选型理由、网络拓扑与镜像挑选如果你有一台群晖 DSM 7.7.1 的 NAS而且家里有一堆智能家居设备那么 MQTT 大概率是你迟早要碰的协议。我当初就是在折腾传感器接入时决定直接在这台群晖上部署 Mosquitto 2.0.22把整个 MQTT 服务器稳定地跑在 NAS 里顺便把外网访问也打通了。这篇就从选型、部署到外网访问把我踩过的坑和实际验证过的步骤完整写出来。动手之前先想清楚三个问题为什么要把 Broker 放在群晖上家里现有网络能不能支持外网访问镜像到底该选哪一个这三件事想明白了后面就不会反复折腾。1.1 为什么是群晖 Mosquitto而不是直接买一台服务器MQTT 服务器本质上就是一个消息中转站专门负责设备之间的订阅和发布。你可以把它理解成一个小区收发室所有设备都不直接互相喊话而是把消息交到收发室再由收发室分发给订阅了对应主题的住户。这样做的最大好处是解耦——传感器挂了不影响执行器工作设备之间也不需要知道彼此的 IP。选群晖当宿主理由其实很朴素NAS 本来就是 7x24 小时开机的设备而且群晖的容器服务DSM 7.x 上叫 Container Manager足够稳定。比起每年掏钱租云服务器或者专门弄一台小主机用现有 NAS 的成本几乎为零功耗也就多个十几瓦。至于为什么选 Mosquitto 而不是 EMQX我是这么考虑的EMQX 确实功能更强支持集群、内置 HTTP API、可视化管理界面但它的资源占用也明显更高在家庭场景下有杀鸡用牛刀的感觉。Mosquitto 是 Eclipse 基金会的老牌开源项目轻量、稳定、协议兼容性好光是做传感器数据采集、设备状态同步、自动化脚本消息通知它完全够用。官方镜像压缩后只有 10MB 左右内存占用通常不到 30MB对群晖来说几乎可以忽略。1.2 部署前必须确认的网络环境与依赖这里有一个很多人忽略的前提部署 Broker 本身不需要公网 IP但如果你打算做外网访问就要提前确认自己的宽带到底是不是公网 IP。我建议动手前先在群晖上确认几个信息DSM 版本已经升级到 7.7.1并且 Container Manager 可以正常打开群晖能拿到一个固定内网 IP比如 192.168.1.100避免路由器重启后容器连接地址变了路由器支持端口转发且管理后台还能正常访问如果要做外网访问确认宽带是公网 IP或者至少是动态公网 IP拨号后可能会变配合 DDNS 就能解决。很多人以为外网访问等于内网穿透其实如果你有公网 IP直接端口转发就是最稳、最快、最不需要第三方中转的方案。没有公网 IP 的话就需要先联系运营商申请开通各地政策不一样但由于智能家居需要外网访问家里的 NAS基本都是能说通的。1.3 镜像版本选择锁定 2.0.22 的理由Mosquitto 在 2.0 版本之后有一个非常大的行为变化默认情况下不再对全网开放匿名访问而且必须显式配置 listener 才能监听非本机地址。这个变化对新手来说特别容易踩坑——明明容器起来了外部就是连不上。我选择的是官方镜像eclipse-mosquitto的2.0.22标签而不是直接用latest。原因很简单latest随时可能更新一旦官方改了默认行为你重启容器后可能整个设备网络就静默断联锁定具体版本号至少能让配置、路径和验证方式保持可复现。如果你关注安全更新之后可以手动升级到同系列新版本但一定要重新看 release notes 再动手。镜像仓库里实际上有几个 tag 可选比如 2.0.22、2.0.22-openssl。普通家庭用途用默认的2.0.22就好openssl 版本主要是给需要 TLS 加密通信的场景准备的。2. 容器部署实操从拉镜像到跑起第一个 Broker我先说明一下群晖的 Container Manager 其实就是原来 Docker 套件的升级版界面长得不一样但底层还是 Docker。所以后面给出的命令在 SSH 下一样能跑。2.1 目录规划与配置文件准备部署 Mosquitto 之前我习惯先把目录结构准备好避免后面容器配置一团乱。目录规划如下/volume1/docker/mosquitto/ ├── conf/ ├── data/ └── log/conf放 mosquitto.conf、密码文件、ACL 文件data放持久化消息数据Mosquitto 启用了 persistence 后会把消息回话存在这里log放运行日志。这三个目录分开的好处是升级容器时只替换镜像配置和数据都还在排查问题时也可以只挂载 log 目录而不影响其他文件。在conf目录下创建一个mosquitto.conf最基础的内容是这样persistence true persistence_location /mosquitto/data/ log_dest file /mosquitto/log/mosquitto.log log_type error log_type warning log_type notice log_type information listener 1883 allow_anonymous false password_file /mosquitto/conf/passwd注意这里先不写 ACL先把匿名关闭、密码认证跑通再逐步加权限控制。配置文件里的/mosquitto/...是容器内部路径不是群晖宿主的/volume1/...这个一定要搞清楚否则挂载后文件找不到容器会报配置错误。2.2 两种部署方式图形界面与 compose 任选群晖 Container Manager 支持两种部署思路。第一种是纯图形界面在注册表里搜索eclipse-mosquitto镜像列表里选2.0.22然后按向导设置端口映射和卷映射。端口映射这里建议这样填本地端口1883映射容器端口1883如果需要 Websocket再加一条本地端口9001映射容器端口9001卷映射就是刚才的三个目录对应关系群晖目录容器目录/volume1/docker/mosquitto/conf/mosquitto/config/volume1/docker/mosquitto/data/mosquitto/data/volume1/docker/mosquitto/log/mosquitto/log这里的坑我后面会细说先记住一个点官方镜像默认把配置文件放在/mosquitto/config/mosquitto.conf所以conf目录要挂载到/mosquitto/config不是/mosquitto/conf。挂载错了容器会直接退出。第二种方式是用 docker compose这也是我更推荐的因为整个部署过程可以留存在一个文件里后面迁移或者重新部署非常方便。在群晖上用 SSH 进到/volume1/docker/mosquitto新建一个docker-compose.ymlservices: mosquitto: image: eclipse-mosquitto:2.0.22 container_name: mosquitto restart: unless-stopped ports: - 1883:1883 - 9001:9001 volumes: - /volume1/docker/mosquitto/conf:/mosquitto/config - /volume1/docker/mosquitto/data:/mosquitto/data - /volume1/docker/mosquitto/log:/mosquitto/log然后在同一目录执行docker compose up -d如果群晖没装 docker compose 插件可以直接在 Container Manager 里找到你创建的项目也能看到容器状态。compose 方式的好处是容器启动失败时你可以先用docker compose logs mosquitto看日志排查效率高很多。2.3 容器启动后的第一轮验证镜像拉取并启动后先在 Container Manager 里看容器是否处于running状态。如果容器反复重启大概率是配置文件路径不对或者目录权限不对。用 SSH 执行docker logs mosquitto正常会看到类似这样的输出168...: mosquitto version 2.0.22 starting 168...: Config loaded from /mosquitto/config/mosquitto.conf. 168...: Opening ipv4 listen socket on port 1883. 168...: mosquitto version 2.0.22 running如果看到Error: Invalid user mosquitto或者Permission denied那就是目录权限问题。官方镜像默认用 UID 1883 的用户运行宿主目录如果属主不是 1883容器进程就没法往里写 data 和 log。解决办法是 SSH 登录群晖后执行sudo chown -R 1883:1883 /volume1/docker/mosquitto然后重启容器。这一步在群晖上特别常见我第一次部署时忽略了结果 log 文件一直创建不出来走了不少弯路。3. 认证与权限控制Mosquitto 2.0 的匿名关闭、密码文件与 ACL这一步是 Mosquitto 2.0 和 1.x 最不一样的地方。我见过不少人从网上找了一篇老教程照着配完发现所有人都能连上来非常危险。所以认证和权限必须自己动手配清楚。3.1 先理解 Mosquitto 2.0 的默认行为变化2.0 版本之前的 Mosquitto默认监听所有网络接口而且默认允许匿名连接。也就是说只要端口暴露在局域网里任何设备都能自由发布和订阅消息。这在家庭场景里后果很严重——如果端口再映射到公网等于任何人都能往你的设备系统里写入消息甚至控制你家里那些不安全的智能设备。Mosquitto 2.0 大幅收紧了默认配置没有显式写 listener 时只监听本机回环地址没有配置密码文件时匿名访问被拒绝。这个变化方向是对的但对很多老教程造成冲击。我们在mosquitto.conf里已经写了listener 1883 allow_anonymous false password_file /mosquitto/conf/passwd注意listener 1883这一行必不可少否则默认只监听 127.0.0.1你从局域网其他设备连不上。3.2 生成密码文件与修改配置文件密码文件生成要用容器里的mosquitto_passwd工具宿主机一般没装这个命令。进入容器执行docker exec -it mosquitto sh cd /mosquitto/config mosquitto_passwd -c passwd mqttuser按提示输入两次密码。这里用的是交互式输入不要在命令行里直接带密码参数否则密码会留在 shell history 里一旦机器被入侵别人翻历史记录就能看到。生成之后退出容器检查一下passwd文件的属主和权限docker exec -it mosquitto ls -l /mosquitto/config/passwd如果属主不是 1883同样要调整宿主目录权限。另外强烈建议把passwd文件权限设为只有属主可读写至少不要让别人随便下载。需要添加更多用户时不要再用-c因为-c会重建文件并覆盖已有用户。追加用户用mosquitto_passwd -b /mosquitto/config/passwd user2 password2但同样-b会把密码明文写在命令行里我建议还是交互式操作mosquitto_passwd /mosquitto/config/passwd user23.3 ACL按主题隔离设备权限密码文件只负责验证身份但它不区分设备权限。也就是说只要密码正确任意客户端都能发布和订阅任何主题。想做到设备之间互相隔离就得配 ACL。在conf目录下创建aclfile例如# 管理员可以访问所有主题 user admin topic readwrite # # 温度传感器只能往自己的主题写数据 user temp_sensor topic write sensor/room1/temperature # 手机端 App 只能订阅传感器数据不能往传感器主题里写内容 user phone_app topic read sensor/#然后在mosquitto.conf里加一行acl_file /mosquitto/config/aclfile保存配置文件后需要重启容器让配置生效docker restart mosquittoACL 的语法核心是三个动作read、write、readwrite。主题匹配支持表示单层通配#表示多层通配。我的建议是为每个设备单独建一个用户比如temp_sensor只允许写sensor/room1/temperaturelight_switch只允许写light/room1/switch业务上需要控制哪个设备就单独授权。虽然用户多了管理起来麻烦但真的能避免设备 A 误操作设备 B 的尴尬局面。如果你觉得自己设备不多、全屋都信得过可以先用 admin 用户全通权限跑通之后再按设备拆分 ACL。千万别图省事直接关闭认证。4. 外网访问全链路公网 IP、端口转发与 DDNS 的配合终于到了标题里最诱人的部分外网访问。很多人一听到外网访问就想到各种旁路设备、中转服务实际上在有公网 IP 的前提下最简单可靠的方案就是 DDNS 路由器端口转发。4.1 先搞清楚家里到底有没有公网 IP判断方法很简单登录路由器管理界面看 WAN 口 IP然后用手机流量访问一个查 IP的服务看两者是否一致。一致就说明是公网 IP不一致则说明上层还有 NAT你获得的只是运营商内网 IP。还有一种更坑的情况宽带 IPv4 没有公网地址但路由器拨号拿到的是运营商大内网地址典型特征是你查到的 IP 和路由器 WAN IP 完全不同而且在路由器里看到的是 100.x.x.x 这种 CGN 地址。这种情况通常要打运营商的客服电话说明家里 NAS 需要外网访问申请开通公网 IP。我个人的经验是能申请就申请这比任何中转方案都稳定。动态公网 IP 也没关系动态就意味着 IP 会变所以才需要 DDNS 来绑定域名。4.2 路由器端口转发与容器端口的关系端口转发的基本逻辑是外部请求进来时路由器看到某个端口的数据包就把数据包转发给内网某个 IP 的某个端口。我们刚才把 Mosquitto 的 1883 端口映射到了群晖的 1883 端口所以路由器上需要把外网端口转发到群晖的 1883 端口。不同路由器的管理界面叫法不同有的叫端口映射有的叫虚拟服务器本质上都一样。我建议在路由器里这样设置参数值外部端口18883不要直接用 1883内部 IP192.168.1.100群晖内网 IP内部端口1883协议TCP为什么外部端口建议改成 18883 而不是 1883因为 1883 是 MQTT 的默认端口扫描器最喜欢扫这些默认端口。改成非标准端口后至少能过滤掉一批无差别扫描的脚本。外网连接时客户端地址就变成你的域名:18883。这里有个细节一定要记住外部端口是路由器决定的内部端口必须是容器映射到群晖宿主机的端口。你在群晖 Container Manager 里看到的是宿主机 1883 - 容器 1883所以路由器内部端口填 1883如果群晖上你映射的是 18883 - 1883那路由器内部端口也要跟着填 18883。4.3 DDNS 域名解析与群晖自带 DDNS 设置有了公网 IP还要解决 IP 变来变去的问题。群晖自带 DDNS 功能不需要额外跑服务直接在控制面板里配置就行。路径是控制面板 → 外部访问 → DDNS → 新增。选择一个你信任的服务商填入一个域名然后群晖会自动把当前公网 IP 绑定到这个域名上。以后 IP 变了群晖会自动更新解析记录你外网访问时只需要记域名就够了。配置完成后先不要急着拿手机去连先在局域网内测试一下域名能不能解析到群晖的 IP。如果 DDNS 状态显示正常再用手机流量连一次试试。4.4 外网访问的安全加固这一步你必须重视。把 1883 端口暴露到公网等于把你家的智能家居总线直接伸出窗外。一旦有人拿到弱密码就能往你家的所有设备广播消息。我整理了一份适合自己的加固清单关闭匿名连接使用强密码这一步前文已经做了外部端口不用默认端口改成随机高位端口每个设备独立账号配 ACL 限制主题权限路由器防火墙只放行必要的来源 IP比如你手机的运营商 IP 段有条件的话给 Mosquitto 配上 TLS 证书用 8883 端口加密通信。最后一条对家庭用户来说有点门槛但值得投入。简单说TLS 可以让消息在传输过程中不被明文抓包看到。如果你手里有域名可以申请一个泛域名证书配置过程也不算太难。我目前用的是自建 CA 客户端证书方案主要在信得过并且流量比较敏感的设备上使用。如果你只想做功能验证先把前四条做好TLS 可以放进后续计划里。5. 实战验证与故障排查从本地 subscribe 到跨网 pub部署完成不等于能用必须实打实地验证一遍。我的习惯是先在本地局域网验证再走到外网去验证这样任何一步出问题都能快速缩小范围。5.1 本地验证流程mosquitto_pub 与 mosquitto_sub先找一个支持 MQTT 的测试客户端。我这里直接用命令行工具。如果你的电脑没安装 Mosquitto 客户端可以用跨平台的 MQTTX 图形工具下载安装后填地址就能用。假设我叫mqttuser密码是yourpassword群晖 IP 是192.168.1.100。先订阅一个主题mosquitto_sub -h 192.168.1.100 -p 1883 -t test/topic -u mqttuser -P yourpassword看到命令挂住不退出说明成功连接到 Broker。再开一个终端发布消息mosquitto_pub -h 192.168.1.100 -p 1883 -t test/topic -m hello from home -u mqttuser -P yourpassword如果在订阅端终端看到了hello from home说明本地收发链路通了。这里有个小技巧不一定非要在电脑上装客户端。直接在群晖容器里也能跑测试命令docker exec -it mosquitto mosquitto_sub -h 127.0.0.1 -p 1883 -t test/topic -u mqttuser -P yourpassword在容器内测试的好处是不依赖外网客户端工具而且能验证容器内部网络是否正常。如果容器内能通但局域网电脑连不通那问题基本出在端口映射或群晖防火墙。5.2 外网端到端测试从手机 4G/5G 网络连一次外网测试时手机一定要切到移动网络不能用 WiFi。因为 WiFi 连着家里的路由器走的还是内网测不出真实效果。在手机 MQTT 客户端里填入地址你配置的 DDNS 域名或者当前公网 IP端口路由器外部端口比如 18883用户名和密码之前创建的用户。然后订阅一个测试主题再用另一台设备可以是家里的电脑向内网 Broker 发布消息。如果手机能收到说明整条链路通了。如果连接失败就从五个方向逐一排查现象可能原因检查方式连接超时路由器端口转发没做对登录路由器看转发规则确认外部端口、内部 IP、内部端口是否一致域名解析失败DDNS 没生效控制面板里看 DDNS 状态是否提示正常认证失败密码文件没生效检查 mosquitto.conf 是否加载了 passwd 文件密码文件路径是否匹配拒绝连接群晖防火墙拦截看群晖控制面板的防火墙规则放行对应端口连上了但收不到消息ACL 权限不对查看订阅端的用户是否有该主题的 read 权限5.3 常见故障链路与日志分析最后分享一个我自己的排错习惯任何连接异常第一件事就是看日志而不是反复改配置。docker logs mosquitto --tail 50日志里如果出现Connection from ... failed后面跟着client ... has exceeded说明密码错误或者用户不存在。如果出现denied: ...基本就是 ACL 限制。如果日志里压根没有任何连接记录那就是网络层问题去检查路由器转发和防火墙。还有一个坑群晖自带的防火墙默认规则有时候会拦截来自局域网外部的连接。你可以在控制面板 → 安全性 → 防火墙里新增一条允许规则放行 1883 端口。我遇到过好几次容器起来了、DDNS 也正常、路由器转发也正确但手机就是连不上最后发现是群晖防火墙把 TCP 1883 拦了。端口冲突也是个老问题。如果你之前在群晖上装过其他 MQTT 套件可能已经占了 1883 端口。启动容器前先检查netstat -tlnp | grep 1883有输出说明端口被占用容器自然起不来。另外关于日志文件膨胀的问题Mosquitto 的 log 文件会一直增长尤其是长期运行后。我的做法是在群晖的计划任务里定期清理超过 7 天的日志文件或者在 mosquitto.conf 里用log_dest file 外部 logrotate 配合。这不是最优雅的方案但对家用来说够用了。我个人在实际操作中的体会是MQTT 服务这种基础设施类应用一旦跑顺了基本就不用管但前面每一步配置都要认真。尤其是外网访问不要为了图方便省略安全配置否则后面吃苦的是自己。先把本地认证、ACL 弄扎实再打开外网通道这样即使有人扫到端口也突破不了第二道防线。最后再分享一个小技巧如果你以后想从手机远程配置群晖容器不需要再额外搭一套方案直接在 Container Manager 里就能查看容器状态和日志。MQTT 这种服务最怕的是无声崩溃所以我给 Mosquitto 加了一条自动重启策略并且把 DSM 的通知服务打开让容器异常退出时第一时间收到消息。这样整套方案才算真正闭环。
返回列表