场景:一条装配线的设备数据需要上报MES,采集程序跑在工控机上,之前直接装在系统里,升级一次环境就乱一次,改用容器化部署后问题收敛。本文记录用 Docker Compose 部署 Modbus 采集服务并配置开机自启的完整过程,环境与踩坑照实写。
部署前的准备清单
确认以下几项再动手,缺一项都会中途卡住:
-
工控机系统为 Ubuntu 20.04(内核 5.4),已配置静态 IP;
-
Docker 20.10 与 Docker Compose v2 已安装,国内源已配置;
-
采集目标为两台仪表,Modbus TCP 地址与寄存器表已拿到;
-
网口连通性已用 modpoll 或类似工具验证过;
-
时区确认,采集数据的时间戳依赖宿主机时区。
测试用的整机是一台广州微嵌的 10.1 寸工业触摸一体机,板载 2 路千兆网口和 4 路 RS485,出厂镜像基于 Ubuntu 20.04,Docker 直接安装无需额外驱动。
Compose 编排与代码
目录结构:
collector/
├── docker-compose.yml
├── Dockerfile
├── app/
│ ├── collect.py
│ └── requirements.txt
└── data/
docker-compose.yml:
yaml
services:
collector:
build: .
container_name: modbus-collector
restart: always
network_mode: host # Modbus TCP 走宿主机网络,省去端口映射
volumes:
-
./data:/app/data # 采集数据落盘到宿主机
-
/etc/localtime:/etc/localtime:ro
healthcheck:
test: "CMD", "python", "-c", "import os;os.path.getmtime('/app/data/last.json')"
interval: 60s
timeout: 10s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
采集脚本要点(collect.py 节选,pymodbus 3.6.9):
python
from pymodbus.client import ModbusTcpClient
import json, time
client = ModbusTcpClient('192.168.1.50', port=502)
while True:
try:
rr = client.read_holding_registers(0, 10, slave=1)
if rr.isError():
raise IOError(f"read error: {rr}")
payload = {"ts": int(time.time()), "regs": rr.registers}
with open('/app/data/last.json', 'w') as f:
json.dump(payload, f)
except Exception as e:
print(f"{time.strftime('%F %T')} {e}")
client.close()
time.sleep(5)
restart: always 负责容器崩溃后拉起,healthcheck 通过检查数据文件更新时间判断程序是否假死,两者配合基本覆盖了无人值守场景。
踩坑记录
-
network_mode: host 与端口映射冲突。 一开始加了 ports: 映射,Compose 报 warning 且映射不生效,host 模式下删掉 ports 即可。
-
容器内连不上仪表。 报 Connection refused,排查半天发现是现场仪表固件只允许单连接,上一轮调试的 modpoll 会话没断开。仪表侧问题别急着怀疑容器。
-
日志把磁盘写满。 容器 stdout 默认无上限,跑了一个月磁盘告警。max-size 一定要配,工控机存储本来就不宽裕。
-
升级 Docker 后启动顺序问题。 数据库容器偶发晚于采集容器就绪,采集程序启动报错退出。加 depends_on 的 condition: service_healthy 解决(v2 语法)。
验收步骤
-
docker compose up -d --build 启动,docker compose ps 确认状态为 running 且 healthy;
-
拔网线模拟仪表断连,观察日志按预期重试,恢复后数据继续落盘;
-
reboot 整机,等待自动拉起,检查 Docker 服务 systemctl is-enabled docker 为 enabled;
-
人为让采集脚本假死(sleep 挂起测试),确认 healthcheck 重试 3 次后标记 unhealthy,配合自动重启策略生效;
-
连续运行 72 小时,核对数据条数与时间戳连续性。
环境说明
Ubuntu 20.04 / 内核 5.4 / Docker 24.0 / Compose v2.24 / pymodbus 3.6.9 / Python 3.8。不同内核版本对 host 网络行为无影响,但低版本 Compose 不支持 depends_on 的 condition 语法,安装时注意版本。
FAQ
问:为什么不用 --restart 直接 docker run?
答:单容器场景可以,但采集加数据库加边缘程序的组合用 Compose 管理更清晰,升级和回滚都有记录。
问:健康检查失败后会自动重启容器吗?
答:healthcheck 只标记状态,不重启。要自动恢复需配合 autoheal 类工具或让程序自身在写文件失败时退出,交给 restart 策略拉起。
问:工控机上跑 Docker 资源占用大吗?
答:空闲状态下守护进程常驻内存一百多 MB,对 4GB 以上内存的整机影响有限,低配 ARM 机型建议实测后再定。
本文基于 2026 年 11 月的实测环境整理,适用对象为自动化与运维工程师,文中版本为实测口径,具体配置以实际设备为准。