背景
在做Robotics相关工作的学习时,常常需要复现一些基于ROS系统开发的算法工作。为了让复现的结果尽可能贴近原工作呈现的效果,以及不想碰到一些奇奇怪怪的编译、环境问题,我总是去安装特定的Ubuntu系统,然后在安装好的系统上安装配置ROS环境。前些年这样的工作也许就只是在ros noetic与ros humble中进行切换,我也就在我的工作电脑上安装了Ubuntu20.04与Ubuntu22.04两个环境。
但随着Claude Code CLI, Codex, Opencode等AI Agent的发展,不使用这种AI工作流进行科研学习已经很难做到。这时候,如果你尝试在Ubuntu20.04上尝试安装一些AI工具,会发现因为过老的软件库版本,很多工具已经不能直接使用apt进行安装。也许你可以通过一些特殊的技巧,自己解决系统中的环境依赖问题,但这也会导致当前进行工作的系统变得复杂,时间久了难以维护。
当然,还有一个原因是我作为一个dotfile爱好者,在两个系统中配置各种工具,实在是一件很蠢的事情,并且不同的系统之间很多时候不能使用同一套配置,你得根据系统的不同去修改你的配置文件。除此之外一些工具也停止对旧的系统进行支持,例如在Ubuntu20.04上,你会因为libwebkit2gtk和libjavascriptcoregtk而无法直接通过apt安装Clash Verge。对我来说还有配NeoVim和Fcitx5的时候遇到的系统gcc版本的问题
既然Ubuntu也无法提供学习时开箱即用的便利,那就决定干脆一点,直接放弃使用Ubuntu。使用Fedora作为我的工作系统,这样不仅能享受到更新的内核更新,享受到各种DE新的特性,还能满足我的折腾需求,何乐而不为呢。至于ROS项目嘛,就交给Docker了。
Docker安装
记录一下在Fedora上安装Docker的过程Docker 官方文档
Docker推荐直接安装Docker Desktop以获得完整的支持,但据我目前的使用感受来说,不管是在Ubuntu上,还是在Windows上,Docker Desktop都挺鸡肋的,平时基本打不开,管理镜像的时候不如命令行方便,加上这玩意儿的GUI与使用DE有一定关系,索性就只安装一些满足需要的包。
sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin对应包的功能:
| 工具 | 作用 |
|---|---|
| Docker Engine | 提供容器运行和管理所需的核心技术,是Docker的“心脏”。 |
| Docker Desktop | 包含Docker Engine、CLI等组件的一体化桌面应用程序,旨在降低使用门槛。 |
| Docker CLI | 与Docker Engine交互的命令行工具,通过指令管理容器、镜像等。 |
| Docker Build | 用于将应用和依赖打包成标准化、可移植容器镜像的工具和生态系统。 |
| Docker Compose | 用于定义和运行多容器应用的工具,通过一个命令就能启动整个服务集群。 |
将当前用户添加到Docker用户组,以便不使用sudo运行docker命令:
# 创建docker组(通常安装时已自动创建)sudo groupadd docker
# 将当前用户加入docker组sudo usermod -aG docker $USER
# 执行以下命令使组权限生效newgrp docker启动服务:
sudo systemctl start dockerTIP这里使用
start而不是各类教程里面提到的enable --now是因为我是在笔记本上进行部署的,我不希望在没有运行镜像需求的时候后台运行docker服务,以节省系统开销
安装验证:
sudo docker run hello-world制作项目镜像
ROS项目复现
拉取镜像
要使用的镜像都可以在Docker Hub上找到(没有魔法可以尝试加速站,如轩辕加速站免费版)
ROS项目复现只需要Docker提供一个集成了对应ROS版本的Ubuntu系统环境即可 ,这里可以直接用ros官方镜像(以ros noetic为例):
# 一般镜像命名ros:<ros版本>-ros-basedocker pull ros:noetic-ros-base配置GUI映射
这部分主要用于解决ROS的一些GUI工具,如Rviz、Gazebo之类的图形化界面的显示问题。
TIPROS1中的工具链不支持Wayland的通信协议,因此在Wayland下只能借助XWayland进行X11转发。
- 确保XWayland正常运行:
ls /tmp/.X11-unix- 安装
xhost工具配置XWayland访问权限:
# 安装 xhostsudo dnf install xhost# 允许所有本地连接访问 X 服务器xhost +local:
# 在完成工作后取消访问权限xhost -local:- Docker显示配置:
# 将宿主机的显示器地址(如 `:0`)传给容器。-e DISPLAY=$DISPLAY进入容器后,初始化 ROS 环境并启动 Rviz。为强制使用 X11 后端,需设置环境变量 QT_QPA_PLATFORM=xcb
# 在容器内执行source /opt/ros/noetic/setup.bashexport QT_QPA_PLATFORM=xcbrviz在容器中启动镜像
docker run -it --rm \ --name my_ros_container \ --network host \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ osrf/ros:noetic-desktop-full \ /bin/bash在 Docker 中运行 ROS 项目的 docker run 命令的关键参数:
-it
-i(interactive):保持 STDIN 打开,即使没有附加终端。这让容器能够持续接收用户输入。-t(tty):为容器分配一个伪终端,能看到的终端具备完整的交互能力(如显示颜色、支持 Ctrl+C 等)。
--rm
容器退出后自动删除容器及其相关的匿名卷。
- 不设置时,容器停止后会保留在本地,占据存储空间,需手动执行
docker rm清理。 - 适合临时、一次性任务,让宿主机环境保持干净。
- 注意:删除后,容器内的所有改动(除非写入挂载的数据卷)都会丢失。
在复现过程中不建议加上这个选项,因为这会删除你辛辛苦苦配置的环境 ::
--name my_ros_container
为容器指定一个自定义名称。
- 不指定时,Docker 会自动生成一个随机名字(如
boring_lamport)。 - 有了名字后,可以直接用
docker exec -it my_ros_container bash进入正在运行的容器,或用于停止、删除等操作,比用容器 ID 更方便。
--network host
使用宿主机网络模式,容器与宿主机共享同一个网络栈。
- 容器内
localhost直接指向宿主机的回环接口。 - 在 ROS 场景下,这非常重要:Rviz 需要连接宿主机上运行的
roscore,或者多个 ROS 节点之间通过标准端口通信时,host网络模式能避免复杂的端口映射和网络转发配置。 - 代价:网络隔离性降低,容器可以直接访问宿主机的所有网络接口。
TIP还有一个隐藏的好处,就是容器可以直接走在宿主机设置的网络代理
-e DISPLAY=$DISPLAY
设置容器内的环境变量。
DISPLAY告诉 X11 客户端应该连接到哪个显示服务器。$DISPLAY是宿主机上当前会话的显示器地址,通常为:0。- 传入该变量后,容器内的 Rviz 等 GUI 程序才知道将窗口渲染到宿主机屏幕上。
-v /tmp/.X11-unix:/tmp/.X11-unix
挂载一个数据卷(Volume),格式为 宿主机路径:容器内路径。
/tmp/.X11-unix目录下存放着 X11 服务器的 Unix 域套接字文件,是 X11 通信的通道。- 挂载后,容器内的应用程序可以通过这个通道与宿主机的 X 服务器(或 XWayland)通信,从而显示图形窗口。
- 使用这个命令还可以直接挂载整个项目:
-v /workspace:/ros_wsros:noetic-ros-base
指定使用的 Docker 镜像。
最后的 /bin/bash 或 command
覆盖镜像默认的启动命令。
- 如果提供
/bin/bash,则容器启动后直接进入一个交互式 shell,可以手动执行后续 ROS 命令。 - 如果希望容器启动后自动运行某个程序,可以直接写,例如
/bin/bash -c "source /opt/ros/noetic/setup.bash && rviz"。
其他配置
-d:后台运行容器,不占用终端(适合长期运行的服务,但需与其他命令如exec配合交互)。-p 8080:80:端口映射,将容器内 80 端口映射到宿主机 8080 端口(仅在bridge网络模式下有效,host模式下无效)。--gpus all:启用 GPU 支持,需安装 NVIDIA Container Toolkit。--device /dev/dri:挂载宿主机 DRM 设备,用于显卡硬件加速。--user $(id -u):$(id -g):以当前用户的 UID 和 GID 运行容器,避免权限问题。
ROS 开发
除了复现之外,可能选定了某个idea,想要在现有的工作上进行开发,这时候虽然也能直接在基础的镜像上进行开发,但为了让环境可移植(和其他人协同工作,或者单纯想在自己电脑和实验室电脑中的环境保持一致)。这时候可以将上述环境打包成DockerFile, 别人想用的时候直接构建镜像就好了。
镜像制作
以我现在正在做的Avoid MPC为例,可以编写如下的一个DockerFile:
# 拉取镜像FROM osrf/ros:noetic-ros-base
# 使用 bash 以便 sourceSHELL ["/bin/bash", "-c"]
# --- 1. 安装系统工具和 ROS 包依赖 ---RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ # 通用工具 python3-catkin-tools \ python3-pip \ git \ wget \ unzip \ # 线性代数、优化、点云等库 libmetis-dev \ gfortran \ libeigen3-dev \ libpcl-dev \ # 项目所需的 ROS 包 ros-noetic-mavros \ ros-noetic-mavros-extras \ ros-noetic-pcl-conversions \ ros-noetic-tf2-sensor-msgs \ && rm -rf /var/lib/apt/lists/*
# --- 2. 从源码编译 CasADi 并安装到 /opt/casadi ---WORKDIR /tmp/casadi_buildRUN git clone https://github.com/casadi/casadi.git -b 3.6.4 && \ mkdir -p casadi/build && cd casadi/build && \ cmake .. \ -DWITH_BUILD_IPOPT=ON \ -DWITH_BUILD_MUMPS=ON \ -DWITH_IPOPT=ON \ -DWITH_MUMPS=ON \ -DWITH_OPENMP=ON \ -DCMAKE_INSTALL_PREFIX=/opt/casadi && \ make -j$(nproc) && \ make install && \ # 清理临时文件 rm -rf /tmp/casadi_build
# 安装 Python 版 CasADi(用于生成 MPC 库)RUN pip3 install casadi
# --- 3. 设置环境变量(每次启动容器时自动生效)---# 将 CasADi 路径写入系统环境,ROS 工作空间稍后在运行时 sourceRUN echo 'export LD_LIBRARY_PATH=/opt/casadi/lib:$LD_LIBRARY_PATH' >> /etc/bash.bashrc && \ echo 'export CMAKE_PREFIX_PATH=/opt/casadi:$CMAKE_PREFIX_PATH' >> /etc/bash.bashrc && \ echo 'source /opt/ros/noetic/setup.bash' >> /etc/bash.bashrc
# 为方便,创建一个工作目录(不是 ROS 工作空间,只是挂载点)WORKDIR /ros_ws使用该DockerFile构建镜像:
docker build --network host -t avoid_mpc_env:noetic .NOTE这里使用
--network标签是因为构建过程中用到了git拉取远程仓库,我需要走我宿主机上的代理,才能顺利访问github
镜像使用
与上文提到的ROS项目复现一致,这时候镜像中已经包含了该项目需要的所有环境,想要运行直接在容器中启动这个容器即可。当然你也可以在项目中添加这么一个脚本,更优雅地启动:
#!/bin/bash
# xhost
xhost +local: > /dev/null 2>&1
# run docker (这时候可以加上--rm标签了)
docker run -it --rm --name avoid_mpc_dev \ --network host \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -v "$HOME/workspace/Avoid-MPC:/ros_ws" \ avoid_mpc_dev:noetic \ /bin/bash
# xhost off# xhost -local: > /dev/null 2>&1