工业一体机是专为工业环境设计的集成化计算设备,将主板、处理器、显示屏、触控面板和各类工业接口整合在一体化结构内,具备宽温运行、防尘防振、7×24小时不间断工作的能力。说通俗点,它就是一台"啥都能扛"的工业电脑,专门在车间这种恶劣环境下干活。
买回来之后呢?系统怎么装?应用怎么部署?这才是大头。
今天聊聊在Ubuntu 22.04 LTS上搞Docker容器化部署的那点事。说实话,我第一次在工业一体机上跑Docker的时候踩了不少坑,GPIO权限、串口映射这些跟服务器上跑容器完全不一样。
一、Ubuntu系统初始化
拿到机器先别急着装Docker,系统基础配置得先搞利索。
更新源是最基本的:
```bash
sudo apt update && sudo apt upgrade -y
```
然后装一些常用开发工具,vim、curl、git这些跑不掉。时区设置成上海,不然日志时间对不上,排查问题的时候能把你逼疯。
```bash
sudo apt install -y vim curl git build-essential
sudo timedatectl set-timezone Asia/Shanghai
```
有个事得提醒你,工业一体机的BIOS里最好确认下VT-x或者虚拟化支持有没有开,Docker虽然不用硬件虚拟化,但有些场景你会用到KVM加速,提前开了省事。
二、Docker引擎安装与配置
装Docker这块,官方脚本是最快的:
```bash
curl -fsSL https://get.docker.com | bash
```
装完别急着跑容器。先配daemon.json,把镜像加速和日志限制加上。工业设备存储空间有限,日志不限制的话,跑个把月磁盘就满了。
```json
{
"registry-mirrors": "https://你的加速地址.mirror.aliyuncs.com",
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
```
配完重启Docker服务就生效了。我见过有人不设日志上限,结果采集程序每秒写一堆日志,两周后磁盘爆了,产线数据全丢了。这种低级错误说实话不该犯。
三、GPIO设备在Docker容器中的访问
这块是重点,也是坑最多的地方。
工业一体机上跑采集程序,经常要操作GPIO。但在Docker容器里,默认是访问不了GPIO的。你得用`--device`参数把设备挂载进去。
sysfs方式是最常见的:
```bash
docker run -d \
--device /dev/gpiomem \
-v /sys/class/gpio:/sys/class/gpio \
my-collector:latest
```
但你猜怎么着,光挂载还不够。GPIO操作需要对应的权限,容器里的用户可能没有。你得在Dockerfile里把用户加到gpio用户组,或者直接用`--privileged`跑(不推荐,安全风险太大)。
我个人的做法是建个专门的用户,给gpio和dialout用户组权限,比privileged安全多了。
四、串口设备映射
串口在工控场景太常见了,Modbus、RS485通信都靠它。Docker里映射串口其实不难:
```bash
docker run -d \
--device /dev/ttyS0 \
--device /dev/ttyUSB0 \
my-modbus:latest
```
但有个细节容易忽略------USB串口设备的设备名不固定。你插拔一次,ttyUSB0可能就变成ttyUSB1了。解决办法是写udev规则,给每个USB串口绑定固定名称。
```
/etc/udev/rules.d/99-usb-serial.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="ttyUSB_MODBUS_01"
```
这样不管怎么插拔,`/dev/ttyUSB_MODBUS_01`这个名字永远是固定的。容器里挂载这个固定名称就行。
我见过一个项目,没写udev规则,结果维护人员拔了USB重新插回去,设备名变了,采集程序找不到串口,数据断了一整天才发现。这种事说起来都是泪。
五、Docker Compose编排多容器
实际工控项目很少只跑一个容器。典型的架构是:数据采集 + 数据库 + 可视化前端,三个服务。用Docker Compose编排起来很清晰。
```yaml
version: '3.8'
services:
collector:
build: ./collector
devices:
-
/dev/ttyS0
-
/dev/gpiomem
volumes:
- /sys/class/gpio:/sys/class/gpio
restart: always
depends_on:
- database
database:
image: timescale/timescaledb:latest-pg14
environment:
POSTGRES_PASSWORD: yourpassword
POSTGRES_DB: iot_data
volumes:
- pgdata:/var/lib/postgresql/data
restart: always
dashboard:
image: grafana/grafana:latest
ports:
- "3000:3000"
restart: always
depends_on:
- database
volumes:
pgdata:
```
这套架构我跑了大半年,稳得很。Grafana做可视化看板,TimescaleDB存时序数据,采集容器负责读串口和GPIO。三个容器各司其职,互相不干扰。
有个事要注意,采集容器如果对实时性要求高(比如毫秒级响应),得在compose里设置CPU亲和性,否则容器被调度到不同核心上会有延迟抖动。工业控制场景对延迟敏感的,用`cpus`和`cpu_shares`参数限制一下。
六、容器化的优势和注意事项
优势很明显------环境隔离、部署快、回滚方便。一个容器挂了不影响其他服务,比所有东西跑在一个进程里安全多了。
但也不是没毛病。Docker默认的bridge网络有性能损耗,对网络吞吐敏感的场景建议用host网络模式。还有实时性问题,Docker的cgroup资源限制可能引入调度延迟,硬实时场景老老实实跑裸机。
下面这个表整理了常用的Docker容器配置参数,部署的时候照着配不会出大问题:
| 配置项 | 参数示例 | 说明 | 注意事项 |
|--------|----------|------|----------|
| 设备挂载 | --device /dev/ttyS0 | 将宿主机设备映射到容器 | USB串口需配合udev规则固定名称 |
| GPIO访问 | -v /sys/class/gpio:/sys/class/gpio | sysfs方式访问GPIO引脚 | 需配置用户组权限,非root用户需加入gpio组 |
| 网络模式 | --network host | 容器使用宿主机网络栈 | 牺牲隔离性换取网络性能,适合高吞吐场景 |
| 重启策略 | --restart always | 容器异常退出自动重启 | 工控场景必须配置,保证7×24运行 |
| 资源限制 | --cpus=2 --memory=1g | 限制容器CPU和内存使用 | 避免单个容器耗尽宿主机资源 |
| 日志限制 | log-opts max-size=50m | 限制单个日志文件大小 | 工业设备存储有限,必须限制 |
| 特权模式 | --privileged | 容器获得宿主机全部权限 | 不推荐使用,安全风险高,应改用--device精细化挂载 |
| 时区配置 | -e TZ=Asia/Shanghai | 设置容器内时区 | 不设置会导致日志时间与实际不符 |
常见问题(FAQ)
**Q1:Docker容器里访问GPIO提示权限不足怎么办?**
Docker容器默认以root用户运行,但部分GPIO设备节点需要特定用户组权限。解决方法是在Dockerfile中创建应用用户并将其加入gpio和dialout用户组,运行时通过--user指定用户身份。也可以使用--device-cgroup-rule参数为特定设备添加访问权限。不建议使用--privileged模式,因为它会暴露宿主机所有设备,存在安全隐患。
**Q2:USB串口设备在Docker中设备名经常变化怎么处理?**
USB串口设备热插拔后设备序号可能改变,导致容器内映射失效。正确做法是在宿主机编写udev规则,根据设备的idVendor和idProduct属性创建固定的符号链接名称,然后在容器启动时使用--device挂载该固定名称。这样无论设备插拔顺序如何变化,容器内始终能通过固定路径访问到正确的串口设备。
**Q3:工业控制场景使用Docker会影响实时性吗?**
Docker的cgroup资源调度机制会引入一定的调度延迟,对于秒级或百毫秒级的工控应用影响可忽略。但毫秒级甚至更严格的硬实时控制场景,Docker容器化可能无法满足要求。建议在compose文件中通过cpus、cpu_shares和cpu_quota参数绑定CPU核心,减少调度抖动。对实时性要求极高的场景,采集和控制逻辑应直接运行在宿主机而非容器内。