4、Docker基础回顾:Docker核心概念、镜像、容器、Docker Compose简介
在正式部署MQTT Broker测试容器之前,必须扎实掌握Docker的核心知识体系。本章并非简单的概念罗列,而是围绕“镜像与容器的关系”“数据持久化机制”“容器间通信”以及“Compose编排”四条主线展开,帮助你建立从单个容器到多服务编排的完整认知。我们将通过一个轻量级MQTT Broker(Eclipse Mosquitto)的部署案例,将抽象概念映射到具体操作中,为后续章节的集群部署与压力测试打下坚实基础。
上图展示了Docker三大核心阶段的关系:构建时通过Dockerfile生成不可变的镜像,运行时由镜像创建出可写的容器实例,而编排时则通过Compose文件管理多个容器的协同工作。这张图将贯穿我们后续所有章节,请务必理解每个阶段对应的命令和产物。
4.1 Docker核心概念:镜像与容器的本质区别
核心原理:分层存储与写时复制
Docker镜像是一个只读的、分层的模板,它包含了运行某个应用所需的完整文件系统——包括代码、运行时、系统工具、库和配置。镜像采用UnionFS(联合文件系统)技术,由多个只读层叠加而成。每一层代表Dockerfile中的一条指令,例如FROM、RUN、COPY等。这种分层设计使得多个镜像可以共享底层数据,极大节省了存储空间。
容器则是镜像的运行实例。当执行docker run时,Docker会在镜像层之上添加一个可写的容器层(Container Layer)。所有对文件系统的写入操作(如修改配置、生成日志)都发生在这一层。容器停止后,这个可写层仍然存在,除非显式删除容器。理解“镜像不可变、容器可变”是Docker操作的第一性原理。
eclipse-mosquitto:2.0镜像可以同时运行三个MQTT Broker容器,分别监听1883、1884、1885端口。
操作流程:镜像与容器的常用命令
# 镜像相关
docker pull eclipse-mosquitto:2.0 # 拉取镜像
docker images # 查看本地镜像列表
docker rmi eclipse-mosquitto:2.0 # 删除镜像(需先删除依赖容器)
docker build -t my-mosquitto:1.0 . # 从Dockerfile构建镜像
# 容器相关
docker run -d --name mqtt-test -p 1883:1883 eclipse-mosquitto:2.0
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(含已停止)
docker exec -it mqtt-test /bin/sh # 进入容器内部
docker logs -f mqtt-test # 跟踪容器日志
docker stop mqtt-test && docker rm mqtt-test # 停止并删除容器
案例拆解:部署第一个Mosquitto容器
假设我们需要在本地快速验证MQTT Broker功能。执行以下命令:
docker run -d --name mqtt-broker -p 1883:1883 -p 9001:9001 eclipse-mosquitto:2.0
这条命令的含义是:以守护进程模式(-d)运行容器,命名为mqtt-broker,将宿主机的1883端口(MQTT协议)和9001端口(WebSocket)映射到容器内。执行后,通过docker ps可以看到容器状态为“Up”。此时,你可以使用MQTT客户端工具(如MQTTX)连接localhost:1883进行消息收发测试。
如果容器启动失败,使用docker logs mqtt-broker查看日志。常见错误是端口被占用,此时需要更换宿主机端口映射,例如-p 1884:1883。
docker attach进入容器主进程,这会导致容器退出。应使用docker exec -it <容器名> /bin/sh进入交互式Shell。另外,删除容器前必须先停止它,否则会报错。
4.2 数据持久化:数据卷(Volume)与绑定挂载
核心原理:容器层的易失性
容器可写层的数据生命周期与容器一致。当容器被删除(docker rm),其中的数据将永久丢失。对于MQTT Broker而言,客户端认证信息、ACL规则、持久化消息等关键数据绝不能存放在容器层。Docker提供了两种持久化方案:数据卷(Volume)和绑定挂载(Bind Mount)。
数据卷由Docker管理,存储在宿主机特定目录(如/var/lib/docker/volumes/),推荐用于存储数据库文件、配置文件等。绑定挂载则将宿主机的任意目录直接映射到容器内,适合开发调试场景,因为你可以直接编辑宿主机文件,容器内立即生效。
| 特性 | 数据卷(Volume) | 绑定挂载(Bind Mount) |
|---|---|---|
| 管理方式 | Docker自动管理 | 用户指定宿主机路径 |
| 适用场景 | 生产环境、数据备份 | 开发调试、配置文件热更新 |
| 权限控制 | 支持只读/读写设置 | 依赖宿主机文件权限 |
| 可移植性 | 高(通过docker run -v或Compose声明) | 低(路径依赖宿主机) |
| 性能 | 略优于绑定挂载 | 直接读写宿主机文件系统 |
操作流程:为Mosquitto配置持久化
# 创建数据卷
docker volume create mosquitto-data
docker volume create mosquitto-config
# 运行容器并挂载卷
docker run -d --name mqtt-broker \
-p 1883:1883 \
-v mosquitto-config:/mosquitto/config \
-v mosquitto-data:/mosquitto/data \
-v mosquitto-log:/mosquitto/log \
eclipse-mosquitto:2.0
这里将配置目录、数据目录、日志目录分别挂载到三个数据卷。即使容器被删除,重新运行相同命令,所有配置和消息数据依然保留。
docker volume inspect mosquitto-data可以查看数据卷在宿主机上的实际路径。
4.3 容器网络:通信模式与端口映射
核心原理:五种网络模式
Docker网络是容器间通信的基础。默认提供五种网络模式:bridge(默认)、host、none、container和overlay(Swarm模式)。对于单机部署MQTT Broker,最常用的是bridge和host。
- bridge模式:每个容器拥有独立IP,通过虚拟网桥(docker0)通信。端口映射通过iptables规则实现,宿主机访问容器需指定映射端口。
- host模式:容器直接使用宿主机网络栈,没有独立IP,端口直接绑定在宿主机上。性能更好,但无法使用端口映射,且存在端口冲突风险。
- none模式:容器无网络,适合离线任务。
操作流程:自定义网络与容器间通信
# 创建自定义bridge网络
docker network create mqtt-net
# 运行两个容器在同一网络
docker run -d --name mqtt-broker1 --network mqtt-net -p 1883:1883 eclipse-mosquitto:2.0
docker run -d --name mqtt-broker2 --network mqtt-net -p 1884:1883 eclipse-mosquitto:2.0
# 容器间通过名称互相访问
docker exec -it mqtt-broker1 ping mqtt-broker2
在自定义网络中,Docker内置DNS服务,容器可以通过名称直接解析到对方IP,无需依赖--link参数。这对于后续搭建MQTT集群(如VerneMQ)至关重要。
4.4 Docker Compose:多容器编排利器
核心原理:声明式服务编排
Docker Compose是一个用于定义和运行多容器应用的工具。通过一个docker-compose.yml文件,你可以声明服务、网络、卷等资源,然后使用docker compose up一键启动所有服务。Compose的核心价值在于可重复性和可读性——所有配置都记录在版本控制的文件中,团队成员可以快速复现相同的环境。
Compose文件使用YAML语法,主要包含三个顶级元素:services(定义各个容器)、networks(定义网络)、volumes(定义数据卷)。每个服务可以指定镜像、端口映射、环境变量、依赖关系等。
操作流程:编写Mosquitto的Compose文件
version: '3.8'
services:
mqtt-broker:
image: eclipse-mosquitto:2.0
container_name: mqtt-broker
ports:
- "1883:1883"
- "9001:9001"
volumes:
- ./mosquitto/config:/mosquitto/config
- mosquitto-data:/mosquitto/data
- mosquitto-log:/mosquitto/log
environment:
- TZ=Asia/Shanghai
restart: unless-stopped
networks:
- mqtt-net
mqtt-client:
image: eclipse-mosquitto:2.0
container_name: mqtt-client
depends_on:
- mqtt-broker
entrypoint: ["/bin/sh", "-c", "sleep 10 && mosquitto_sub -h mqtt-broker -t 'test/topic' -v"]
networks:
- mqtt-net
volumes:
mosquitto-data:
mosquitto-log:
networks:
mqtt-net:
driver: bridge
这个Compose文件定义了两个服务:mqtt-broker作为Broker,mqtt-client作为订阅客户端。执行docker compose up -d后,Compose会自动创建网络和数据卷,并按依赖顺序启动服务。depends_on确保Broker先启动,客户端后启动。
案例拆解:使用Compose进行MQTT消息收发验证
假设我们要验证Broker是否正常工作。在宿主机上执行:
# 启动所有服务
docker compose up -d
# 查看日志,确认Broker启动成功
docker compose logs mqtt-broker
# 在宿主机上发布消息
mosquitto_pub -h localhost -p 1883 -t 'test/topic' -m 'Hello MQTT'
# 查看客户端日志,确认收到消息
docker compose logs mqtt-client
如果一切正常,mqtt-client的日志中会显示test/topic Hello MQTT。这个案例验证了Compose编排的完整流程:服务定义、依赖管理、网络通信、日志查看。
restart: unless-stopped策略非常实用,它确保Broker在宿主机重启或容器崩溃后自动恢复。对于生产环境,建议同时配置healthcheck来监控Broker的健康状态。
4.5 常见误区与复盘
| 误区 | 后果 | 正确做法 |
|---|---|---|
| 在容器内直接修改配置文件 | 容器删除后配置丢失 | 使用数据卷或绑定挂载 |
使用docker attach进入容器 | 导致容器主进程退出 | 使用docker exec -it |
| 忽略端口映射直接访问容器IP | 宿主机无法访问容器服务 | 正确使用-p参数映射端口 |
| Compose文件缩进错误 | YAML解析失败 | 使用空格缩进,禁止使用Tab |
| 多个Broker使用相同端口 | 端口冲突,容器启动失败 | 为每个Broker分配不同宿主机端口 |