2. 搭建MQTT实验环境

在正式进入 mTLS 双向认证之前,必须先有一个可反复启动、可随时重置的 MQTT Broker 实验环境。本节的目标很明确:选一个 Broker、用 Docker 把它跑起来、开放基础监听端口,最后用 mosquitto_pub 与 mosquitto_sub 完成一次真实的发布/订阅往返,确认链路是通的。只有这一步稳定了,后面叠加 TLS、客户端证书、CA 校验时,才能把“网络问题”和“认证问题”区分开。

一、Broker 选型:Mosquitto 还是 EMQX

两者都能承载 mTLS,但定位不同。Mosquitto 是轻量级 C 实现,单文件配置、资源占用小,配置文件 mosquitto.conf 结构清晰,非常适合教学与协议细节验证;EMQX 基于 Erlang/OTP,自带 Dashboard、集群、规则引擎和丰富的认证插件,适合模拟生产级接入。对于本课程,建议以 Mosquitto 为主,因为它把 TLS 与认证配置暴露得最直接,便于观察每一项参数的作用;EMQX 可作为对照,用于理解企业环境中的等价配置。

选型判断可以简化为:如果目标是“看清 mTLS 握手的每一个开关”,选 Mosquitto;如果目标是“验证大规模设备接入与可视化运维”,选 EMQX。本节实验统一使用 Mosquitto 2.x 镜像。

二、Docker 快速部署

实验目录建议固定为 ~/mqtt-lab,内部结构如下:config/ 放配置文件,certs/ 放证书,data/ 与 log/ 用于持久化。先创建目录并写入最小配置:

mkdir -p ~/mqtt-lab/{config,certs,data,log}
cd ~/mqtt-lab

cat > config/mosquitto.conf <<'EOF'
listener 1883 0.0.0.0
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest stdout
log_type all
EOF

这里 listener 1883 0.0.0.0 表示在容器内所有网卡上监听 1883;allow_anonymous true 仅用于本节连通性验证,进入 mTLS 章节后必须改为 false 并挂载证书。启动容器:

docker run -d --name mqtt-lab \
  -p 1883:1883 \
  -v ~/mqtt-lab/config:/mosquitto/config \
  -v ~/mqtt-lab/data:/mosquitto/data \
  -v ~/mqtt-lab/log:/mosquitto/log \
  eclipse-mosquitto:2.0

docker logs -f mqtt-lab

日志中出现 Opening ipv4 listen socket on port 1883 与 mosquitto version 2.0.x running,说明 Broker 已就绪。若容器反复重启,优先检查配置文件缩进与路径挂载是否正确。

三、端口与网络关系

端口规划要提前想清楚,因为后续 mTLS 会新增监听。基础阶段使用 1883(明文 TCP);mTLS 阶段通常使用 8883(TLS);若启用 WebSocket,可再加 8083。Docker 的 -p 宿主机:容器 映射必须与 mosquitto.conf 中的 listener 端口一致,否则会出现“容器内正常、宿主机连不上”的典型问题。

MQTT 实验环境:宿主机 ↔ Docker ↔ Broker 端口映射 宿主机 Host mosquitto_pub 发布客户端 mosquitto_sub 订阅客户端 localhost:1883 宿主机监听端口 ~/mqtt-lab/config ~/mqtt-lab/data -p 1883:1883 Docker 容器 mqtt-lab eclipse-mosquitto:2.0 Broker 进程 listener 1883 容器内监听 allow_anonymous true(仅本节) /mosquitto/config /mosquitto/data -v 挂载 端口规划 1883 TCP 明文,本节使用 8883 TLS mTLS 阶段启用 8083 WS WebSocket 可选 端口必须与 listener 一致

四、用 mosquitto_pub/sub 验证连通性

验证思路是“先订阅、后发布”,观察订阅端是否收到消息。打开两个终端。终端 A 订阅主题 lab/test:

docker exec -it mqtt-lab \
  mosquitto_sub -h localhost -p 1883 -t 'lab/test' -v

终端 B 发布一条消息:

docker exec -it mqtt-lab \
  mosquitto_pub -h localhost -p 1883 -t 'lab/test' -m 'hello-mtls'

终端 A 应输出 lab/test hello-mtls。若在宿主机而非容器内执行,把 docker exec -it mqtt-lab 去掉,-h 仍用 localhost,因为端口已映射到宿主机。这一步成功,说明 Broker 监听、端口映射、发布订阅链路全部正常。

进一步验证通配符与 QoS:订阅 lab/# 可接收所有子主题;发布时加 -q 1 观察 QoS 1 的 PUBACK 行为。还可以用 -d 参数打印协议交互细节,例如:

mosquitto_sub -h localhost -p 1883 -t 'lab/#' -q 1 -d

输出中会看到 CONNECT、CONNACK、SUBSCRIBE、SUBACK、PUBLISH、PUBACK 等报文,这对理解后续 mTLS 握手前后的差异非常有帮助。

五、常见问题与判断依据

  • Connection refused:容器未启动或端口未映射。用 docker ps 确认状态,用 docker logs mqtt-lab 查看监听日志。
  • 订阅端无输出:主题不匹配或发布先于订阅完成。先启动订阅,再发布;注意大小写与层级。
  • 容器启动即退出:配置文件语法错误。检查 mosquitto.conf 是否有非法指令或路径不存在。
  • 宿主机连不上但容器内正常:-p 映射端口与 listener 端口不一致,或宿主机防火墙拦截。
建议把本节命令整理成一个 lab-up.sh 脚本,包含目录创建、配置写入、容器启动与日志查看。后续每次做 mTLS 实验前先执行 docker rm -f mqtt-lab 重置环境,保证每次实验从干净状态开始,避免旧证书或旧配置干扰判断。

完成本节后,你应当拥有一个可随时启动的 Mosquitto 容器、一个明确的端口规划,以及一套可复现的发布/订阅验证方法。这套环境就是后续 mTLS 配置的基线:当 8883 端口启用 TLS 后,同样的 pub/sub 命令只需增加证书参数,就能对比出“明文连通”和“双向认证连通”的差异。