一、背景与目标
在做 MQTT 架构 PoC / 技术验证 时,常见需求包括:
-
在 同一台服务器 上部署 EMQX 集群
-
使用 Docker Compose
-
不使用企业版、不引入 License
-
验证:
- 集群可用性
- 后续 TLS / 认证 / Topic 设计
本文记录一次 完整、真实的实战过程 ,以及最终稳定可复现的解决方案。
二、结论先行(重要)
如果你不使用企业版、不配置 License,又需要多节点集群:
✅ 请使用 EMQX 5.8.x(如 5.8.8)
❌ 不要使用 5.9.0 及以上版本
原因见下文 License 章节。
三、核心问题与踩坑总结
1️⃣ EMQX 5.9.0+ 的 License 行为变化(关键)
从 EMQX v5.9.0 开始:
- EMQX 切换到 BSL 1.1(商业源许可证)
- 即使使用
emqx/emqx(社区镜像) - 在技术层面限制多节点启动
当你尝试多节点集群时,日志会直接报错并退出:
text
SINGLE_NODE_LICENSE,
make sure this node and peer nodes are configured with a valid license
📌 结论:
| 版本 | 是否可无 License 集群 |
|---|---|
| 5.8.x | ✅ 可以 |
| 5.9.0+ | ❌ 不可以 |
2️⃣ Docker + Erlang 集群的经典大坑
EMQX 集群底层是 Erlang 分布式系统,对节点名要求非常严格:
❌ 常见错误方式
- 使用
hostname - 使用 Docker DNS alias
- 使用宿主机 IP
- 使用 FQDN / 不一致的 host
这些都会导致典型错误:
text
Node emqx1@xxx not responding to pings
甚至连:
bash
emqx eval 'node().'
都会失败。
3️⃣ 正确、稳定、最少踩坑的方式(结论)
在 Docker Compose 同机部署场景中:
✅ 使用 Docker 内部固定 IP
❌ 不依赖 hostname / DNS
这是最稳妥、最容易复现的方案。
四、最终方案设计
技术选型
- EMQX:5.8.8
- 部署方式:Docker Compose
- 节点数:3
- 网络:Docker bridge + 固定 IP
- 数据:本地目录持久化
五、最终目录结构
text
.
├── docker-compose.yml
├── emqx1
│ ├── data
│ └── log
├── emqx2
│ ├── data
│ └── log
└── emqx3
├── data
└── log
六、完整 docker-compose.yml(可直接使用)
yaml
services:
emqx1:
image: emqx/emqx:5.8.8
container_name: emqx1
restart: unless-stopped
networks:
emqx-net:
ipv4_address: 172.31.255.11
environment:
EMQX_NODE__NAME: emqx1@172.31.255.11
EMQX_NODE__COOKIE: emqxclustersecret
EMQX_CLUSTER__DISCOVERY_STRATEGY: static
EMQX_CLUSTER__STATIC__SEEDS: emqx1@172.31.255.11,emqx2@172.31.255.12,emqx3@172.31.255.13
volumes:
- ./emqx1/data:/opt/emqx/data
- ./emqx1/log:/opt/emqx/log
ports:
- "1883:1883"
- "18083:18083"
emqx2:
image: emqx/emqx:5.8.8
container_name: emqx2
restart: unless-stopped
networks:
emqx-net:
ipv4_address: 172.31.255.12
environment:
EMQX_NODE__NAME: emqx2@172.31.255.12
EMQX_NODE__COOKIE: emqxclustersecret
EMQX_CLUSTER__DISCOVERY_STRATEGY: static
EMQX_CLUSTER__STATIC__SEEDS: emqx1@172.31.255.11,emqx2@172.31.255.12,emqx3@172.31.255.13
volumes:
- ./emqx2/data:/opt/emqx/data
- ./emqx2/log:/opt/emqx/log
emqx3:
image: emqx/emqx:5.8.8
container_name: emqx3
restart: unless-stopped
networks:
emqx-net:
ipv4_address: 172.31.255.13
environment:
EMQX_NODE__NAME: emqx3@172.31.255.13
EMQX_NODE__COOKIE: emqxclustersecret
EMQX_CLUSTER__DISCOVERY_STRATEGY: static
EMQX_CLUSTER__STATIC__SEEDS: emqx1@172.31.255.11,emqx2@172.31.255.12,emqx3@172.31.255.13
volumes:
- ./emqx3/data:/opt/emqx/data
- ./emqx3/log:/opt/emqx/log
networks:
emqx-net:
driver: bridge
ipam:
config:
- subnet: 172.31.255.0/24
七、启动步骤(严格按顺序)
bash
# 创建目录
mkdir -p emqx1/{data,log} emqx2/{data,log} emqx3/{data,log}
chmod -R 777 emqx1 emqx2 emqx3
# 停止旧容器
docker compose down
# 清空旧数据(避免 5.9.x 残留)
rm -rf emqx1/data/* emqx2/data/* emqx3/data/*
# 启动
docker compose up -d
八、验证步骤
1️⃣ 容器状态
bash
docker ps | grep emqx
应看到 3 个 Up。
2️⃣ Erlang 节点验证(关键)
bash
docker exec -it emqx1 emqx eval 'node().'
docker exec -it emqx2 emqx eval 'node().'
docker exec -it emqx3 emqx eval 'node().'
输出示例:
text
emqx1@172.31.255.11
emqx2@172.31.255.12
emqx3@172.31.255.13
3️⃣ 集群状态
bash
docker exec -it emqx1 emqx ctl cluster status
期望结果:
text
running_nodes =>
emqx1@172.31.255.11
emqx2@172.31.255.12
emqx3@172.31.255.13
九、Dashboard 访问
text
http://宿主机IP:18083
默认账号:
text
admin / public
十、经验总结(非常重要)
✅ 推荐做法
- 使用 EMQX 5.8.x
- Docker 内部 IP 做节点名
- 显式指定 Erlang Cookie
- 清理 data 再切版本
❌ 不推荐做法
- 5.9.0+ 社区版无 License 集群
- 使用宿主机 IP 作为节点名
- 使用 hostname / DNS alias
- 版本切换不清 data
十一、下一步可做的事情
在此集群基础上,后续可以非常顺利地扩展:
- MQTT 双向 TLS(设备证书)
- HAProxy / Nginx MQTT 负载均衡
- EMQX 集群监控(Prometheus)
- 设备侧 Netty → MQTT 迁移
十二、结语
这次实践的最大价值不在于"搭起来了",而在于:
踩过了 EMQX + Docker + Erlang + License 的所有关键坑
后续无论是上云、上 K8s、还是切企业版,这套认知都会持续受用。