为什么用Docker部署物联网系统

物联网项目的部署环境五花八门:客户机房的服务器、工控机、树莓派、云服务器。每个环境的操作系统版本、依赖库版本都不一样。

Docker解决的就是"在我电脑上能跑"的问题。

把你的物联网平台、MQTT Broker、数据库、管理后端全部容器化,任何装了Docker的机器上docker-compose up一把启动,不用管底层的OS差异。

这在实际项目中意味着什么?你在公司开发机上调试好的系统,部署到客户机房时不用再花两天时间装环境、排依赖。镜像拉下来,配置文件改一改,跑起来。

整体架构

一个完整的物联网设备管理系统,Docker化后的架构:

docker-compose.yml
├── emqx          # MQTT Broker
├── mysql         # 业务数据库
├── redis         # 缓存
├── go-worker     # MQTT消息处理微服务
├── php-api       # 管理API + Web界面
└── nginx         # 反向代理

所有组件跑在同一台服务器上,通过Docker内部网络通信。

完整docker-compose.yml

version: '3.8'

services:
  mysql:
    image: mysql:8.0
    container_name: iot_mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: iot_platform
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - iot_net

  redis:
    image: redis:7-alpine
    container_name: iot_redis
    restart: always
    command: redis-server --requirepass ${REDIS_PASSWORD}
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - iot_net

  emqx:
    image: emqx/emqx:5.8
    container_name: iot_emqx
    restart: always
    environment:
      EMQX_NAME: iot_emqx
      EMQX_HOST: 127.0.0.1
      EMQX_DASHBOARD__DEFAULT_PASSWORD: ${EMQX_DASHBOARD_PASSWORD}
    ports:
      - "1883:1883"     # MQTT TCP
      - "8083:8083"     # WebSocket
      - "18083:18083"   # Dashboard
    volumes:
      - emqx_data:/opt/emqx/data
    networks:
      - iot_net

  go-worker:
    build: ./go-worker
    container_name: iot_worker
    restart: always
    depends_on:
      - emqx
      - mysql
      - redis
    environment:
      MQTT_BROKER: emqx:1883
      MYSQL_DSN: root:${MYSQL_ROOT_PASSWORD}@tcp(mysql:3306)/iot_platform
      REDIS_ADDR: redis:6379
      REDIS_PASSWORD: ${REDIS_PASSWORD}
    networks:
      - iot_net

  php-api:
    build: ./php-api
    container_name: iot_api
    restart: always
    depends_on:
      - mysql
      - redis
    environment:
      DB_HOST: mysql
      DB_DATABASE: iot_platform
      DB_USERNAME: root
      DB_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      REDIS_HOST: redis
      REDIS_PASSWORD: ${REDIS_PASSWORD}
    volumes:
      - ./php-api/src:/var/www/html
    networks:
      - iot_net

  nginx:
    image: nginx:alpine
    container_name: iot_nginx
    restart: always
    depends_on:
      - php-api
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./php-api/src:/var/www/html
    networks:
      - iot_net

volumes:
  mysql_data:
  redis_data:
  emqx_data:

networks:
  iot_net:
    driver: bridge

.env配置

MYSQL_ROOT_PASSWORD=your_secure_password_here
REDIS_PASSWORD=your_redis_password_here
EMQX_DASHBOARD_PASSWORD=your_emqx_password_here

Go Worker关键代码

Go Worker负责订阅MQTT消息,处理设备上报数据并写入数据库:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"

    mqtt "github.com/eclipse/paho.mqtt.golang"
    "github.com/joho/godotenv"
    _ "github.com/go-sql-driver/mysql"
    "github.com/gomodule/redigo/redis"
    "database/sql"
)

type Telemetry struct {
    DeviceID string  `json:"device_id"`
    Temp     float64 `json:"temp"`
    Hum      float64 `json:"hum"`
    Voltage  float64 `json:"voltage"`
}

var db *sql.DB
var redisConn redis.Conn

func main() {
    godotenv.Load()
    
    // 初始化MySQL
    dsn := os.Getenv("MYSQL_DSN")
    db, _ = sql.Open("mysql", dsn)
    defer db.Close()

    // 初始化Redis
    redisAddr := os.Getenv("REDIS_ADDR")
    redisConn, _ = redis.Dial("tcp", redisAddr)
    defer redisConn.Close()

    // 连接MQTT
    opts := mqtt.NewClientOptions()
    opts.AddBroker(os.Getenv("MQTT_BROKER"))
    opts.SetClientID("go-worker-001")
    opts.OnConnect = func(c mqtt.Client) {
        c.Subscribe("devices/+/+/telemetry", 1, handleMessage)
        log.Println("Subscribed to telemetry topic")
    }

    client := mqtt.NewClient(opts)
    if token := client.Connect(); token.Wait() && token.Error() != nil {
        log.Fatal(token.Error())
    }

    // 等待退出信号
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
    <-sig
}

func handleMessage(client mqtt.Client, msg mqtt.Message) {
    var t Telemetry
    if err := json.Unmarshal(msg.Payload(), &t); err != nil {
        return
    }

    // 写入MySQL
    _, err := db.Exec(
        "INSERT INTO device_telemetry (device_id, temp, hum, voltage, created_at) VALUES (?, ?, ?, ?, NOW())",
        t.DeviceID, t.Temp, t.Hum, t.Voltage,
    )
    if err != nil {
        log.Printf("MySQL insert error: %v", err)
    }

    // 更新Redis缓存(最新状态)
    redisConn.Do("HSET", "device:"+t.DeviceID, "temp", t.Temp, "hum", t.Hum, "voltage", t.Voltage)
    redisConn.Do("HSET", "device:"+t.DeviceID, "last_seen", "NOW()")

    // 告警判断
    if t.Temp > 80 {
        redisConn.Do("LPUSH", "alerts", fmt.Sprintf(`{"device":"%s","type":"high_temp","value":%.1f}`, t.DeviceID, t.Temp))
    }
}

Nginx配置

server {
    listen 80;
    server_name _;

    root /var/www/html/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass php-api:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # WebSocket代理(设备实时通信)
    location /ws {
        proxy_pass http://emqx:8083;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

部署步骤

# 1. 克隆代码
git clone https://github.com/your-repo/iot-platform.git
cd iot-platform

# 2. 配置环境变量
cp .env.example .env
nano .env  # 修改密码

# 3. 启动所有服务
docker-compose up -d

# 4. 初始化数据库
docker exec -it iot_mysql mysql -uroot -p iot_platform < sql/init.sql

# 5. 检查服务状态
docker-compose ps
docker-compose logs -f

整个过程5分钟。

性能与资源占用

在一台4核8G的云服务器上实测:

服务内存占用CPU占用
MySQL~500MB<5%
Redis~30MB<2%
EMQX~200MB<10%
Go Worker~50MB<5%
PHP-FPM~100MB<5%
Nginx~20MB<1%
总计~900MB<30%

500台设备每5秒上报一次数据,系统负载轻松hold住。

实际踩坑

坑1:MySQL时区

Docker MySQL默认UTC,但业务需要东八区。在docker-compose.yml中加:

environment:
  TZ: Asia/Shanghai

或者直接在MySQL配置文件中设置default-time-zone='+08:00'

坑2:EMQX WebSocket跨域

前端通过WebSocket连接EMQX时可能遇到CORS问题。在EMQX配置中:

emqx:
  environment:
    EMQX_LISTENER__WS__DEFAULT__ACCESS_CONTROL__ALLOW_ORIGINS: "*"

坑3:Docker日志撑爆磁盘

容器默认不限制日志大小。加上:

logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

总结

Docker化部署物联网系统,核心好处是环境一致性和快速交付。你在本地调好的系统,到客户现场5分钟部署完,不需要在现场折腾环境问题。

虎王科技的设备管理平台就是基于这套架构,目前稳定运行中。Docker Compose文件和部分代码在GitHub上:github.com/huwangkeji

有问题欢迎评论区交流。


沧州虎王科技,专注物联网软硬件开发、通信设备、嵌入式系统与设备管理平台。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐