前言

众所周知,ROS由于对于系统软件包版本的高度依赖,导致每个大版本ROS都必须绑定着新版Ubuntu系统发布,例如:

这里只是简单列出一些,并不完整,详细可以参考:

https://blog.csdn.net/qq_42450767/article/details/124738627

ROS版本

Ubuntu版本

ROS1-Melodic

18.04(Bionic Beaver)

ROS1-Neotic

20.04(Focal Fossa)

ROS2-Foxy Fitzroy

20.04(Focal Fossa)

ROS2-Galactic Geochelone

20.04(Focal Fossa)

ROS2-Humble Hawksbill

22.04(Jammy Jellyfish)

ROS2-Iron Irwini

22.04(Jammy Jellyfish)

但是某些嵌入式设备有时并没有最新的Ubuntu版本支持(没错,说的就是你NVIDIA),例如我手上这台Jetson Xavier NX,其官方提供的最新的JetPack 5.1.3版本也仅仅停留在Ubuntu 20.04要想用上22.04或更高的版本,要么自己折腾,要么换新设备例如Orin系列……

因此为了在这种几乎无法更新到新版本Ubuntu发行版的设备上安装ROS2的新版本,使用Docker安装是一个更好的选择。

但这里还有一个坑,在Jetson上不推荐直接使用公共Docker Image,NVIDIA为一些常用的镜像编译了适合Jetson的版本,仓库:

https://github.com/dusty-nv/jetson-containers/tree/master

配置系统环境

https://github.com/dusty-nv/jetson-containers/blob/master/docs/setup.md

此部分对官方教程作简单提取翻译,但可能过时(截至2024/08/14),建议有能力者直接阅读原文教程。

检查系统版本

官方建议安装当前型号支持的最新的JetPack版本(使用官方工具刷入),目前最新版本如下:

Nano/TX1/TX2:JetPack4

Xavier:JetPack5

Orin:JetPack6

需求的最低版本:

  • JetPack 4.6.1+ (>= L4T R32.7.1)

  • JetPack 5.1+ (>= L4T R35.2.1)

  • JetPack 6.0 DP (L4T R36.2.0)

如果你不确定当前的JetPack版本,可以用jtop工具看一下(安装jtop

确定系统版本之后,就可以继续配置了,否则你可能需要重新刷一下系统镜像。

安装Jetson Containers

先把仓库克隆到本地,再执行安装:

git clone https://github.com/dusty-nv/jetson-containers
bash jetson-containers/install.sh

安装过程中会询问密码,并且安装Python依赖(通过链接到/usr/local/bin下的文件)

注意

  • 如果你没有更换国内镜像源(aptpip),建议更换之后再运行脚本,否则可能下载出错

  • 其中有git clone的过程,如果发现频繁卡在这一步,可以按照以下命令配置代理

git config --global https.proxy https://x.x.x.x:7890  # 替换成你代理的地址和端口
git config --global http.proxy http://x.x.x.x:7890    # 替换成你代理的地址和端口
  • 如果你移动了jetson-containers仓库文件夹,你需要重新运行安装脚本

  • 如果你的系统安装在了eMMC上而不是直接运行在NVMe固态硬盘上,建议参考这部分教程将Docker移动到外部磁盘上

https://github.com/dusty-nv/jetson-containers/blob/master/docs/setup.md#docker-default-runtime

更改Docker默认运行时

如果要在Jetson上构建镜像,需要将默认运行时设为nvidia,只有这样NVCC编译器和GPU才可以在docker build过程中被使用。

编辑/etc/docker/daemon.json,添加以下内容

{
    "runtimes": {
        "nvidia": {
            "path": "nvidia-container-runtime",
            "runtimeArgs": []
        }
    },

    "default-runtime": "nvidia"
}

(默认情况下,只需要添加"default-runtime"那一行就可以了)

如果你之前没有更换过Docker国内源,推荐在这里顺便更换一下:

https://patzer0.com/archives/configure-docker-registry-mirrors-with-mirrors-available-in-cn-mainland

禁用系统桌面GUI(可选)

如果你使用的是内存较小的设备(例如JetsonNano),可以尝试禁用GNOME桌面来增加可用内存(Unity/GNOME 约 800MB,LXDE 约 250MB)

暂时禁用

sudo init 3     # 停止桌面
# 禁用桌面之后,使用 Ctrl+Alt+F1, F2, 等快捷键让当前用户登陆到终端
sudo init 5     # 重启桌面

永久禁用

sudo systemctl set-default multi-user.target     # 禁用桌面开机自启
sudo systemctl set-default graphical.target      # 启用桌面开机自启

我手头的Xavier NX禁用桌面自启之后,直接从1.6G左右的内存占用降低到了0.8G左右,效果还是非常明显的,尤其是这类小内存设备上,此举可以大幅提高可用内存

添加用户到Docker组

默认情况下,Ubuntu用户不属于docker用户组,执行docker命令都需要使用sudo,这会造成一些麻烦,添加到用户组即可解决:

sudo usermod -aG docker 你的用户名

记得注销重新登录来使设置生效。

测试一下:

docker info

如果配置正确,不需要sudo也能正常运行此命令。

设置Jetson性能模式

如果你关闭了桌面自启(或者你切换到了其他桌面环境),你就需要通过命令行工具来切换性能模式了:

sudo nvpmodel -q    # 显示当前的性能模式
sudo nvpmodel -m 0  # 通常是最高性能模式

安装ROS2

拉取镜像

由于硬件SDK需要,我这里选择humble版本安装,如果你需要安装其他版本,依然可以参考此教程。

Dokcer Hub仓库https://hub.docker.com/r/dustynv/ros/tags

在里面找到适合自己的版本,然后Copy命令到Jetson上执行即可。

注意:镜像结尾标明最低需要的L4T版本,请确认你的系统版本高于此版本,否则无法正常运行

启动容器

注意:经过测试发现,使用MobaXTerm以及XShell+XManager的远程X11转发方案,在Jetson重启之后都无法继续正常转发Docker内的GUI程序,建议使用VNC或NX(NoMachine)远程桌面继续下面的步骤,以及后续使用ROS2的GUI程序。

先使用jetson-containers命令启动,而不是一开始就直接使用docker(需要添加巨量参数,而jc会自动为你做这件事):

jetson-containers run -v /主机/路径:/容器/路径 <镜像名称>
# 例如我想要:
# 启动这个镜像 dustynv/ros:humble-desktop-pytorch-l4t-r35.4.1
# 挂载 /home/jetson/ros2_ws 到容器内的 /home/workspace
# 那么命令将是:
# jetson-containers run -v /home/jetson/ros2_ws:/home/workspace dustynv/ros:humble-desktop-pytorch-l4t-r35.4.1

提示:使用jetson-containers run命令时可以添加任何docker run支持的参数,都会传递给docker run

执行结果:

(可以看到确实有非常多的参数……包括文件夹链接和设备挂载等)

并且,运行之后,当前终端会默认进入到这个容器内(当前身份已经变成了root@ubuntu:/#

但当你退出(exit)之后,会发现这个容器直接消失了,这是因为jetson-containers默认创建的命令中带有了--rm参数,这使得容器会在关闭之后销毁,因此我们需要修改之前jetson-containers给出的命令,

将之前给出的命令复制下来,然后去掉--rm,再加上一条--restart=always,确保容器能一直运行。

建议加一个参数指定容器名称,例如--name ros2_humble,注意必须是英文,这样后续使用exec时会方便一些。

修改后的命令如下(不同版本Jetson/ROS/Docker镜像都有可能导致命令不同,不建议套用,仅供参考)

docker run --runtime nvidia -it --rm --network host \
    --volume /tmp/argus_socket:/tmp/argus_socket \
    --volume /etc/enctune.conf:/etc/enctune.conf \
    --volume /etc/nv_tegra_release:/etc/nv_tegra_release \
    --volume /tmp/nv_jetson_model:/tmp/nv_jetson_model \
    --volume /var/run/dbus:/var/run/dbus \
    --volume /var/run/avahi-daemon/socket:/var/run/avahi-daemon/socket \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume /home/jetson/jetson-containers/data:/data \
    --device /dev/snd \
    --device /dev/bus/usb \
    -e DISPLAY=:0 \
    -v /tmp/.X11-unix/:/tmp/.X11-unix \
    -v /tmp/.docker.xauth:/tmp/.docker.xauth \
    -e XAUTHORITY=/tmp/.docker.xauth \
    --device /dev/i2c-0 \
    --device /dev/i2c-1 \
    --device /dev/i2c-2 \
    --device /dev/i2c-3 \
    --device /dev/i2c-4 \
    --device /dev/i2c-5 \
    --device /dev/i2c-6 \
    --device /dev/i2c-7 \
    --device /dev/i2c-8 \
    --device /dev/i2c-9 \
    -v /home/jetson/ros2_ws:/home/workspace \
    dustynv/ros:humble-desktop-pytorch-l4t-r35.4.1

若要再退出后再次进入容器,使用:

docker exec -it <容器ID或名称> /bin/bash
# 例如 docker exec -it ros2 /bin/bash

容器内配置

补全~/.bashrc

如果退出容器之后再进入,你可能会发现,在这里无法执行ros2命令,按下TAB发现只有这三个命令:

root@ubuntu:/# ros
rosdep                rosdep-source         rosinstall_generator

这是因为默认的.bashrc文件没有sourceROS2的setup.bash,在初次启动的时候docker自动为你做了这件事,后续手动进入bash则没有,因此需要修改一下.bashrc

nano ~/.bashrc

在最底部添加:

source /opt/ros/humble/install/setup.bash

这里的路径每个ROS版本会不同,我这里安装的是humble版本便在此路径下,如果你安装的是其他版本镜像,手动进/opt/ros目录找一下具体的路径即可。

让设置生效(也可以退出重进):

source ~/.bashrc

这时你就能发现,ROS2的其他命令都有了:

root@ubuntu:/# ros
ros2                  rosdep                rosdep-source         ros-discovery         rosidl                rosinstall_generator

重启后ROS容器无法启动

重启之后,使用docker ps -a查看所有容器,会发现原本设置了--restart=always的容器此时却没有启动

如果尝试使用docker start ros2手动启动,会直接报错:

搜索了一下,发现有很多人遇到这个问题:

https://github.com/dusty-nv/jetson-containers/issues/242https://github.com/dusty-nv/jetson-containers/issues/212

总结一下,解决办法就是重新创建/tmp/.docker.xauth这个文件,但直接执行原issue solution中那几行也是不行的,这里给出一个完整脚本:

sudo rm -rf /tmp/.docker.xauth
touch /tmp/.docker.xauth
sudo xhost +si:localuser:root
XAUTH=/tmp/.docker.xauth
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
chmod 777 $XAUTH

执行完这些,就可以正常运行容器了,并且GUI程序也可以正常运行:

为了让每次开机容器都能正常启动,直接将脚本写入/etc/rc.local,就会在docker启动之前执行这些脚本,以便让容器正常启动。

sudo nano /etc/rc.local

填入以下内容

#!/bin/bash
sudo rm -rf /tmp/.docker.xauth
touch /tmp/.docker.xauth
sudo xhost +si:localuser:root
XAUTH=/tmp/.docker.xauth
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
chmod 777 $XAUTH

exit 0

保存后,添加执行权限

sudo chmod +x /etc/rc.local

接下来,试着重启一下Jetson,看看容器能否自动启动:

注意:使用MobaXTerm或XShell+XManager进行远程转发的方案(从创建容器开始一直使用,因此不是$DISPLAY设置错误的问题)在重启Jetson后就无法正常转发GUI程序了(即便已经执行了上面的脚本),错误如下,暂时还没找到解决办法,但使用VNC或NX远程桌面则没有问题。

ROS教程推荐

顺带推荐一下小鱼家的ROS教程,个人觉得还是很不错的:

https://fishros.com/d2lros2