Bootstrap

【docker&ros2】ROS2节点通信:docker容器之间/docker容器与宿主机之间

🌀 一个中大型ROS项目常需要各个人员分别完成特定的功能,而后再组合部署,而各人员完成的功能常常依赖于一定的环境,而我们很难确保这些环境之间不会相互冲突,特别是涉及深度学习环境时。这就给团队项目的部署落地带来了极大的困难。虽然类似于conda这样的工具可以弥补一部分的不足,但是对于项目部署而言,还是不足的。

🚀现在就针对上述的问题,提出一种基于docker的ROS2通信解决方案,其中会涉及docker network和docker compose等内容。容器与宿主机的ROS2版本,LTS版(foxy/humble/…)都行。
在这里插入图片描述


🌔01
前情说明

首先有以下几点说明

(1) 宿主系统是基于ubuntu系统,系统的架构x86_64或arm均可,使用的是NVIDIA显卡,且系统内已安装相应的显卡驱动。
(2) 宿主机和容器的架构(x86_64/arm64)保持一致。
(3) 如果宿主机开了VPN,先关掉,避免干扰。

为了讲解的方便,假设我们有以下环境:

(1) 宿主系统是ubuntu20.04,架构是x86_64,安装了ros foxy,显卡是NVIDIA GTX 1650,且显卡驱动可用。
(2) 宿主系统内docker和docker compose都可以正常使用。
(3) 有一个docker镜像(假设叫做image:latest),镜像环境是ubuntu22.04,架构也是x86_64,安装了ros humble。


🌔02
创建docker compose文件

第一步编写一个docker compose文件,用以从镜像创建容器。这种方式会比使用docker run好些,因为它能够把容器的参数写到文件里,还能联调多个容器的关系,对于多容器组合的项目开发比较友好。并且我建议,如果你只有一个docker容器,那也使用docker compose,这样相对于docker run的命令行方式更容易维护。

mkdir project && cd project
touch project.yml

然后编写project.yml的内容,示例如下。这里之所有没用device:来挂载摄像头、激光雷达等外设,那是因为这些外设的驱动节点(传感器数据发布节点)放在宿主机中更好,容器只要订阅这些节点发布的话题进行处理即可。另外,ROS_DOMAIN_ID要一样才能通信,默认为0。且DDS类型要一致,都是Fast DDS(默认)或都是Cyclone DDS。

# 多对多,自由通信
#【可选】先用这个尝试,如果通信不了的话,尝试把该文件中的networks内容改为host,与宿主机共享网络栈
services:
  c1:  # 服务名称(一个服务对应一个容器),自己取名即可
      container_name: c1  # 容器名称
      image: image:latest # 镜像名称
      working_dir: [your_workdir_path1]  # 工作目录,比如/home/zhangsan
      shm_size: [your_share_memory_size]  # 共享内存大小,比如4G
      runtime: nvidia  
      environment:
        - NVIDIA_VISIBLE_DEVICES=all
        - DISPLAY=${DISPLAY}  # 设置DISPLAY环境变量 
      volumes:
        - [your_source_path1]:[your_target_path1]  # 挂载项目目录
        - /dev/dri:/dev/dri  # 挂载 GPU 设备
        - /tmp/.X11-unix:/tmp/.X11-unix:rw  # 挂载 X11 Unix 套接字
        - ${HOME}/.Xauthority:/root/.Xauthority:rw  # 挂载 X11 认证文件
      networks:
        - [network_name] # 共享网络名称,比如ros_network
      stdin_open: true  # 相当于 -i
      tty: true  # 相当于 -t
      
  c2:  # 服务名称(一个服务对应一个容器),自己取名即可
      container_name: c2  # 容器名称
      image: image:latest # 镜像名称
      working_dir: [your_workdir_path2]  # 工作目录,比如/home/lisi
      shm_size: [your_share_memory_size]   # 共享内存大小,比如4G
      runtime: nvidia  
      environment:
        - NVIDIA_VISIBLE_DEVICES=all
        - DISPLAY=${DISPLAY}  # 设置DISPLAY环境变量 
      volumes:
        - [your_source_path2]:[your_target_path2]  # 挂载项目目录
        - /dev/dri:/dev/dri  # 挂载 GPU 设备
        - /tmp/.X11-unix:/tmp/.X11-unix:rw  # 挂载 X11 Unix 套接字
        - ${HOME}/.Xauthority:/root/.Xauthority:rw  # 挂载 X11 认证文件
      networks:
        - [network_name] # 共享网络名称,比如ros_network
      stdin_open: true  # 相当于 -i
      tty: true  # 相当于 -t

networks: # 如果没有名称为ros_network的网络,会自己创建一个。
  [network_name]:
    name: [network_name]
    driver: bridge

🌔03
启动docker compose服务

开放X11服务(这样容器中的窗口就可以显示出来),并从project.yml文件启动docker容器。

xhost +
docker-compose -f project.yml up

接着你可以查看网络信息,确认是否共享上了:

docker network ls

你会看这样的信息:

NETWORK ID     NAME                          DRIVER    SCOPE
ecb8bca83c79   bridge                        bridge    local
0e489127c701   host                          host      local  #这个是你宿主机的网络
fad86f998533   none                          null      local
65b64309ebc2   ros_network                   bridge    local   #这个是你刚才创建的共享网络

进一步查看共享网络信息:

docker network inspect ros_network  

你会看到这样的输出:

# c1和c2之间的ROS2节点在ros_network子网中通信
# 容器ROS2节点通过把数据转发给ros_network网关,网关再转接给host,实现与宿主机ROS2节点通信
[
    {
        "Name": "ros_network",
        "Id": "65b64309ebc2df88cd05c3a1fb33635ab4818fd6d2022ce028ad25ef22f2e0ef",
        "Created": "2025-01-11T20:20:06.632800735+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16", # 子网
                    "Gateway": "172.18.0.1" # 网关
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "79ba2ce2e7914e02e9c36828eb40c343c5abe5f3ca43f2c61b304b91f8cd8de6": {
                "Name": "c1",
                "EndpointID": "f3956bf172423f84b4e2bf0d55d9c1149b9135dcc32cfc6d01c6770b86a0015d",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16", # c1的ip
                "IPv6Address": ""
            },
            "d02027c00ae82e6589dd4690fb7f69974de9d867958f17ced17f3babe0c7527b": {
                "Name": "c2",
                "EndpointID": "50b4abf51ee3f4b118b99813b061830a2cf2858ecffa4b2c6147849e378fe8b7",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16", #c2的ip
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

🌔04
ROS2通信测试

另开两个终端,分别在终端内运行以下命令进入容器(你可以开多个):

docker exec -it c1 /bin/bash # 第一个终端(可以开多个)
docker exec -it c2 /bin/bash # 第二个终端(可以开多个)

启动以下节点:

# 在宿主机中
ros2 run turtlesim turtlesim_node
# 在c1容器中
ros2 run demo_nodes_cpp talker # [INFO] [1736647116.948458682] [talker]: Publishing: 'Hello World: 1'
# 在c2容器中
ros2 run demo_nodes_cpp listener  # [INFO] [1736647126.948946624] [listener]: I heard: [Hello World: 1]

并分别在各环境中运行:

ros2 node list

如果所有环境中都输出了以下内容,那就说明成功了。

/listener
/talker
/turtlesim

🌔05
附录

5.1 安装docker compose

# 从官方下载软件包(确保和自己机器的架构一致):https://github.com/docker/compose/releases
# 假设下载了docker-compose-linux-x86_64到~/Downloads
cd ~/Downloads
# 改名
mv docker-compose-linux-x86_64 docker-compose
# 移动到指定位置
sudo mv docker-compose /usr/local/bin
# 赋予权限
sudo chmod +x /usr/local/bin/docker-compose

5.2 安装网络工具

sudo apt install net-tools
sudo apt install iputils-ping
sudo apt install iproute2

5.3 其他配置

# 查看ros2的信息,包括版本,DDS类型等
ros2 doctor --report 
# 下载Cyclone DDS,如果需要的话
sudo apt install ros-$ROS_DISTRO-rmw-cyclonedds-cpp 

# bashrc内设置
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# 如果用默认的Fast DDS
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
# 如果用Cyclone DDS
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_DOMAIN_ID=0
export ROS_LOCALHOST_ONLY=0
# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;