ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

deepin下用Docker搭建ROS2开发环境完整指南

deepin下用Docker搭建ROS2开发环境完整指南 先说个背景。我这台deepin本来是用来日常办公的后来想折腾机器人开发就动了装ROS2的心思。一开始我很天真直接在系统里照着Ubuntu的教程用apt装ros-humble-desktop结果装到一半提示要升级一堆系统基础库我没多想就回车然后重启之后桌面直接进不去了。那次之后我彻底明白了一个道理在deepin上折腾ROS2最好别碰系统原生的依赖树老老实实走docker才是正路。现在这套方案我已经用了挺长时间了deepin系统本身干干净净ROS2环境全在容器里搞坏了就重来再也不怕把系统搞崩。这篇文章就把完整的流程记录下来deepin上怎么装docker、怎么选ROS2镜像、容器里怎么把rviz2和gazebo的画面调出来以及我踩过的那些坑。如果你也是deepin用户又想玩ROS2这篇文章可以直接照着抄。1. 为什么非得绕Docker这一圈ROS2版本与系统版的硬绑关系1.1 ROS2不是装个包就行版本和Ubuntu是一一绑定的先看一组ROS2官方版本支持表。ROS2每个发行版都绑定一个Ubuntu LTS版本这个绑定关系是写死在构建脚本里的ROS2版本对应Ubuntu版本发布时间维护状态Foxy FitzroyUbuntu 20.04 (Focal)2020年6月已EOLHumble HawksbillUbuntu 22.04 (Jammy)2022年5月当前LTSIron IrwiniUbuntu 22.04 (Jammy)2023年5月已EOLJazzy JaliscoUbuntu 24.04 (Noble)2024年5月当前LTS为什么会有这种硬绑定因为ROS2的二进制包依赖某个特定版本的Boost、Python、OpenSSL、OpenCV等库而这些库在Ubuntu版本间差异很大。官方只对指定Ubuntu版本做完整测试所以预编译包只发对应版本。如果你用不匹配的系统版本强行装apt会告诉你依赖冲突你只能看到一串又一串需要但无法安装的报错。deepin本质上基于Debian系和Ubuntu同源但库版本又不一样。ROS2官方并没有发布deepin版本所以要装只能引入Ubuntu源而这一引入就把整个系统的APT依赖树搅浑了。这就是我在开头说的桌面崩掉事故的根本原因。1.2 在deepin上硬装的真实下场可能有人会说我手动从源码编译ROS2不行吗 可以但代价非常大。源码编译一个humble-desktop全量版本光是依赖就能折腾一整天而且编译过程中可能因为deepin的编译器版本、CMake版本和Ubuntu镜像不一致出现各种奇奇怪怪的编译错误。更别提编译到一半发现某个ROS2包依赖的系统库版本不对又得手动去搞。网上还有那种一键安装脚本在Ubuntu上确实方便但拿到deepin上跑脚本会识别系统信息发现不是Ubuntu就拒绝执行或者干脆硬着头皮去改系统的Python和pip版本最后把系统搞得更乱。我见过不止一次有人把系统Python换成3.10之后整个deepin的应用商店和桌面组件全部罢工。所以我最终放弃直接在deepin上装ROS2转向docker。这是目前最稳的路不碰宿主系统的依赖树ROS2需要什么库就装在容器里容器用的镜像基于Ubuntu 22.04和ROS2 humble的官方发布版本完全一致。1.3 Docker解决的不只是安装还有可复现除了避免系统崩溃docker这套方案还有个很大的好处可复现。我可以把整个ROS2环境包括装好的turtlesim、rviz2插件、自己编译的功能包打包成一个镜像拷到另一台电脑上直接跑完全不需要重新配置。对于做机器人开发的人来说这个特性太重要了——你换电脑、换系统、带学生做实训一套镜像全搞定。另外docker容器的隔离性也给了你随便折腾的底气。容器里装坏了删掉容器重新create一份几十秒又是一条好汉。这种试错成本是直接在deepin系统上操作没法比的。2. deepin上装Docker Engine源、驱动与用户组三件套2.1 为什么不用Docker Desktop先解开那个virtualization报错很多从Windows转过来的朋友第一反应是装Docker Desktop然后大概率会撞上这个报错virtualization support not detected。这个报错的意思是Docker Desktop需要在你的机器上启用CPU虚拟化扩展VT-x或AMD-V它本质上是一个跑在轻量虚拟机里的方案对宿主要求高而且deepin对Docker Desktop的兼容性并不好。但做ROS2开发我们根本不需要Docker Desktop。我们用Docker Engine就够了就是纯命令行工具不依赖任何虚拟机技术在deepin上非常稳。我自己一直用的是CLI方式日常开发完全够用甚至比Desktop更清爽、更省资源。2.2 添加Docker源时最容易卡住的一步deepin上装Docker Engine最麻烦的不是执行apt install而是添加docker官方软件源。docker官方源是按Debian版本提供apt仓库的它需要你明确告诉它我是Debian哪个版本。deepin的/etc/os-release里写的版本代号是deepin自己的名字比如amber或beigedocker源不认这个如果你直接照抄官方文档的命令apt update会报错说找不到Release文件。正确做法是去看/etc/debian_version这个文件里写的是deepin底层对应的Debian版本号。我的机器上对应的是bullseye所以docker源要按Debian 11来配。如果你的deepin版本不同以你机器上/etc/debian_version的值为准。操作步骤比较简单# 安装依赖工具 sudo apt update sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release # 添加docker官方GPG密钥 curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 添加docker仓库注意bullseye换成你的debian版本代号 echo deb [archamd64 signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable | sudo tee /etc/apt/sources.list.d/docker.list # 刷新索引 sudo apt update这里我刻意用了阿里云的镜像地址而不是docker官方地址因为在国内直接访问docker官方仓库经常超时换成镜像源能省掉很多等待时间。2.3 安装、启动、免sudo三步走源配好之后安装就很直接了sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable --now docker docker versiondocker version能看到客户端和服务端版本号说明服务已经正常运行。如果是第一次装可以先跑一下sudo docker run hello-world验证整个链路能通。最后一步是免sudo。docker CLI默认需要root权限每次敲sudo docker很烦而且后面挂载目录、跑容器时权限问题会很多。把当前用户加进docker组就能解决sudo usermod -aG docker $USER newgrp docker docker run hello-worldnewgrp docker让当前终端立即生效不用重新登录。如果重启终端之后docker还是报权限拒绝那就是用户组没刷新成功重新登录一次就好。2.4 配一个国内镜像加速器否则拉镜像会怀疑人生docker装好后第一件要做的事是配镜像加速器。不配的话直接拉ros镜像可能卡在下载层动弹不得。在/etc/docker/daemon.json里配置镜像源{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] }配好之后重启dockersudo systemctl restart docker加速器的作用相当于给Docker Hub加了一层国内缓存拉同名的镜像时直接从镜像站取速度快很多。注意镜像站有时候会调整域名配多个备选可以减少拉取失败的次数。3. 拿官方镜像先跑一个最小ROS2环境3.1 镜像版本怎么选从Humble到Jazzydocker官方仓库里有ROS2镜像最常见的镜像名是ros和osrf/ros。选镜像版本的时候跟着你实际需要的ROS2版本走即可。做机器人开发目前我还是推荐Humble原因很简单生态最成熟资料最多绝大多数教学和开源项目都用humble。如果你的项目明确要求Jazzy或者更新的版本再根据下表选择镜像标签对应ROS2版本对应Ubuntu适用场景ros:humble-ros-core-jammyHumble22.04最小核心适合网关类设备ros:humble-ros-base-jammyHumble22.04基础通信常用工具建议日常开发用这个ros:humble-desktopHumble22.04带图形工具rviz2等体积大ros:jazzy-ros-base-nobleJazzy24.04新项目、新硬件平台ros:jazzy-desktopJazzy24.04新平台带桌面工具我实际用的是ros:humble-ros-base因为desktop版本体积太大而rviz2这些图形工具自己可以按需安装用到什么装什么反而更清爽。3.2 docker run参数逐个拆解启动一个ROS2容器我会用这样一串参数docker run -it --name ros2_humble \ --network host \ -e DISPLAY$DISPLAY \ -e LIBGL_ALWAYS_SOFTWARE1 \ -e TZAsia/Shanghai \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -v $HOME/ros2_ws:/ros2_ws \ ros:humble-ros-base逐个说说为什么这么配--network hostROS2的通信依赖DDSDDS默认用UDP多播做节点发现。用host网络模式容器直接共享deepin主机的网络栈多播包畅通无阻节点互相发现零障碍。这步是ROS2容器能否正常通信的关键。-e DISPLAY$DISPLAY把宿主的图形显示环境变量传进容器让rviz2知道往哪里画窗口。-e LIBGL_ALWAYS_SOFTWARE1强制OpenGL走软件渲染。deepin上如果显卡驱动没处理好rviz2很可能黑屏崩溃先从软渲染跑通后面再优化硬件加速。-e TZAsia/Shanghai容器默认UTC时区不设的话日志时间差8小时后面排错时容易混淆。-v /tmp/.X11-unix:/tmp/.X11-unix挂载X11的socket文件这是图形程序从容器连回宿主桌面的通道。-v $HOME/ros2_ws:/ros2_ws共享工作目录宿主机上写代码容器里编译运行。注意我加了--name但没加--rm因为我想保留这个容器容器里安装的额外包不会丢。如果只想临时用用加--rm更干净。3.3 容器里的第一行命令source启动容器后你会直接进入一个root shell。第一件事是加载ROS2环境变量source /opt/ros/humble/setup.bash ros2 --help如果没有这步source直接敲ros2百分百是command not found。为了方便把source写进容器的bashrcecho source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrc之后再进容器就不用每次手动source了。这也是新手最容易忽略的细节——网上很多教程直接让你敲ros2命令却没人告诉你前提是source环境。3.4 把启动命令固化成脚本手动敲docker run那一长串参数终究不是长久之计。我在deepin主机的~/bin/下放了一个启动脚本#!/bin/bash # 启动ROS2 humble容器 xhost local:root docker start ros2_humble 2/dev/null || docker run -it --name ros2_humble \ --network host \ -e DISPLAY$DISPLAY \ -e LIBGL_ALWAYS_SOFTWARE1 \ -e TZAsia/Shanghai \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -v $HOME/ros2_ws:/ros2_ws \ ros:humble-ros-base脚本做了一个判断如果容器已经存在直接docker start启动不存在才docker run创建。xhost local:root这句放在脚本里省得每次都手动授权X11。4. 画面要出来让rviz2在容器里看见deepin桌面4.1 X11授权三连DISPLAY、xhost、/tmp/.X11-unix容器里跑图形程序本质是让容器内的进程把画面画到deepin桌面上。Linux的X11显示协议走的是socket通信进程要连上X server必须同时满足三个条件知道X server的地址、有访问socket的权限、能通过X server的授权校验。对应的三个操作我已经在前面布置好了-e DISPLAY$DISPLAY把显示地址传进去。deepin默认是:0代表本机的第一个显示设备。-v /tmp/.X11-unix:/tmp/.X11-unix把X11的socket目录挂进容器。在deepin主机上执行xhost local:root允许本地的root用户连接X server。xhost local:root这条命令每次重启机器后都需要重新执行所以我把它直接写进了启动脚本里。如果你发现容器里启动rviz2时报cannot open display优先检查这三项是否都到位了。4.2 显卡渲染的两条路软渲染与挂载/dev/dri图形界面能打开不代表渲染正常。deepin的机器配置差异很大有的用N卡闭源驱动有的用Intel核显。N卡驱动在容器里很容易出问题因为容器内没有宿主的X驱动和CUDA库直接调用GPU渲染会黑屏或者花屏。我的建议是先走软渲染LIBGL_ALWAYS_SOFTWARE1这个环境变量会让OpenGL全部走CPU渲染。缺点是帧率低但rviz2这种界面程序不是游戏CPU渲染足够了。如果你做仿真比如gazebo带模型加载CPU渲染可能卡那就把宿主机的GPU设备挂进容器docker run ... --device/dev/dri .../dev/dri是Linux的DRM设备节点挂进去之后容器里的用户态驱动就可以直接访问GPU了。这个方法对Intel核显和AMD卡效果很好N卡反而没有在X11下直接挂CUDA走OpenGL的通用做法还是软渲染更省心。4.3 实测在容器里跑turtlesim和rviz2验证图形链路是否通了最轻量的方法是跑turtlesim。在容器里执行apt update apt install -y ros-humble-turtlesim ros2 run turtlesim turtlesim_node如果一切正常deepin桌面上会弹出一个小海龟的窗口你甚至可以再开一个终端用键盘控制它。这个看似简单的窗口能弹出来说明DISPLAY、X11授权、渲染环境三个环节全部打通后面跑rviz2换汤不换药。接着验证rviz2apt install -y ros-humble-rviz2 rviz2rviz2的界面比turtlesim复杂得多如果能正常显示3D场景且拖动不崩溃那这套容器图形方案就完全成立了。5. 让容器和deepin主机真正协同工作网络、共享目录与用户映射5.1 host网络模式为什么是ROS2的默认首选ROS2的通信机制和ROS1不同它底层用DDSDDS的节点发现靠UDP多播。默认情况下同一个局域网内的ROS2节点会通过多播地址互相宣告存在。这个特性在家用路由器环境里很方便但到了容器就要注意docker默认的bridge网络模式会隔离多播包两个容器用默认网络跑ROS2节点经常互相看不见。用--network host就一劳永逸了容器和deepin主机共享同一个网络栈多播包直接走宿主物理网卡节点发现和通信跟直接在宿主机上跑没区别。这也是为什么我强烈建议开发阶段用host网络模式。代价是端口隔离失效。比如容器里起了一个监听固定端口8080的服务它就占用了宿主机的8080。ROS2用的端口是动态分配的冲突概率很低但如果你在容器里跑其他Web服务心里得有个数。5.2 用共享目录做开发工作流容器里写代码最大的痛点是文件隔离。我通常在deepin主机上用VS Code打开~/ros2_ws写代码容器里编译运行两者通过挂载共享同一份文件。这样文件操作在宿主侧权限管理直观还能用宿主上熟悉的编辑器。共享目录的工作流需要一套固定的操作# 在deepin主机创建并进入工作目录 mkdir -p ~/ros2_ws/src cd ~/ros2_ws # 在容器内进入同样的路径因为挂载两边看到的是同一份文件 cd /ros2_ws colcon build --symlink-install source install/setup.bash我在容器里还装了个ros-humble-demo-nodes-cpp之类的示例包确认编译后的install目录能被source。--symlink-install这个参数很实用它用符号链接替代文件拷贝改完源码不用重新编译对频繁调试特别友好。5.3 容器内用户的坑与解决默认容器里是root用户root在共享目录里创建的文件宿主侧属主也是root。这会导致一个很尴尬的情况你在deepin主机上想改容器里生成的文件没有sudo权限就得干瞪眼。我的解决办法是让容器内用户和宿主用户保持一致的UID。deepin主机上先看自己的UIDid -u然后用-u参数指定容器内以哪个UID运行docker run -it \ --network host \ -u $(id -u):$(id -g) \ -v $HOME/ros2_ws:/ros2_ws \ ros:humble-ros-base这样容器内创建的文件UID和宿主用户一致宿主侧随手就能改。但要注意容器内很多系统操作需要root权限用普通UID运行时会受限。我的折中方案是日常编译和运行节点用-u指定普通用户需要装包时才进root容器。实际操作里我会准备两个启动脚本一个是root版用来apt install一个是普通用户版用来日常开发简单粗暴但很好用。6. 实战验证与常见翻车点排查6.1 跑通talker/listener验证核心链路图形环境验证过了再验证通信链路。开两个终端进入同一个容器或者分别进两个容器host网络下都一样一边跑发布者一边跑订阅者# 终端A ros2 run demo_nodes_cpp talker # 终端B ros2 run demo_nodes_cpp listenerlistener能持续打印I heard: Hello World说明DDS通信链路正常。接着可以看话题列表ros2 topic list ros2 topic echo /chatter如果topic能看到但订阅没数据大概率是DDS发现协议出了问题按下面的排查表处理。6.2 全身排查七类典型故障及修复我把实操中容易踩的坑整理成一张表按现象排错就行现象根因修复方案docker: permission denied用户不在docker组或组未生效newgrp docker或重新登录拉镜像超时未配镜像加速器修改/etc/docker/daemon.json后重启ros2: command not found未source环境source /opt/ros/humble/setup.bash两个容器互相看不到节点DDS多播被bridge网络隔离改用--network hostrviz2报cannot open displayX11授权或DISPLAY未传递检查xhost local:root和-e DISPLAYrviz2黑屏或崩溃GPU渲染问题设LIBGL_ALWAYS_SOFTWARE1软渲染容器时间差8小时未设时区-e TZAsia/Shanghai重新运行这里面最隐蔽的是DDS多播问题因为报错不直观。多个容器跑起来后ros2 node list只能看到自己别人一概报NODE NOT FOUND。新手很难想到是网络模式的问题我还见过有人怀疑是防火墙在deepin上折腾半天最后发现只是docker bridge网络把多播包挡住了。换成host网络模式立刻解决。6.3 把容器固化下来docker commit与Compose开发过程中你会装一堆额外的包比如turtlesim、rviz2、gazebo插件、自己的功能包。如果容器删了这些全得重装所以务必在环境搭好后固化镜像docker commit ros2_humble ros2_humble:dev-v1docker commit会把当前容器状态打包成新镜像下次直接用这个镜像创建容器所有已装包都在。我习惯每稳定一个阶段就commit一次版本号递增出问题可以回滚。更现代的做法是写docker-compose.yml把启动参数文件化services: ros2: image: ros:humble-ros-base network_mode: host environment: - DISPLAY${DISPLAY} - LIBGL_ALWAYS_SOFTWARE1 - TZAsia/Shanghai volumes: - /tmp/.X11-unix:/tmp/.X11-unix - $HOME/ros2_ws:/ros2_ws stdin_open: true tty: true command: bash用docker compose up -d一条命令启动参数全在文件里换机器也方便。我个人觉得docker compose虽然比纯命令多一步学习成本但长期收益非常大尤其是项目环境要给别人复现的时候。最后再分享一个实际工作中的体会deepin下这套dockerROS2方案真正舒服的地方在于deepin桌面本身对开发环境干预少、系统干净容器跑起来几乎感觉不到性能损耗。我用了半年多系统一直稳定再也没出现依赖风暴把桌面搞崩的情况。如果你是deepin用户又想在机器人开发这条路上走远一点强烈建议花一晚上把docker环境搭好后面你会感谢这个决定的。
返回列表