Docker 在嵌入式设备中的常用玩法
目录
1. 基础概念
什么是嵌入式 Docker?
在嵌入式开发中,Docker 主要用于:
- 环境隔离:为不同项目/芯片维护独立的编译环境
- 可复现构建:确保团队成员使用完全一致的工具链
- CI/CD 集成:在服务器上自动化构建嵌入式固件
- 设备端部署:在资源受限的设备上运行容器化应用
Docker 多架构支持
bash
# 注册 QEMU 模拟器(关键步骤,用于构建 ARM 镜像)
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# 验证
docker run --rm arm64v8/ubuntu uname -m
# 输出: aarch64
2. 交叉编译环境
2.1 统一工具链容器
最常见的用法------把交叉编译工具链封装成 Docker 镜像:
dockerfile
# Dockerfile.arm-gcc
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
gcc-arm-none-eabi \
gdb-multiarch \
make \
cmake \
ninja-build \
python3 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /workspace
# 默认进入 bash
CMD ["/bin/bash"]
使用方式:
bash
# 构建镜像
docker build -t arm-gcc -f Dockerfile.arm-gcc .
# 挂载项目目录并编译
docker run --rm -v $(pwd):/workspace arm-gcc \
make CROSS_COMPILE=arm-none-eabi- all
2.2 多工具链切换
dockerfile
# Dockerfile.multi-toolchain
FROM ubuntu:22.04
# ARM Cortex-M (STM32, NRF52 等)
RUN apt-get update && apt-get install -y gcc-arm-none-eabi
# ARM Cortex-A (Linux 应用)
RUN apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# RISC-V
RUN apt-get install -y gcc-riscv64-unknown-elf
# MIPS
RUN apt-get install -y gcc-mips-linux-gnu
RUN rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
2.3 Yocto / Buildroot 环境
dockerfile
# Dockerfile.yocto
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
gawk wget git diffstat unzip texinfo gcc build-essential \
chrpath socat cpio python3 python3-pip python3-pexpect \
python3-git python3-jinja2 python3-subunit \
xz-utils debianutils iputils-ping \
libsdl1.2-dev xterm \
locales \
&& rm -rf /var/lib/apt/lists/*
RUN locale-gen en_US.UTF-8
ENV LANG=en_US.UTF-8
RUN useradd -m builder
USER builder
WORKDIR /home/builder
# 克隆 Yocto
RUN git clone -b kirkstone \
https://git.yoctoproject.org/poky.git /home/builder/poky
3. 固件构建与打包
3.1 可复现固件构建
dockerfile
# Dockerfile.firmware-build
FROM ubuntu:22.04
# 固定版本的工具链
RUN apt-get update && apt-get install -y \
gcc-arm-none-eabi=15:10.3-2021.10-* \
make cmake \
&& rm -rf /var/lib/apt/lists/*
# 固定版本的构建脚本
COPY build.sh /opt/build.sh
RUN chmod +x /opt/build.sh
WORKDIR /firmware
ENTRYPOINT ["/opt/build.sh"]
bash
# 构建固件
docker run --rm -v $(pwd):/firmware firmware-build:v1.0
# 产出物自动落在当前目录
ls -la build/*.bin
3.2 OpenWrt 构建
dockerfile
# Dockerfile.openwrt
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
build-essential ccache ecj fastjar file g++ gawk \
gettext git java-propose-class-default libelf-dev \
libncurses5-dev libncursesw5-dev libssl-dev \
python3 python2.7-dev python3-distutils python3-setuptools \
rsync subversion swig time unzip wget xsltproc zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/openwrt/openwrt.git /opt/openwrt
WORKDIR /opt/openwrt
RUN ./scripts/feeds update -a && ./scripts/feeds install -a
4. CI/CD 流水线
4.1 GitHub Actions 集成
yaml
# .github/workflows/firmware.yml
name: Build Firmware
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/yourorg/arm-gcc:latest
steps:
- uses: actions/checkout@v4
- name: Build
run: make CROSS_COMPILE=arm-none-eabi- all
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: firmware
path: build/*.bin
4.2 GitLab CI 集成
yaml
# .gitlab-ci.yml
stages:
- build
- test
- deploy
build:
stage: build
image: yourregistry/arm-gcc:latest
script:
- make CROSS_COMPILE=arm-none-eabi- all
artifacts:
paths:
- build/*.bin
- build/*.elf
4.3 Jenkins Pipeline
groovy
pipeline {
agent {
docker {
image 'yourregistry/arm-gcc:latest'
}
}
stages {
stage('Build') {
steps {
sh 'make CROSS_COMPILE=arm-none-eabi- all'
}
}
stage('Flash') {
steps {
sh 'openocd -f board/stm32f4discovery.cfg -c "program build/firmware.elf verify reset exit"'
}
}
}
}
5. 设备端容器化运行
5.1 轻量级运行时选择
| 运行时 | 大小 | 适用场景 |
|---|---|---|
| Docker (moby) | ~100MB+ | 资源充足的边缘网关 |
| Podman | ~80MB | 无 daemon,更安全 |
| containerd | ~30MB | 轻量级,K8s 生态 |
| CRI-O | ~25MB | Kubernetes 专用 |
| LXC/LXD | ~10MB | 系统级容器 |
| runc | ~5MB | 最小化容器运行时 |
5.2 balenaOS (原 resinOS)
专为嵌入式/IoT 设计的容器化操作系统:
bash
# balena 设备上的应用结构
# 项目根目录
├── docker-compose.yml
├── Dockerfile.template
└── src/
└── main.py
yaml
# docker-compose.yml (balena)
version: '2'
services:
sensor-reader:
build: .
privileged: true
restart: always
labels:
io.balena.features.kernel-modules: '1'
data-processor:
image: myregistry/processor:armv7hf
depends_on:
- sensor-reader
5.3 Torizon (NXP 平台)
bash
# TorizonCore 上部署应用
torizoncore-builder bundle docker-compose.yml
# 推送到设备
torizoncore-builder images unpack torizon-core-docker-apalis-imx6-Tezi_*.tar
torizoncore-builder deploy --remote-host 192.168.1.100 --remote-username torizon
5.4 设备端 Compose 示例
yaml
# docker-compose.yml (边缘网关)
version: '3.8'
services:
# MQTT Broker
mosquitto:
image: eclipse-mosquitto:2-openssl
ports:
- "1883:1883"
volumes:
- ./mosquitto/config:/mosquitto/config
- mosquitto-data:/mosquitto/data
restart: always
# 数据采集
collector:
build: ./collector
privileged: true
devices:
- /dev/i2c-1:/dev/i2c-1
- /dev/spidev0.0:/dev/spidev0.0
environment:
- MQTT_BROKER=mosquitto
depends_on:
- mosquitto
restart: always
# 边缘推理
inference:
image: myregistry/edge-ai:arm64
devices:
- /dev/accelerator:/dev/accelerator # NPU/AI 加速器
volumes:
- ./models:/models:ro
restart: always
# Web 管理界面
webui:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./webui:/usr/share/nginx/html:ro
restart: always
volumes:
mosquitto-data:
6. 模拟与测试
6.1 QEMU 模拟嵌入式设备
bash
# 运行 ARM 虚拟机
docker run --rm -it \
--platform linux/arm64 \
multiarch/ubuntu-core:arm64-focal \
bash
# 在容器内模拟 RISC-V
docker run --rm -it riscv64/ubuntu:22.04 bash
6.2 硬件在环测试 (HIL)
dockerfile
# Dockerfile.hil-test
FROM python:3.11-slim
RUN pip install \
pyserial \
pytest \
pytest-timeout \
pyocd
COPY tests/ /tests/
WORKDIR /tests
CMD ["pytest", "--tb=short", "-v"]
bash
# 需要访问 USB 设备(调试器)
docker run --rm \
--privileged \
-v /dev/bus/usb:/dev/bus/usb \
-v $(pwd):/tests \
hil-test
6.3 网络模拟
yaml
# docker-compose.network-sim.yml
version: '3.8'
services:
device-1:
build: ./device
networks:
iot-net:
ipv4_address: 10.0.0.10
device-2:
build: ./device
networks:
iot-net:
ipv4_address: 10.0.0.11
# 模拟网关
gateway:
build: ./gateway
ports:
- "8080:8080"
networks:
iot-net:
ipv4_address: 10.0.0.1
wan-net:
# 模拟云端
cloud:
image: nginx:alpine
networks:
wan-net:
networks:
iot-net:
driver: bridge
ipam:
config:
- subnet: 10.0.0.0/24
wan-net:
driver: bridge
7. 常用工具与镜像
7.1 官方 & 社区镜像
| 镜像 | 用途 |
|---|---|
arm32v7/ubuntu |
ARM 32位 Ubuntu |
arm64v8/debian |
ARM 64位 Debian |
balenalib/%%BALENA_MACHINE_NAME%%-ubuntu |
balena 设备基础镜像 |
torizon/debian |
Torizon 基础镜像 |
eclipse-mosquitto |
MQTT Broker |
homeassistant/home-assistant |
智能家居 |
nodered/node-red |
Node-RED 可视化编程 |
7.2 开发工具镜像
bash
# GDB 调试
docker run --rm -it \
--privileged \
-v /dev/bus/usb:/dev/bus/usb \
arm-gcc \
gdb-multiarch -ex "target remote :3333" build/firmware.elf
# Wireshark 抓包分析
docker run --rm -it \
--net=host \
--privileged \
linuxserver/wireshark
# Serial 串口调试
docker run --rm -it \
--device=/dev/ttyUSB0 \
alpine \
picocom -b 115200 /dev/ttyUSB0
8. 实战示例
8.1 完整的 STM32 项目
dockerfile
# Dockerfile.stm32
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
gcc-arm-none-eabi \
libnewlib-arm-none-eabi \
cmake \
make \
git \
python3 \
python3-pip \
stlink-tools \
openocd \
&& rm -rf /var/lib/apt/lists/*
# 安装 STM32CubeMX CLI (可选)
# RUN pip3 install stm32cubemx
WORKDIR /project
# 构建 & 烧录脚本
COPY scripts/build.sh scripts/flash.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/build.sh /usr/local/bin/flash.sh
ENTRYPOINT ["/bin/bash"]
bash
# build.sh
#!/bin/bash
set -e
mkdir -p build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-none-eabi.cmake ..
make -j$(nproc)
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
echo "Build complete: build/firmware.bin ($(wc -c < firmware.bin) bytes)"
bash
# 使用
docker run --rm -v $(pwd):/project stm32-dev build.sh
# 烧录(需要 USB 设备访问)
docker run --rm \
--privileged \
-v /dev/bus/usb:/dev/bus/usb \
-v $(pwd):/project \
stm32-dev flash.sh
8.2 Raspberry Pi 应用开发
dockerfile
# Dockerfile.rpi
FROM arm64v8/python:3.11-slim
RUN apt-get update && apt-get install -y \
libgpiod2 \
i2c-tools \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python3", "main.py"]
bash
# 在 x86 主机上构建 ARM 镜像
docker buildx build --platform linux/arm64 -t my-rpi-app -f Dockerfile.rpi .
# 推送到树莓派
docker save my-rpi-app | ssh pi@raspberrypi 'docker load'
# 树莓派上运行
docker run --rm \
--device /dev/i2c-1 \
--device /dev/gpiochip0 \
my-rpi-app
8.3 多服务 IoT 系统
yaml
# docker-compose.iot.yml
version: '3.8'
services:
# 数据采集
sensor-hub:
build: ./sensor-hub
privileged: true
environment:
- SENSOR_INTERVAL=5
restart: always
# 本地数据库
influxdb:
image: influxdb:1.8
volumes:
- influxdb-data:/var/lib/influxdb
ports:
- "8086:8086"
restart: always
# 数据可视化
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
depends_on:
- influxdb
restart: always
# 规则引擎
nodered:
image: nodered/node-red:latest
ports:
- "1880:1880"
volumes:
- nodered-data:/data
restart: always
# 反向代理
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- grafana
- nodered
restart: always
volumes:
influxdb-data:
grafana-data:
nodered-data:
9. 注意事项与最佳实践
9.1 资源受限设备的优化
dockerfile
# 使用 Alpine 减小体积
FROM arm64v8/alpine:3.18
# 多阶段构建
FROM arm64v8/gcc:12 AS builder
COPY . /src
WORKDIR /src
RUN gcc -O2 -o app main.c
FROM arm64v8/alpine:3.18
COPY --from=builder /src/app /usr/local/bin/
CMD ["app"]
# 最终镜像可能只有 10MB
9.2 安全注意事项
yaml
# 安全最佳实践
services:
secure-app:
image: my-app
# 1. 不要使用 privileged,尽量指定具体设备
devices:
- /dev/i2c-1:/dev/i2c-1
# 2. 只读文件系统
read_only: true
tmpfs:
- /tmp
# 3. 非 root 用户
user: "1000:1000"
# 4. 限制资源
deploy:
resources:
limits:
memory: 256M
cpus: '0.5'
# 5. Drop 不必要的 capabilities
cap_drop:
- ALL
cap_add:
- SYS_RAWIO # 仅在需要时
9.3 存储优化
bash
# 使用 tmpfs 避免写入闪存(延长存储寿命)
docker run --rm \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--tmpfs /var/log:rw,noexec,nosuid,size=32m \
my-app
# 使用 overlay2 存储驱动
# /etc/docker/daemon.json
{
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
]
}
9.4 网络配置
bash
# 使用 host 网络模式(减少开销)
docker run --rm --net=host my-iot-app
# 或使用 macvlan(设备看起来像独立主机)
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
macvlan-net
docker run --rm --net=macvlan-net --ip=192.168.1.100 my-app
9.5 常见陷阱
| 陷阱 | 解决方案 |
|---|---|
| 镜像太大 | 多阶段构建 + Alpine 基础镜像 |
| 编译太慢 | 使用 BuildKit 缓存 + ccache |
| USB 设备访问 | --privileged 或 --device |
| GPIO 访问 | --privileged + --device /dev/gpiochip0 |
| 时钟不同步 | --cap-add SYS_TIME 或挂载宿主机时钟 |
| 日志占满磁盘 | 配置日志轮转 --log-opt max-size=10m |
| 跨架构构建慢 | 使用 docker buildx + 远程 builder |
附录:快速命令参考
bash
# 构建 ARM 镜像
docker buildx build --platform linux/arm64 -t my-app:arm64 .
# 导出镜像到离线设备
docker save my-app:arm64 | gzip > my-app-arm64.tar.gz
ssh target "gunzip -c my-app-arm64.tar.gz | docker load"
# 查看容器资源使用
docker stats
# 清理无用资源
docker system prune -a
# 导出容器文件系统
docker export my-container > rootfs.tar
# 导入为镜像
cat rootfs.tar | docker import - my-image:latest