環(huán)境完整指南)
先說個背景。我這臺deepin本來是用來日常辦公的后來想折騰機器人開發(fā)就動了裝ROS2的心思。一開始我很天真直接在系統(tǒng)里照著Ubuntu的教程用apt裝ros-humble-desktop結(jié)果裝到一半提示要升級一堆系統(tǒng)基礎(chǔ)庫我沒多想就回車然后重啟之后桌面直接進不去了。那次之后我徹底明白了一個道理在deepin上折騰ROS2最好別碰系統(tǒng)原生的依賴樹老老實實走docker才是正路?,F(xiàn)在這套方案我已經(jīng)用了挺長時間了deepin系統(tǒng)本身干干凈凈ROS2環(huán)境全在容器里搞壞了就重來再也不怕把系統(tǒng)搞崩。這篇文章就把完整的流程記錄下來deepin上怎么裝docker、怎么選ROS2鏡像、容器里怎么把rviz2和gazebo的畫面調(diào)出來以及我踩過的那些坑。如果你也是deepin用戶又想玩ROS2這篇文章可以直接照著抄。1. 為什么非得繞Docker這一圈ROS2版本與系統(tǒng)版的硬綁關(guān)系1.1 ROS2不是裝個包就行版本和Ubuntu是一一綁定的先看一組ROS2官方版本支持表。ROS2每個發(fā)行版都綁定一個Ubuntu LTS版本這個綁定關(guān)系是寫死在構(gòu)建腳本里的ROS2版本對應(yīng)Ubuntu版本發(fā)布時間維護狀態(tài)Foxy FitzroyUbuntu 20.04 (Focal)2020年6月已EOLHumble HawksbillUbuntu 22.04 (Jammy)2022年5月當(dāng)前LTSIron IrwiniUbuntu 22.04 (Jammy)2023年5月已EOLJazzy JaliscoUbuntu 24.04 (Noble)2024年5月當(dāng)前LTS為什么會有這種硬綁定因為ROS2的二進制包依賴某個特定版本的Boost、Python、OpenSSL、OpenCV等庫而這些庫在Ubuntu版本間差異很大。官方只對指定Ubuntu版本做完整測試所以預(yù)編譯包只發(fā)對應(yīng)版本。如果你用不匹配的系統(tǒng)版本強行裝apt會告訴你依賴沖突你只能看到一串又一串需要但無法安裝的報錯。deepin本質(zhì)上基于Debian系和Ubuntu同源但庫版本又不一樣。ROS2官方并沒有發(fā)布deepin版本所以要裝只能引入Ubuntu源而這一引入就把整個系統(tǒng)的APT依賴樹攪渾了。這就是我在開頭說的桌面崩掉事故的根本原因。1.2 在deepin上硬裝的真實下場可能有人會說我手動從源碼編譯ROS2不行嗎 可以但代價非常大。源碼編譯一個humble-desktop全量版本光是依賴就能折騰一整天而且編譯過程中可能因為deepin的編譯器版本、CMake版本和Ubuntu鏡像不一致出現(xiàn)各種奇奇怪怪的編譯錯誤。更別提編譯到一半發(fā)現(xiàn)某個ROS2包依賴的系統(tǒng)庫版本不對又得手動去搞。網(wǎng)上還有那種一鍵安裝腳本在Ubuntu上確實方便但拿到deepin上跑腳本會識別系統(tǒng)信息發(fā)現(xiàn)不是Ubuntu就拒絕執(zhí)行或者干脆硬著頭皮去改系統(tǒng)的Python和pip版本最后把系統(tǒng)搞得更亂。我見過不止一次有人把系統(tǒng)Python換成3.10之后整個deepin的應(yīng)用商店和桌面組件全部罷工。所以我最終放棄直接在deepin上裝ROS2轉(zhuǎn)向docker。這是目前最穩(wěn)的路不碰宿主系統(tǒng)的依賴樹ROS2需要什么庫就裝在容器里容器用的鏡像基于Ubuntu 22.04和ROS2 humble的官方發(fā)布版本完全一致。1.3 Docker解決的不只是安裝還有可復(fù)現(xiàn)除了避免系統(tǒng)崩潰docker這套方案還有個很大的好處可復(fù)現(xiàn)。我可以把整個ROS2環(huán)境包括裝好的turtlesim、rviz2插件、自己編譯的功能包打包成一個鏡像拷到另一臺電腦上直接跑完全不需要重新配置。對于做機器人開發(fā)的人來說這個特性太重要了——你換電腦、換系統(tǒng)、帶學(xué)生做實訓(xùn)一套鏡像全搞定。另外docker容器的隔離性也給了你隨便折騰的底氣。容器里裝壞了刪掉容器重新create一份幾十秒又是一條好漢。這種試錯成本是直接在deepin系統(tǒng)上操作沒法比的。2. deepin上裝Docker Engine源、驅(qū)動與用戶組三件套2.1 為什么不用Docker Desktop先解開那個virtualization報錯很多從Windows轉(zhuǎn)過來的朋友第一反應(yīng)是裝Docker Desktop然后大概率會撞上這個報錯virtualization support not detected。這個報錯的意思是Docker Desktop需要在你的機器上啟用CPU虛擬化擴展VT-x或AMD-V它本質(zhì)上是一個跑在輕量虛擬機里的方案對宿主要求高而且deepin對Docker Desktop的兼容性并不好。但做ROS2開發(fā)我們根本不需要Docker Desktop。我們用Docker Engine就夠了就是純命令行工具不依賴任何虛擬機技術(shù)在deepin上非常穩(wěn)。我自己一直用的是CLI方式日常開發(fā)完全夠用甚至比Desktop更清爽、更省資源。2.2 添加Docker源時最容易卡住的一步deepin上裝Docker Engine最麻煩的不是執(zhí)行apt install而是添加docker官方軟件源。docker官方源是按Debian版本提供apt倉庫的它需要你明確告訴它我是Debian哪個版本。deepin的/etc/os-release里寫的版本代號是deepin自己的名字比如amber或beigedocker源不認(rèn)這個如果你直接照抄官方文檔的命令apt update會報錯說找不到Release文件。正確做法是去看/etc/debian_version這個文件里寫的是deepin底層對應(yīng)的Debian版本號。我的機器上對應(yīng)的是bullseye所以docker源要按Debian 11來配。如果你的deepin版本不同以你機器上/etc/debian_version的值為準(zhǔn)。操作步驟比較簡單# 安裝依賴工具 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官方地址因為在國內(nèi)直接訪問docker官方倉庫經(jīng)常超時換成鏡像源能省掉很多等待時間。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能看到客戶端和服務(wù)端版本號說明服務(wù)已經(jīng)正常運行。如果是第一次裝可以先跑一下sudo docker run hello-world驗證整個鏈路能通。最后一步是免sudo。docker CLI默認(rèn)需要root權(quán)限每次敲sudo docker很煩而且后面掛載目錄、跑容器時權(quán)限問題會很多。把當(dāng)前用戶加進docker組就能解決sudo usermod -aG docker $USER newgrp docker docker run hello-worldnewgrp docker讓當(dāng)前終端立即生效不用重新登錄。如果重啟終端之后docker還是報權(quán)限拒絕那就是用戶組沒刷新成功重新登錄一次就好。2.4 配一個國內(nèi)鏡像加速器否則拉鏡像會懷疑人生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加速器的作用相當(dāng)于給Docker Hub加了一層國內(nèi)緩存拉同名的鏡像時直接從鏡像站取速度快很多。注意鏡像站有時候會調(diào)整域名配多個備選可以減少拉取失敗的次數(shù)。3. 拿官方鏡像先跑一個最小ROS2環(huán)境3.1 鏡像版本怎么選從Humble到Jazzydocker官方倉庫里有ROS2鏡像最常見的鏡像名是ros和osrf/ros。選鏡像版本的時候跟著你實際需要的ROS2版本走即可。做機器人開發(fā)目前我還是推薦Humble原因很簡單生態(tài)最成熟資料最多絕大多數(shù)教學(xué)和開源項目都用humble。如果你的項目明確要求Jazzy或者更新的版本再根據(jù)下表選擇鏡像標(biāo)簽對應(yīng)ROS2版本對應(yīng)Ubuntu適用場景ros:humble-ros-core-jammyHumble22.04最小核心適合網(wǎng)關(guān)類設(shè)備ros:humble-ros-base-jammyHumble22.04基礎(chǔ)通信常用工具建議日常開發(fā)用這個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參數(shù)逐個拆解啟動一個ROS2容器我會用這樣一串參數(shù)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默認(rèn)用UDP多播做節(jié)點發(fā)現(xiàn)。用host網(wǎng)絡(luò)模式容器直接共享deepin主機的網(wǎng)絡(luò)棧多播包暢通無阻節(jié)點互相發(fā)現(xiàn)零障礙。這步是ROS2容器能否正常通信的關(guān)鍵。-e DISPLAY$DISPLAY把宿主的圖形顯示環(huán)境變量傳進容器讓rviz2知道往哪里畫窗口。-e LIBGL_ALWAYS_SOFTWARE1強制OpenGL走軟件渲染。deepin上如果顯卡驅(qū)動沒處理好rviz2很可能黑屏崩潰先從軟渲染跑通后面再優(yōu)化硬件加速。-e TZAsia/Shanghai容器默認(rèn)UTC時區(qū)不設(shè)的話日志時間差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環(huán)境變量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了。這也是新手最容易忽略的細節(jié)——網(wǎng)上很多教程直接讓你敲ros2命令卻沒人告訴你前提是source環(huán)境。3.4 把啟動命令固化成腳本手動敲docker run那一長串參數(shù)終究不是長久之計。我在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腳本做了一個判斷如果容器已經(jīng)存在直接docker start啟動不存在才docker run創(chuàng)建。xhost local:root這句放在腳本里省得每次都手動授權(quán)X11。4. 畫面要出來讓rviz2在容器里看見deepin桌面4.1 X11授權(quán)三連DISPLAY、xhost、/tmp/.X11-unix容器里跑圖形程序本質(zhì)是讓容器內(nèi)的進程把畫面畫到deepin桌面上。Linux的X11顯示協(xié)議走的是socket通信進程要連上X server必須同時滿足三個條件知道X server的地址、有訪問socket的權(quán)限、能通過X server的授權(quán)校驗。對應(yīng)的三個操作我已經(jīng)在前面布置好了-e DISPLAY$DISPLAY把顯示地址傳進去。deepin默認(rèn)是:0代表本機的第一個顯示設(shè)備。-v /tmp/.X11-unix:/tmp/.X11-unix把X11的socket目錄掛進容器。在deepin主機上執(zhí)行xhost local:root允許本地的root用戶連接X server。xhost local:root這條命令每次重啟機器后都需要重新執(zhí)行所以我把它直接寫進了啟動腳本里。如果你發(fā)現(xiàn)容器里啟動rviz2時報cannot open display優(yōu)先檢查這三項是否都到位了。4.2 顯卡渲染的兩條路軟渲染與掛載/dev/dri圖形界面能打開不代表渲染正常。deepin的機器配置差異很大有的用N卡閉源驅(qū)動有的用Intel核顯。N卡驅(qū)動在容器里很容易出問題因為容器內(nèi)沒有宿主的X驅(qū)動和CUDA庫直接調(diào)用GPU渲染會黑屏或者花屏。我的建議是先走軟渲染LIBGL_ALWAYS_SOFTWARE1這個環(huán)境變量會讓OpenGL全部走CPU渲染。缺點是幀率低但rviz2這種界面程序不是游戲CPU渲染足夠了。如果你做仿真比如gazebo帶模型加載CPU渲染可能卡那就把宿主機的GPU設(shè)備掛進容器docker run ... --device/dev/dri .../dev/dri是Linux的DRM設(shè)備節(jié)點掛進去之后容器里的用戶態(tài)驅(qū)動就可以直接訪問GPU了。這個方法對Intel核顯和AMD卡效果很好N卡反而沒有在X11下直接掛CUDA走OpenGL的通用做法還是軟渲染更省心。4.3 實測在容器里跑turtlesim和rviz2驗證圖形鏈路是否通了最輕量的方法是跑turtlesim。在容器里執(zhí)行apt update apt install -y ros-humble-turtlesim ros2 run turtlesim turtlesim_node如果一切正常deepin桌面上會彈出一個小海龜?shù)拇翱谀闵踔量梢栽匍_一個終端用鍵盤控制它。這個看似簡單的窗口能彈出來說明DISPLAY、X11授權(quán)、渲染環(huán)境三個環(huán)節(jié)全部打通后面跑rviz2換湯不換藥。接著驗證rviz2apt install -y ros-humble-rviz2 rviz2rviz2的界面比turtlesim復(fù)雜得多如果能正常顯示3D場景且拖動不崩潰那這套容器圖形方案就完全成立了。5. 讓容器和deepin主機真正協(xié)同工作網(wǎng)絡(luò)、共享目錄與用戶映射5.1 host網(wǎng)絡(luò)模式為什么是ROS2的默認(rèn)首選ROS2的通信機制和ROS1不同它底層用DDSDDS的節(jié)點發(fā)現(xiàn)靠UDP多播。默認(rèn)情況下同一個局域網(wǎng)內(nèi)的ROS2節(jié)點會通過多播地址互相宣告存在。這個特性在家用路由器環(huán)境里很方便但到了容器就要注意docker默認(rèn)的bridge網(wǎng)絡(luò)模式會隔離多播包兩個容器用默認(rèn)網(wǎng)絡(luò)跑ROS2節(jié)點經(jīng)?;ハ嗫床灰姟S?-network host就一勞永逸了容器和deepin主機共享同一個網(wǎng)絡(luò)棧多播包直接走宿主物理網(wǎng)卡節(jié)點發(fā)現(xiàn)和通信跟直接在宿主機上跑沒區(qū)別。這也是為什么我強烈建議開發(fā)階段用host網(wǎng)絡(luò)模式。代價是端口隔離失效。比如容器里起了一個監(jiān)聽固定端口8080的服務(wù)它就占用了宿主機的8080。ROS2用的端口是動態(tài)分配的沖突概率很低但如果你在容器里跑其他Web服務(wù)心里得有個數(shù)。5.2 用共享目錄做開發(fā)工作流容器里寫代碼最大的痛點是文件隔離。我通常在deepin主機上用VS Code打開~/ros2_ws寫代碼容器里編譯運行兩者通過掛載共享同一份文件。這樣文件操作在宿主側(cè)權(quán)限管理直觀還能用宿主上熟悉的編輯器。共享目錄的工作流需要一套固定的操作# 在deepin主機創(chuàng)建并進入工作目錄 mkdir -p ~/ros2_ws/src cd ~/ros2_ws # 在容器內(nèi)進入同樣的路徑因為掛載兩邊看到的是同一份文件 cd /ros2_ws colcon build --symlink-install source install/setup.bash我在容器里還裝了個ros-humble-demo-nodes-cpp之類的示例包確認(rèn)編譯后的install目錄能被source。--symlink-install這個參數(shù)很實用它用符號鏈接替代文件拷貝改完源碼不用重新編譯對頻繁調(diào)試特別友好。5.3 容器內(nèi)用戶的坑與解決默認(rèn)容器里是root用戶root在共享目錄里創(chuàng)建的文件宿主側(cè)屬主也是root。這會導(dǎo)致一個很尷尬的情況你在deepin主機上想改容器里生成的文件沒有sudo權(quán)限就得干瞪眼。我的解決辦法是讓容器內(nèi)用戶和宿主用戶保持一致的UID。deepin主機上先看自己的UIDid -u然后用-u參數(shù)指定容器內(nèi)以哪個UID運行docker run -it \ --network host \ -u $(id -u):$(id -g) \ -v $HOME/ros2_ws:/ros2_ws \ ros:humble-ros-base這樣容器內(nèi)創(chuàng)建的文件UID和宿主用戶一致宿主側(cè)隨手就能改。但要注意容器內(nèi)很多系統(tǒng)操作需要root權(quán)限用普通UID運行時會受限。我的折中方案是日常編譯和運行節(jié)點用-u指定普通用戶需要裝包時才進root容器。實際操作里我會準(zhǔn)備兩個啟動腳本一個是root版用來apt install一個是普通用戶版用來日常開發(fā)簡單粗暴但很好用。6. 實戰(zhàn)驗證與常見翻車點排查6.1 跑通talker/listener驗證核心鏈路圖形環(huán)境驗證過了再驗證通信鏈路。開兩個終端進入同一個容器或者分別進兩個容器host網(wǎng)絡(luò)下都一樣一邊跑發(fā)布者一邊跑訂閱者# 終端A ros2 run demo_nodes_cpp talker # 終端B ros2 run demo_nodes_cpp listenerlistener能持續(xù)打印I heard: Hello World說明DDS通信鏈路正常。接著可以看話題列表ros2 topic list ros2 topic echo /chatter如果topic能看到但訂閱沒數(shù)據(jù)大概率是DDS發(fā)現(xiàn)協(xié)議出了問題按下面的排查表處理。6.2 全身排查七類典型故障及修復(fù)我把實操中容易踩的坑整理成一張表按現(xiàn)象排錯就行現(xiàn)象根因修復(fù)方案docker: permission denied用戶不在docker組或組未生效newgrp docker或重新登錄拉鏡像超時未配鏡像加速器修改/etc/docker/daemon.json后重啟ros2: command not found未source環(huán)境source /opt/ros/humble/setup.bash兩個容器互相看不到節(jié)點DDS多播被bridge網(wǎng)絡(luò)隔離改用--network hostrviz2報cannot open displayX11授權(quán)或DISPLAY未傳遞檢查xhost local:root和-e DISPLAYrviz2黑屏或崩潰GPU渲染問題設(shè)LIBGL_ALWAYS_SOFTWARE1軟渲染容器時間差8小時未設(shè)時區(qū)-e TZAsia/Shanghai重新運行這里面最隱蔽的是DDS多播問題因為報錯不直觀。多個容器跑起來后ros2 node list只能看到自己別人一概報NODE NOT FOUND。新手很難想到是網(wǎng)絡(luò)模式的問題我還見過有人懷疑是防火墻在deepin上折騰半天最后發(fā)現(xiàn)只是docker bridge網(wǎng)絡(luò)把多播包擋住了。換成host網(wǎng)絡(luò)模式立刻解決。6.3 把容器固化下來docker commit與Compose開發(fā)過程中你會裝一堆額外的包比如turtlesim、rviz2、gazebo插件、自己的功能包。如果容器刪了這些全得重裝所以務(wù)必在環(huán)境搭好后固化鏡像docker commit ros2_humble ros2_humble:dev-v1docker commit會把當(dāng)前容器狀態(tài)打包成新鏡像下次直接用這個鏡像創(chuàng)建容器所有已裝包都在。我習(xí)慣每穩(wěn)定一個階段就commit一次版本號遞增出問題可以回滾。更現(xiàn)代的做法是寫docker-compose.yml把啟動參數(shù)文件化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一條命令啟動參數(shù)全在文件里換機器也方便。我個人覺得docker compose雖然比純命令多一步學(xué)習(xí)成本但長期收益非常大尤其是項目環(huán)境要給別人復(fù)現(xiàn)的時候。最后再分享一個實際工作中的體會deepin下這套dockerROS2方案真正舒服的地方在于deepin桌面本身對開發(fā)環(huán)境干預(yù)少、系統(tǒng)干凈容器跑起來幾乎感覺不到性能損耗。我用了半年多系統(tǒng)一直穩(wěn)定再也沒出現(xiàn)依賴風(fēng)暴把桌面搞崩的情況。如果你是deepin用戶又想在機器人開發(fā)這條路上走遠一點強烈建議花一晚上把docker環(huán)境搭好后面你會感謝這個決定的。