4、Docker基础回顾:Docker核心概念、镜像、容器、Docker Compose简介

在正式部署MQTT Broker测试容器之前,必须扎实掌握Docker的核心知识体系。本章并非简单的概念罗列,而是围绕“镜像与容器的关系”“数据持久化机制”“容器间通信”以及“Compose编排”四条主线展开,帮助你建立从单个容器到多服务编排的完整认知。我们将通过一个轻量级MQTT Broker(Eclipse Mosquitto)的部署案例,将抽象概念映射到具体操作中,为后续章节的集群部署与压力测试打下坚实基础。

构建时(Build Time) 运行时(Run Time) 编排时(Orchestration) Dockerfile 基础镜像 + 指令 docker build 生成只读模板 镜像(Image) 分层存储 / 不可变 镜像仓库 Docker Hub / 私有仓库 docker run 创建可写容器层 容器(Container) 进程隔离 / 生命周期 数据卷(Volume) 持久化 / 宿主机映射 网络模式 bridge / host / none docker-compose.yml 服务 / 网络 / 卷声明 docker compose up 一键创建多容器 服务编排 依赖管理 / 环境隔离 扩展与日志 scale / logs / down run compose

上图展示了Docker三大核心阶段的关系:构建时通过Dockerfile生成不可变的镜像,运行时由镜像创建出可写的容器实例,而编排时则通过Compose文件管理多个容器的协同工作。这张图将贯穿我们后续所有章节,请务必理解每个阶段对应的命令和产物。

4.1 Docker核心概念:镜像与容器的本质区别

核心原理:分层存储与写时复制

Docker镜像是一个只读的、分层的模板,它包含了运行某个应用所需的完整文件系统——包括代码、运行时、系统工具、库和配置。镜像采用UnionFS(联合文件系统)技术,由多个只读层叠加而成。每一层代表Dockerfile中的一条指令,例如FROM、RUN、COPY等。这种分层设计使得多个镜像可以共享底层数据,极大节省了存储空间。

容器则是镜像的运行实例。当执行docker run时,Docker会在镜像层之上添加一个可写的容器层(Container Layer)。所有对文件系统的写入操作(如修改配置、生成日志)都发生在这一层。容器停止后,这个可写层仍然存在,除非显式删除容器。理解“镜像不可变、容器可变”是Docker操作的第一性原理。

关键对比:镜像相当于“类”(Class),容器相当于“对象”(Object)。你可以从同一个镜像创建多个容器,每个容器拥有独立的可写层,互不干扰。例如,从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)至关重要。

风险提示:使用host模式时,容器内的服务直接监听宿主机端口,如果同时运行多个Broker实例,必须手动修改每个实例的监听端口,否则会因端口冲突而启动失败。

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编排的完整流程:服务定义、依赖管理、网络通信、日志查看。

经验总结:Compose文件中的restart: unless-stopped策略非常实用,它确保Broker在宿主机重启或容器崩溃后自动恢复。对于生产环境,建议同时配置healthcheck来监控Broker的健康状态。

4.5 常见误区与复盘

误区后果正确做法
在容器内直接修改配置文件容器删除后配置丢失使用数据卷或绑定挂载
使用docker attach进入容器导致容器主进程退出使用docker exec -it
忽略端口映射直接访问容器IP宿主机无法访问容器服务正确使用-p参数映射端口
Compose文件缩进错误YAML解析失败使用空格缩进,禁止使用Tab
多个Broker使用相同端口端口冲突,容器启动失败为每个Broker分配不同宿主机端口