我在 EndeavourOS 上维护了一个 Ubuntu 20.04 + ROS Noetic 的 x86_64 Distrobox。ROS 编译和命令行节点一直正常,但运行 RViz 时,OpenGL 实际落到了 llvmpipe 软件渲染。

这篇不重复 ROS Noetic 的完整安装过程,重点记录如何在保留现有容器和独立 home 的前提下,先克隆验证,再把容器安全改成 NVIDIA 硬件加速模式。

最终环境:

  • 容器名:ros-noetic
  • 容器 home:~/distrobox-homes/ros-noetic
  • 系统:Ubuntu 20.04.6 LTS / amd64
  • ROS:ros-noetic-desktop-full 1.5.0-1focal.20250521.014741
  • 容器 Mesa:21.2.6
  • 独显:NVIDIA GeForce RTX 5070 Ti Laptop GPU
  • 宿主 NVIDIA 驱动:610.43.02
  • OpenGL renderer:NVIDIA / OpenGL 4.6

一、先确认不是设备映射问题

容器可以看到宿主机的 DRI 设备:

BASH
distrobox enter ros-noetic -- bash -lc '
ls -l /dev/dri
for d in /dev/dri/card* /dev/dri/render*; do
  stat -c "%n mode=%a owner=%U:%G" "$d"
done
'

实际存在:

TEXT
card0
card1
renderD128
renderD129

render node 也可以读写。因此问题不是 Distrobox 没有传入 GPU,而是容器里的用户态驱动不认识当前核显。

二、为什么 direct rendering: Yes 仍然可能是软件渲染

宿主机 Intel 核显是 Arrow Lake-S,PCI ID 为 8086:7d67。Ubuntu 20.04 容器里的 Mesa 21.2.6 加载 iris 时会明确报告:

TEXT
MESA: warning: Driver does not support the 0x7d67 PCI ID.
libGL error: failed to create dri screen
libGL error: failed to load driver: iris

随后回退到:

TEXT
direct rendering: Yes
Device: llvmpipe (LLVM 12.0.0, 256 bits)
Accelerated: no
OpenGL renderer string: llvmpipe (LLVM 12.0.0, 256 bits)

所以不能只看 direct rendering: Yes。真正应该检查的是:

  • OpenGL renderer string
  • Mesa renderer 下的 Accelerated
  • 是否出现 llvmpipeswrastfailed to load driver

为了保持 Ubuntu 20.04 + ROS Noetic 依赖稳定,我没有给容器混装第三方 Mesa/LLVM,而是复用宿主机已经正常工作的 NVIDIA 驱动。

三、验证宿主机 NVIDIA PRIME offload

先在宿主机执行:

BASH
nvidia-smi --query-gpu=name,driver_version --format=csv,noheader

__NV_PRIME_RENDER_OFFLOAD=1 \
__GLX_VENDOR_LIBRARY_NAME=nvidia \
glxinfo -B

本机结果:

TEXT
NVIDIA GeForce RTX 5070 Ti Laptop GPU, 610.43.02
OpenGL vendor string: NVIDIA Corporation
OpenGL renderer string: NVIDIA GeForce RTX 5070 Ti Laptop GPU/PCIe/SSE2
OpenGL core profile version string: 4.6.0 NVIDIA 610.43.02

只有宿主机 PRIME offload 正常,才继续改容器。

四、先克隆测试,不直接删除原容器

先停止原容器:

BASH
distrobox stop ros-noetic --yes

创建独立的测试 home,并克隆现有容器:

BASH
mkdir -p ~/distrobox-homes/ros-noetic-nvidia-test

distrobox create \
  --clone ros-noetic \
  --name ros-noetic-nvidia-test \
  --home ~/distrobox-homes/ros-noetic-nvidia-test \
  --nvidia \
  --additional-flags "--env __NV_PRIME_RENDER_OFFLOAD=1 --env __GLX_VENDOR_LIBRARY_NAME=nvidia --env __VK_LAYER_NV_optimus=NVIDIA_only" \
  --yes

这里三个环境变量分别负责:

  • 启用 NVIDIA PRIME render offload;
  • 让 GLX 默认选择 NVIDIA;
  • 让 Vulkan 应用优先只枚举 NVIDIA GPU。

首次进入测试容器时,Distrobox 会显示:

TEXT
Setting up host's nvidia integration... [ OK ]

五、验证测试克隆

进入测试容器:

BASH
distrobox enter ros-noetic-nvidia-test

检查 NVIDIA 驱动和 OpenGL:

BASH
nvidia-smi --query-gpu=name,driver_version --format=csv,noheader
glxinfo -B

测试克隆的实际输出:

TEXT
NVIDIA GeForce RTX 5070 Ti Laptop GPU, 610.43.02
direct rendering: Yes
OpenGL vendor string: NVIDIA Corporation
OpenGL renderer string: NVIDIA GeForce RTX 5070 Ti Laptop GPU/PCIe/SSE2
OpenGL core profile version string: 4.6.0 NVIDIA 610.43.02

再启动一次 RViz:

BASH
source /opt/ros/noetic/setup.bash
rviz

测试时 RViz 持续运行,日志中没有 MESA: warninglibGL errorfailed to load driver

六、用验证过的镜像重建同名容器

distrobox create --clone 会把原容器提交为日期标签镜像。例如本次生成的是:

TEXT
localhost/ros-noetic:2026-07-10

确认测试克隆正常后,停止测试容器,把旧容器暂时改名保留:

BASH
distrobox stop ros-noetic-nvidia-test --yes
podman rename ros-noetic ros-noetic-no-nvidia-backup

用克隆镜像和原来的 home 创建同名容器:

BASH
distrobox create \
  --image localhost/ros-noetic:2026-07-10 \
  --name ros-noetic \
  --home ~/distrobox-homes/ros-noetic \
  --nvidia \
  --additional-flags "--env __NV_PRIME_RENDER_OFFLOAD=1 --env __GLX_VENDOR_LIBRARY_NAME=nvidia --env __VK_LAYER_NV_optimus=NVIDIA_only" \
  --yes

home 是宿主机上的独立目录,因此重新指定同一个 --home 后,代码、shell 配置和用户数据仍然保留;/opt/ros 等系统内容则来自刚才验证过的克隆镜像。

七、冷启动后验证 ROS 通信

不要只在第一次进入时验证。停止并重新进入一次:

BASH
distrobox stop ros-noetic --yes
distrobox enter ros-noetic

再次确认默认 renderer 中没有 llvmpipe:

BASH
glxinfo -B | grep -E \
  'direct rendering|OpenGL vendor|OpenGL renderer|OpenGL core profile version'

然后测试 ROS Master 与话题收发:

BASH
source /opt/ros/noetic/setup.bash

roscore >/tmp/ros-noetic-roscore.log 2>&1 &
master_pid=$!
sleep 3

rostopic echo -n 1 /gpu_test &
echo_pid=$!
sleep 2

rostopic pub -1 /gpu_test std_msgs/String \
  'data: hello_ros_noetic_nvidia'

wait "$echo_pid"
kill "$master_pid"
wait "$master_pid" 2>/dev/null || true

本次实际接收结果:

TEXT
data: "hello_ros_noetic_nvidia"
---

最后确认包数据库没有中断状态:

BASH
dpkg --audit

八、清理测试环境

同名容器通过冷启动、RViz 和 ROS 通信验证后,再删除测试容器与旧备份:

BASH
distrobox rm -f ros-noetic-nvidia-test
podman rm ros-noetic-no-nvidia-backup
rm -rf ~/distrobox-homes/ros-noetic-nvidia-test

不要在新容器验证前提前执行这一步。

九、宿主 NVIDIA 驱动升级后

Distrobox 集成的是与宿主机驱动版本匹配的 NVIDIA 用户态库。宿主机升级驱动后,停止并重新进入容器,让 Distrobox 重新执行集成:

BASH
distrobox stop ros-noetic --yes
distrobox enter ros-noetic

nvidia-smi
glxinfo -B

最终验收标准仍然是 OpenGL renderer string 为 NVIDIA,并且输出中没有 llvmpipe。