3. 开发环境搭建:使用Docker和DevContainer搭建可复现的ROS2 Humble开发环境

说实话,我见过太多团队在环境配置上栽跟头了。

「在我机器上能跑啊」——这句话,几乎是每个机器人工程师的噩梦。我自己就经历过:项目做到一半,同事换了台电脑,结果依赖库版本对不上,整整浪费了两天去排查。从那以后,我下定决心,必须用容器化方案来统一开发环境。

今天我们就来聊聊,怎么用Docker和DevContainer,搭建一个可复现的ROS2 Humble开发环境。

为什么非要用容器?

你想想看,ROS2的依赖有多复杂?Python包、C++库、系统工具链、还有各种硬件驱动。每个人装的方式不一样,系统版本不一样,迟早出问题。

容器化开发的好处很明显:

  • 环境一致:你、我、他,拉下来跑起来,一模一样
  • 快速重建:搞坏了?删了重来,三分钟搞定
  • 版本控制:Dockerfile写进Git,环境变更可追溯
  • 隔离干净:不污染宿主机,一个项目一个容器

核心思路:把开发环境当作代码来管理。Dockerfile就是环境的「源代码」,镜像就是「编译产物」。

方案一:纯Docker镜像

这是最直接的方式。我早期做项目时就用这个,简单粗暴。

编写Dockerfile

先看一个基础版:

FROM ros:humble-ros-base-jammy

# 安装常用工具
RUN apt-get update && apt-get install -y \
    vim \
    git \
    curl \
    wget \
    python3-pip \
    python3-colcon-common-extensions \
    && rm -rf /var/lib/apt/lists/*

# 设置工作目录
WORKDIR /workspace

# 设置环境变量
ENV ROS_DOMAIN_ID=42

# 默认命令
CMD ["/bin/bash"]

嗯,这里要注意一点:rm -rf /var/lib/apt/lists/* 这行不能省。我见过有人没加,结果镜像体积直接翻倍。Docker镜像每层都是增量存储,apt缓存不清掉,会一直留在里面。

构建与运行

# 构建镜像
docker build -t ros2-humble-dev .

# 运行容器(带GUI支持)
docker run -it --rm \
    --name ros2_dev \
    --network host \
    --env DISPLAY=$DISPLAY \
    --volume /tmp/.X11-unix:/tmp/.X11-unix:rw \
    --volume $(pwd):/workspace \
    ros2-humble-dev

小技巧:如果你需要可视化(比如rviz2),记得加上 --network host 和X11转发。我刚开始忘了加,rviz2死活打不开,折腾了半天才发现是网络模式的问题。

方案二:DevContainer(推荐)

说实话,纯Docker方案有个痛点:每次都要手动敲docker run命令,参数一长串,很容易漏。而且VS Code的远程开发插件没法直接对接。

DevContainer就是来解决这个问题的。它是VS Code的一个扩展,让你直接在容器里写代码、调试、运行终端。

项目结构

我习惯在项目根目录下建一个 .devcontainer 文件夹:

my_ros2_project/
├── .devcontainer/
│   ├── devcontainer.json
│   └── Dockerfile
├── src/
│   └── ...
├── .gitignore
└── README.md

配置devcontainer.json

{
    "name": "ROS2 Humble Dev",
    "build": {
        "dockerfile": "Dockerfile",
        "context": ".."
    },
    "runArgs": [
        "--network=host",
        "--env=DISPLAY=${env:DISPLAY}",
        "--volume=/tmp/.X11-unix:/tmp/.X11-unix:rw"
    ],
    "mounts": [
        "source=${localWorkspaceFolder}/src,target=/workspace/src,type=bind"
    ],
    "customizations": {
        "vscode": {
            "extensions": [
                "ms-vscode.cpptools",
                "ms-python.python",
                "twxs.cmake",
                "ms-iot.vscode-ros"
            ],
            "settings": {
                "terminal.integrated.defaultProfile.linux": "bash",
                "cmake.configureOnOpen": true
            }
        }
    },
    "postCreateCommand": "rosdep update && colcon build"
}

这里有个坑,我踩过:postCreateCommand 里如果直接跑 colcon build,第一次构建会很久。建议先只装依赖,等容器启动后再手动构建。或者把构建命令拆成两步。

Dockerfile(增强版)

FROM ros:humble-ros-base-jammy

# 安装开发工具
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    git \
    python3-colcon-common-extensions \
    python3-rosdep \
    python3-vcstool \
    && rm -rf /var/lib/apt/lists/*

# 初始化rosdep
RUN rosdep init && rosdep update

# 安装常用ROS2包
RUN apt-get update && apt-get install -y \
    ros-humble-rviz2 \
    ros-humble-gazebo-ros-pkgs \
    ros-humble-nav2-* \
    && rm -rf /var/lib/apt/lists/*

# 创建非root用户(安全考虑)
RUN useradd -m -s /bin/bash rosuser \
    && echo "rosuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
USER rosuser

WORKDIR /workspace

注意:生产环境中,我强烈建议创建一个非root用户。虽然开发时用root方便,但一旦容器要部署到生产环境,root权限就是个安全隐患。我曾经见过一个团队,因为容器里一直用root,结果CI/CD流水线被攻破,整个镜像仓库都被污染了。

方案三:多阶段构建(进阶)

如果你的项目比较大,或者需要交叉编译,多阶段构建是个好选择。

# 第一阶段:构建阶段
FROM ros:humble-ros-base-jammy AS builder

COPY src/ /workspace/src
WORKDIR /workspace

RUN rosdep install --from-paths src --ignore-src -r -y \
    && colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release

# 第二阶段:运行阶段
FROM ros:humble-ros-base-jammy

COPY --from=builder /workspace/install /workspace/install
COPY --from=builder /workspace/src /workspace/src

WORKDIR /workspace

CMD ["ros2", "launch", "my_project", "start.launch.py"]

这样做的好处是:最终镜像只保留运行所需的文件,构建工具链全部丢弃。镜像体积能缩小60%以上。我有个项目,原本镜像1.2GB,用多阶段构建后只剩400MB。

避坑指南

这些年用Docker做ROS2开发,我总结了几条经验:

  • 网络问题:国内拉取ros镜像慢?用镜像加速器。我一般用阿里云的,速度还不错。
  • 权限问题:容器里创建的文件,宿主机上可能属于root。解决方案是在docker run时指定用户ID:--user $(id -u):$(id -g)
  • GPU支持:如果你要用CUDA,记得加 --gpus all 参数,并且用nvidia/cuda作为基础镜像。
  • 时间同步:容器时间默认是UTC,和宿主机不一致。加 --volume /etc/localtime:/etc/localtime:ro 解决。

我的习惯:每个项目都配一个 Makefile,把常用的docker命令封装起来。比如 make buildmake runmake clean。这样团队里任何人,只要敲几个字母就能开始干活。

验证环境

搭建完了,怎么确认环境没问题?我一般跑几个测试:

# 检查ROS2版本
ros2 --version

# 测试话题通信
ros2 run demo_nodes_cpp talker &
ros2 run demo_nodes_cpp listener

# 检查colcon
colcon --version

# 检查Python
python3 -c "import rclpy; print('rclpy OK')"

如果这些都能跑通,恭喜你,环境搭建成功了。

最后说一句:环境搭建看似基础,但它是整个项目的基石。花点时间把这一步做好,后面能省下无数麻烦。我个人习惯,每个新项目都会先花半天时间把DevContainer配好,然后把这个配置提交到Git仓库。这样不管谁来接手,都能立刻进入开发状态。

下一章,我们会在这个环境里,开始写第一个ROS2节点。准备好了吗?