ROS(docker)

一、什么是ROS

ROS(Robot Operating System)是一个开源的机器人软件框架,它不是传统意义上的操作系统,而是一套运行在Linux之上的中间件和工具集。ROS为机器人软件开发提供了统一的通信机制、包管理、构建系统和调试工具。
目前ROS有两个主要版本:ROS1(如 Noetic、Melodic、Kinetic)和ROS2(如 Humble、Iron、Jazzy)。本文以 ROS Noetic(ROS1的最后一个LTS版本)为基础进行讲解,但核心概念在 ROS 2 中同样适用。

1.1 ROS 核心概念

ROS的设计哲学是基于分布式架构,系统由多个独立进程(节点)组成,节点之间通过消息传递进行通信。理解 ROS 的关键是理解以下几个核心概念:

概念说明类比
节点(Node)一个独立运行的进程,负责特定功能一个工人
话题(Topic)节点间异步通信的通道,发布/订阅模式广播频道
消息(Message)在话题上传输的数据结构广播的内容
发布(Publish)节点向话题发送消息播音
订阅(Subscribe)节点从话题接收消息收听
服务(Service)节点间同步通信,请求/响应模式电话呼叫
Master节点注册和查找的中间人电话交换机
Launch文件XML 格式的配置文件,批量启动多个节点启动脚本
PackageROS 软件的基本组织单位一个项目/模块
TF坐标变换系统,管理各坐标系之间的关系地图上的坐标网格

1.2 ROS 文件系统

ROS项目通常遵循以下目录结构:

├── src/                     # 源代码目录
│   └── my_package/          # 一个ROS包
│       ├── CMakeLists.txt   # 构建配置
│       ├── package.xml      # 包描述
│       ├── src/             # C++ 源文件
│       ├── include/         # 头文件
│       ├── scripts/         # Python 脚本
│       ├── launch/          # Launch 文件
│       └── config/          # 配置文件
├── build/                   # 编译中间产物
├── devel/                   # 编译产物(可执行文件、库)
└── install/                 # 安装目录

编译安装的通常在/opt/里面,也就是install目录文件夹。内部通常是:

1.3 ROS 通信机制详解

ROS 支持两种通信方式:话题(Topic)和服务(Service)。

话题是异步的、多对多的通信机制。一个节点可以发布消息到某个话题,其他节点可以订阅这个话题接收消息。话题使用发布/订阅模型,发布者(Publisher)和订阅者(Subscriber)之间是解耦的。发布者不需要知道谁在接收,订阅者也不需要知道谁在发送。

# 话题通信示例(Python)
# 发布者
import rospy
from std_msgs.msg import String
pub = rospy.Publisher('my_topic', String, queue_size=10)
pub.publish("Hello ROS!")

# 订阅者
def callback(msg):
    rospy.loginfo("Received: %s", msg.data)
rospy.Subscriber('my_topic', String, callback)

服务是同步的、一对一的通信机制。客户端(Client)发送请求,服务端(Server)处理后返回响应。适用于需要等待结果的场景,比如查询机器人状态、执行一次性的动作。

# 服务通信示例(Python)
# 服务端
from my_package.srv import AddTwoInts, AddTwoIntsResponse
def handle_add(req):
    return AddTwoIntsResponse(req.a + req.b)
rospy.Service('add_two_ints', AddTwoInts, handle_add)


# 客户端
rospy.wait_for_service('add_two_ints')
add = rospy.ServiceProxy('add_two_ints', AddTwoInts)
result = add(3, 5)

1.4 Launch 文件

Launch 文件是 ROS 中批量启动节点和配置参数的核心工具。使用XML格式编写,可以一次性启动多个节点、设置参数、包含其他Launch文件。

<!-- 示例:启动两个节点 -->
<launch>
    <!-- 设置参数 -->
    <arg name="rviz" default="true"/>

    <!-- 启动 FAST-LIO -->
    <node pkg="fast_lio" type="fastlio_mapping"
          name="laserMapping" output="screen">
        <param name="common/lid_topic" value="/livox/lidar"/>
        <param name="common/imu_topic" value="/livox/imu"/>
        <param name="pcd_save/pcd_save_en" value="true"/>
    </node>

    <!-- 启动 RViz 可视化 -->
    <node if="$(arg rviz)" pkg="rviz" type="rviz"
          name="rviz" args="-d $(find fast_lio)/rviz_cfg/loam.rviz"/>
</launch>

Launch 文件的关键语法:

  • <launch>:根元素,所有其他元素都包含在其中
  • <node>:启动一个节点,指定包名(pkg)、可执行文件(type)、节点名(name)
  • <param>:设置参数,会加载到ROS参数服务器
  • <arg>:定义变量,可以在运行时通过命令行传入
  • <include>:包含另一个Launch文件
  • <group>:对一组节点进行操作,可以设置命名空间

1.5 TF 坐标变换系统

TF(Transform)是ROS中管理坐标变换的核心系统。在机器人系统中,不同传感器、不同部件都有自己的坐标系,TF负责维护这些坐标系之间的变换关系。

例如,一个典型的移动机器人可能包含以下坐标系:

  • map:全局地图坐标系,固定不动
  • odom:里程计坐标系,以机器人启动位置为原点
  • base_link:机器人本体坐标系,固定在机器人上
  • lidar:激光雷达坐标系,相对于 base_link 有固定偏移
  • imu:IMU 坐标系,相对于 base_link 有固定偏移

TF树是一个有向树结构,每个坐标系只有一个父节点。通过TF可以方便地将一个坐标系下的点转换到另一个坐标系下。

二、ROS的Docker基础

Docker是一个开源的容器化平台,它可以将应用程序及其依赖打包到一个轻量级、可移植的容器中。相比虚拟机,Docker容器共享宿主机的内核,启动更快、资源占用更少。

2.1 Docker核心概念

概念说明类比
镜像(Image)只读的模板,包含运行应用所需的一切光盘/ISO文件
容器(Container)镜像的运行实例运行中的程序
Dockerfile定义如何构建镜像的脚本菜谱
仓库(Registry)存储和分发镜像的服务应用商店
挂载(Volume)将宿主机目录映射到容器内部共享文件夹
端口映射将容器端口映射到宿主机端口端口转发

2.2 Docker基础命令

以下是Docker最常用的命令:

docker pull ubuntu:20.04 # 拉取镜像

docker images  # 列出本地镜像

docker rmi <image_id> # 删除镜像

docker save -o my_image.tar <image>   # 导出镜像为tar文件

docker load -i my_image.tar    # 从tar文件导入镜像

docker run -it ubuntu:20.04 /bin/bash   # 启动交互式容器

docker ps  # 列出运行中的容器

docker ps -a  # 列出所有容器(含已停止)

docker stop <container> # 停止容器

docker rm <container>  # 删除容器

docker exec -it <container> bash  # 进入运行中的容器

docker run -v /host/path:/container/path <image>  # 目录挂载

docker run -v my_volume:/container/path <image>   # 命名卷挂载

docker run -p 8080:80 <image>    # 将容器80端口映射到宿主机8080

2.3 Dockerfile编写

Dockerfile是构建镜像的脚本文件,定义了镜像的每一层。以下是一个ROS Noetic的示例Dockerfile:

# 基于ROS Noetic官方镜像FROM osrf/ros:noetic-desktop-full

# 设置环境变量ENV DEBIAN_FRONTEND=noninteractive

# 安装常用工具RUN apt-get update && apt-get install -y \
    git wget vim nano \
    ros-noetic-pcl-ros \
    ros-noetic-rviz \
    && rm -rf /var/lib/apt/lists/*

# 设置工作目录WORKDIR /workspace

# 设置自动加载ROS环境RUN echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc

CMD ["/bin/bash"]

Dockerfile的关键指令:

  • FROM:指定基础镜像
  • RUN:在构建时执行命令
  • COPY/ADD:将文件复制到镜像中
  • ENV:设置环境变量
  • WORKDIR:设置工作目录
  • EXPOSE:声明容器监听的端口
  • CMD/ENTRYPOINT:容器启动时执行的命令

2.4 Docker Compose

Docker Compose是一个用于定义和运行多容器应用的工具。通过一个YAML文件配置多个容器,一键启动整个应用栈。

# docker-compose.yml示例version: '3'
services:
  fastlio:
    image: fastlio_noetic:v3
    container_name: fastlio_container
    volumes:
      - ./workspace:/workspace
    network_mode: host
    stdin_open: true
    tty: true

  rviz:
    image: osrf/ros:noetic-desktop-full
    container_name: rviz_container
    environment:
      - DISPLAY=${DISPLAY}
    volumes:
      - /tmp/.X11-unix:/tmp/.X11-unix
    network_mode: host
    depends_on:
      - fastlio

使用Docker Compose启动:

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

docker-compose down         # 停止并删除所有服务docker-compose logs -f      # 查看日志

三、ROS + Docker

将ROS应用打包到Docker容器中,是目前机器人开发和部署的最佳实践。它解决了“环境依赖地狱”问题。同一套代码在任何机器上都能跑。

3.1 ROS网络配置

ROS的节点通信依赖于网络。在Docker中运行ROS时,网络配置是关键。有三种主要方式:

网络模式说明适用场景
host容器共享宿主机网络栈ROS节点间通信,最常用
bridge容器使用独立的虚拟网络隔离环境,需要端口映射
none容器无网络离线测试

对于ROS应用,推荐使用host模式。因为ROS节点需要在不同容器之间通过话题通信,host模式让所有容器共享同一网络,节点之间可以直接发现彼此。

# 使用host网络模式启动ROS容器docker run -it --rm --network host \
  -v $(pwd)/workspace:/workspace \
  fastlio_noetic:v3 /bin/bash

3.2多容器ROS通信

在复杂的机器人系统中,通常会将不同功能拆分到不同的容器中。例如:

  • 容器A:运行FAST-LIO建图算法
  • 容器B:运行RViz可视化
  • 容器C:运行数据处理脚本
  • 容器D:运行机器人底盘驱动

这些容器之间通过ROS话题进行通信。只要使用host网络模式,容器内的ROS节点就像在同一台机器上运行一样,可以自然地发现彼此并通信。

# 容器1: 启动FAST-LIO
docker exec -it fastlio_container /bin/bash
roslaunch fast_lio mapping_avia.launch rviz:=false

# 容器2: 进入同一个容器播放bag
docker exec -it fastlio_container /bin/bash
cd /workspace
rosbag play data.bag

# 容器3: 在另一个容器中查看话题
docker exec -it rviz_container /bin/bash

rostopic list    # 可以看到FAST-LIO发布的话题rostopic echo /Odometry    # 查看里程计数据

3.3数据持久化

Docker容器是临时的。删除容器后,容器内的所有数据都会丢失。因此,必须将重要数据挂载到宿主机上。

最佳实践:

  • 代码和配置文件:通过 -v挂载到宿主机,方便修改和版本控制
  • 数据文件(bag、PCD):挂载到宿主机目录,容器只负责处理
  • 日志文件:挂载到宿主机,方便后续分析
# 推荐的数据挂载方式

docker run -it --rm \
  -v $(pwd)/workspace:/workspace \     # 数据和代码
  -v $(pwd)/bags:/bags \             # bag包
  -v $(pwd)/output:/output \         # 输出结果fastlio_noetic:v3 /bin/bash

3.4 X11转发(GUI应用)

ROS中很多工具是图形界面的(如RViz、rqt_graph)。在Docker中使用GUI应用需要X11转发。

# 方法1: 使用host网络 + X11 socket挂载docker run -it --rm --network host \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -v $HOME/.Xauthority:$HOME/.Xauthority \
  osrf/ros:noetic-desktop-full /bin/bash

# 方法2: 使用xhost允许Docker访问X11
xhost +local:docker
docker run -it --rm --network host \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  osrf/ros:noetic-desktop-full /bin/bash

四、实战:构建ROS SLAM容器

下面通过一个完整的例子,展示如何从零构建一个基于ROS Noetic的激光SLAM Docker容器。

4.1编写Dockerfile

FROM osrf/ros:noetic-desktop-full

ENV DEBIAN_FRONTEND=noninteractive

# 安装PCL和Eigen
RUN apt-get update && apt-get install -y \
    libpcl-dev \
    libeigen3-dev \
    ros-noetic-pcl-ros \
    ros-noetic-tf \
    ros-noetic-rviz \
    && rm -rf /var/lib/apt/lists/*

# 创建工作空间RUN mkdir -p /root/fastlio_ws/src
WORKDIR /root/fastlio_ws

# 自动加载ROS环境RUN echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc

WORKDIR /workspace
CMD ["/bin/bash"]

4.2构建和运行

# 构建镜像docker build -t fastlio_noetic:v1 .

# 启动容器docker run -it --rm --name fastlio_container \
  -v $(pwd)/workspace:/workspace \
  fastlio_noetic:v1 /bin/bash

# 在容器内编译FAST-LIO(首次)cd /root/fastlio_ws/src
git clone https://github.com/hku-mars/FAST_LIO.git
cd /root/fastlio_ws
catkin_make

# 后续使用:直接启动即可,代码已内置echo "source /root/fastlio_ws/devel/setup.bash" >> ~/.bashrc

4.3镜像版本管理

随着项目迭代,镜像会不断演进。建议采用语义化版本管理:

• v1.0:基础环境 + ROS Noetic + PCL

• v1.1:加入FAST-LIO源码

• v2.0:代码内置 + 环境自动加载 + PCD直出宿主机(开箱即用版)

• v2.1:修复PCD保存路径问题

# 查看镜像历史docker history fastlio_noetic:v2

# 给镜像打标签docker tag fastlio_noetic:v2 fastlio_noetic:latest
docker tag fastlio_noetic:v2 fastlio_noetic:20260616

# 导出镜像用于分发docker save -o fastlio_noetic_v2.tar fastlio_noetic:v2

# 在新机器上导入docker load -i fastlio_noetic_v2.tar

五、常见问题与排查

5.1容器内找不到ROS命令

进入容器后执行roscore提示command not found。原因是没有source ROS环境。

# 手动加载
source /opt/ros/noetic/setup.bash

# 永久生效(写入 .bashrc)
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc

5.2多容器间无法通信

检查以下几点:

  • 确认所有容器使用相同的网络模式(推荐host)
  • 确认ROS_MASTER_URI指向同一个roscore
  • 确认防火墙没有阻止相关端口
  • 使用rostopic list检查话题是否可见

5.3容器内无法显示GUI

确保X11 socket已挂载,且宿主机已执行xhost +local:docker。如果使用SSH远程连接,需要启用X11转发:

ssh -X user@host    # 启用X11转发

5.4挂载目录权限问题

Docker容器默认以root用户运行,创建的文件在宿主机上可能无法修改。解决方案:

# 方法1: 给挂载目录开权限(简单粗暴)chmod 777 ~/workspace

# 方法2: 在容器内创建非root用户useradd -m rosuser && su - rosuser

# 方法3: 使用user namespace映射docker run --userns=host ...

六、总结

ROS和Docker的结合是机器人开发的最佳实践。ROS提供了强大的通信和工具框架,Docker提供了环境隔离和一键部署能力。两者的结合让机器人软件的开发、测试和部署变得前所未有的简单。

关键要点:

  • ROS的核心是分布式通信。节点通过话题和服务交换数据
  • Docker的核心是容器化。将应用和依赖打包成可移植的镜像
  • ROS + Docker的关键是网络配置。使用host模式让容器间自然通信
  • 数据持久化靠挂载。重要数据一定要映射到宿主机• 镜像版本化管理。方便团队协作和部署