尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Jetson Nano Docker GPU加速实战:从运行时配置到CUDA容器迁移

Jetson Nano Docker GPU加速实战:从运行时配置到CUDA容器迁移 简介面向 Jetson Nano 等 ARM64 平台的 Docker 部署入门文档适合需要在嵌入式设备上搭建深度学习推理环境的开发者。内容围绕 docker 安装、nvidia-docker 运行时配置与 GPU 调用展开从 apt 安装 docker-ce到安装 nvidia-container-runtime再到修改 daemon.json 将 nvidia 设为默认运行时覆盖了镜像构建、容器启动时映射 /dev/nvhost-* 设备节点与 tegra 驱动目录等关键环节。文档整理为 1 个 docx 文件共 208KB按安装与部署顺序梳理便于对照操作。已有 157 人浏览学习相比网上零散的教程这份资料把 Jetson 平台与 GPU 容器结合时容易踩坑的设备映射、库路径挂载等问题集中说明并给出了可执行的 Dockerfile 示例对于想快速跑通 nvidia-docker 并调用 GPU 的开发者可以节省不少排查时间。文档中 deviceQuery 容器测试与 Dockerfile 基础镜像选择等细节对有 CUDA 环境诉求的用户也很有参考价值作者还提供了配套免费博文方便进一步查阅迁移部署细节。1. 在 Jetson Nano 上让 Docker 调用 GPU为什么刚装好的 Docker 跑不了 CUDA 容器很多人拿到 Jetson Nano 的第一反应是先装个 Docker装完跑 hello-world 一切正常接着docker run --gpus all nvidia/cuda直接报错 could not select device driver。这不是命令写错了而是 Jetson 的 GPU 访问链路和 PC 完全不一样Tegra 的驱动不在镜像里CUDA 库在宿主机的 /usr/lib/aarch64-linux-gnu/tegra 下GPU 设备则分散在一组 /dev/nvhost-* 节点里缺一个容器就拿不到计算资源。这篇文章把我在 Jetson Nano 上从零走通的完整流程拆开讲Docker 安装、NVIDIA Container Runtime 配置、daemon.json 默认运行时、Dockerfile 构建、设备节点挂载最后是镜像迁移。每一步都给出能直接复制的命令坑单独放在第五章按现象找原因就行。这篇适合想在自己板子上跑 CUDA 容器、想把整套环境固化下来迁到别的 Jetson 设备上的读者照着做基本一遍过。2. 安装 Docker 与 NVIDIA Container Runtime从 apt 源到运行时验证Jetson 上的 JetPack 底层就是 UbuntuDocker 本体可以走官方 Ubuntu 安装流程但有几个参数必须按 ARM 平台调整。我按实际操作的顺序写每一步说明为什么这么写遇到 404 或者依赖缺失也知道去哪查。2.1 先装 Docker 本体apt 源与架构参数一次配好先把基础工具装齐然后用官方 GPG 密钥配置 apt 源最后安装 docker-ce 三件套。如果你之前用 apt 装过 docker 或 containerd先sudo apt remove docker docker-engine docker.io containerd runc清干净并把 /var/lib/docker 改名备份避免升级残留冲突这是从旧系统带着配置过来最容易翻车的地方。sudo apt-get update sudo apt-get install -y \ apt-transport-https \ ca-certificates \ curl \ gnupg \ lsb-release curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo \ deb [archarm64 signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io第一组 apt-get install 装的是 apt 走 HTTPS 和校验 GPG 所需的前置工具。curl 那行把 Docker 的公钥下载下来用 gpg --dearmor 转成二进制放到 /usr/share/keyrings 下这样 apt 校验源签名时不会弹 warning。注意输出文件名是 docker-archive-keyring.gpg后面 apt 源里 signed-by 必须指向同一个文件路径写错会直接导致后续 update 失败。echo 那行写 apt 源archarm64是本次安装最容易错的地方写成 amd64 会在 apt update 时提示架构不匹配什么都不写虽然也能装上但 apt 会拿错架构的包列表。$(lsb_release -cs)自动取系统代号JetPack 4.x 对应 Ubuntu 18.04bionicJetPack 5.x 对应 20.04focal写成变量后换系统版本不用改配置。装完确认两件事docker --version sudo systemctl status docker如果 docker 命令存在但服务没起来先看journalctl -u docker再继续常见原因是 daemon.json 写错导致 dockerd 拒绝启动或者和已存在的 containerd 版本冲突。顺手把当前用户加进 docker 组sudo usermod -aG docker $USER之后不用每条命令都带 sudo。这里比 PC 上省心的点在于Jetson 走的是 Linux 原生 Docker 加 NVIDIA Container Runtime 的路线不会遇到 Docker Desktop 在 PC 上常见的 virtualization support not detected 这类虚拟化层问题少了一层排查。2.2 安装 nvidia-container-toolkit 与 runtime顺序反了会白折腾x86 上很多人直接apt install nvidia-docker2就完事Jetson 不行。Tegra 平台 GPU 驱动不是通过 PCIe 暴露的nvidia-docker2 的 hook 在 Jetson 上找不到硬件必须改用 nvidia-container-runtime 加 nvidia-container-toolkit 这套组合。执行curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - distribution$(. /etc/os-release;echo $ID$VERSION_ID) echo $distribution curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit sudo apt-get install -y nvidia-container-runtime sudo systemctl restart dockerdistribution$(...echo $ID$VERSION_ID)这行是关键它会拼出 ubuntu18.04 或 ubuntu20.04 这样的字符串下一行 curl 用这个字符串拼仓库路径。如果这里输出是空的检查 /etc/os-release 是否存在部分 JetPack 精简版系统会缺这个文件手工填 distributionubuntu18.04 也能走通。两个 apt install 的顺序建议固定toolkit 里包含 nvidia-container-cliruntime 依赖它做设备注入。先装 runtime 再装 toolkit 也能装但 toolkit 装完会覆盖更新 runtime 的配置等于白折腾一遍。装完不重启 Dockerruntime 不会生效后面docker info里看不到 nvidia。我见过不少人卡在这一步误以为没装成功反复重装了好几轮。如果仓库 404可以单独下载 nvidia-container-runtime-hook 的 deb 包手工装wget https://nvidia.github.io/nvidia-container-runtime/ubuntu14.04/amd64/./nvidia-container-runtime-hook_1.4.0-1_amd64.deb sudo dpkg -i nvidia-container-runtime-hook_1.4.0-1_amd64.deb sudo apt-get -f install注意这里包的发布路径是上游仓库的遗留命名里面带了 ubuntu14.04 和 amd64实际上是个通用 hook 包arm64 的 Jetson 系统装了也能正常用。dpkg 报依赖缺失就执行sudo apt-get -f install补齐再继续。2.3 配置前的基线验证docker info 与 deviceQuery 的预期输出现在先别急着改配置文件跑两条命令记录未配置状态后面才有对比docker info | grep -i runtime docker run -it --rm jitteam/devicequery ./deviceQuery第一条命令未配置时通常只显示 runc如果显示 runc 和 nvidia 都出现说明系统里已经存在旧配置第 3 章的改动会被旧配置影响。第二条命令用 Jetson 社区常用的 jitteam/devicequery 镜像里面打包了 NVIDIA 官方 deviceQuery 程序的交叉编译产物专门用来验证容器能否访问 GPU。关键点第一次跑理想输出是ResultFAIL。这不是失败而是基线——Docker 默认用 runc 启动容器容器里没有任何 GPU 资源deviceQuery 探测不到设备自然返回 FAIL。看到 FAIL 不需要重装驱动只要容器能正常启动、能看到 CUDA 相关日志就说明 Docker 本体没问题。更稳妥的验证方式是同时看容器的 exit code如果 docker run 直接报错退出说明容器没起来问题在 Docker 侧如果容器起来了、输出了 FAIL问题只在 GPU 资源分配这是第 3 章要解决的事。提示日志里出现 could not select device driver说明 dockerd 的 runtimes 列表里还没有 nvidia这是配置问题不是驱动问题别卸载重装。3. 把 nvidia 设为默认运行时daemon.json 两个字段与三次确认装完 runtime 只完成了一半。Docker 默认还是用 runc 启动所有容器必须显式告诉 dockerd nvidia 这个运行时怎么调、要不要作为默认。这一步不配置GPU 容器永远跑不起来而且报错非常误导人。3.1 daemon.json 字段解读default-runtime 与 runtimes 缺一不可修改 /etc/docker/daemon.json内容如下{ default-runtime: nvidia, runtimes: { nvidia: { path: nvidia-container-runtime, runtimeArgs: [] } } }两个字段各管一件事。runtimes是注册表告诉 dockerd 存在一个名为 nvidia 的运行时它的可执行文件是 nvidia-container-runtimeruntimeArgs 留空数组表示不需要额外参数。default-runtime是全局开关把 nvidia 设为默认后以后所有 docker run 启动的容器都自动走 nvidia-container-runtime不用每条命令都带--runtime nvidia。我每次改这个文件前都会先备份sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bakdaemon.json 本身是可选的如果之前不存在直接新建。JSON 里不能有注释不能有尾逗号键名不能写错。最容易犯的低级错误是把 default-runtime 写进 runtimes 里面或者把 runtimes 写成单数 runtime这两处写错 dockerd 都会启动失败错误信息却只是 generic 的 failed to start daemon不仔细看 journalctl 根本定位不到。改完后先做一次语法校验再重启sudo dockerd --config-file /etc/docker/daemon.json这条命令会前台启动 dockerd如果 JSON 格式有问题会直接打印出具体的解析错误行号。确认没有报错后 CtrlC 退出再执行sudo systemctl restart docker。我习惯把这条验证写进操作清单它能把配置错误和系统问题在 10 秒内分开。3.2 改完配置的完整验证路径重启、grep、再跑一次 deviceQuery重启后按顺序做三次确认缺一次都可能在后面埋雷。sudo systemctl restart docker docker info | grep -i runtime正常情况下输出包含以下两行Runtimes: nvidia runc Default Runtime: nvidia如果 Runtimes 里有 nvidia 但 Default Runtime 不是 nvidia说明 default-runtime 字段没写对或者拼写和 runtimes 里的 key 不一致。如果 Runtimes 里压根没有 nvidia说明 daemon.json 没被加载检查文件路径和权限/etc/docker/daemon.json 需要 root 可读一般 644 权限没问题。确认注册成功后再跑一次 deviceQuery这次显式指定运行时docker run -it --runtime nvidia jitteam/devicequery ./deviceQuery这次结果应该是ResultPASS设备名称显示 Jetson 的 GPU 型号。跑完 PASS 后再用默认运行时跑一次不带 --runtime 参数的版本确认 default-runtime 确实生效。两次都 PASSGPU 容器链路才算真正打通可以进入第 4 章构建自己的镜像。3.3 为什么设成默认运行时省掉的不仅是命令参数有人会问不设 default-runtime每次手动加--runtime nvidia不也一样表面看一样实际有两个隐患。第一个是环境变量丢失。nvidia-container-runtime 在启动容器时会根据宿主机注入 CUDA 相关环境变量比如 CUDA_VERSION、NVIDIA_VISIBLE_DEVICES。如果忘记带--runtime nvidia容器里即便有 CUDA 库也识别不到设备报错还特别隐蔽程序可能直接段错误。第二个是编排工具的兼容性。docker-compose 对 runtime 字段的支持在部分版本上不完整compose 文件里没写 runtime 时容器就会走默认的 runcGPU 访问静默失败。设成默认 runtime 之后所有容器不管怎么起都走同一个运行时少踩很多坑。但默认运行时不是万能的它有明确的边界daemon.json 只解决用哪个运行时启动容器设备节点和 tegra 库的挂载仍然要自己在 docker run 或 compose 里写。换句话说第 3 章解决的是运行时选择第 4 章构建镜像第 5 章的设备挂载解决的是容器里能看到哪些 GPU 资源三件事分开理解排查时才不会互相绕。4. 用 Dockerfile 构建可用的 CUDA 镜像基础镜像、依赖与 build 参数运行时配好了接下来要做的不是直接拉现成的 CUDA 镜像而是构建一个符合 Jetson Tegra 环境的容器。很多 PC 上的 CUDA 镜像在 Jetson 上跑不起来因为它们没有针对 Tegra 的库路径做处理。这一章从 Dockerfile 逐行拆解到 build 命令把每个依赖为什么装说清楚。4.1 Dockerfile 逐段拆解基础镜像、时区与依赖创建一个工作目录并新建 Dockerfilemkdir docker-test cd docker-test vi DockerfileDockerfile 内容如下我按构建顺序逐段说明FROM arm64v8/ubuntu:16.04 ENV LD_LIBRARY/usr/lib/aarch64-linux-gnu/tegra RUN mkdir /cudaSamples COPY deviceQuery /cudaSamples/ ENV LANG C.UTF-8 RUN apt-get update -y apt-get upgrade -y RUN apt-get install -y tzdata ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN apt-get -y install vim RUN apt-get install -y libsqlite3-dev RUN apt-get -y install gcc RUN apt-get install libssl-dev -y RUN apt-get install make RUN apt-get -y install zlib* RUN apt-get -y install wget基础镜像选 arm64v8/ubuntu:16.04这是 JetPack 4.x 时代最常见的系统基座Tegra 的库文件路径按 aarch64 结构组织。ENV LD_LIBRARY这一行原文里写的是 LD_LIBRARY实际作用等价于后续容器内的库搜索路径前缀建议写成ENV LD_LIBRARY_PATH/usr/lib/aarch64-linux-gnu/tegra否则运行 deviceQuery 时动态链接器找不到 libcuda.so。COPY deviceQuery 这行要求构建目录里必须存在一个名为 deviceQuery 的可执行文件它是从 jitteam/devicequery 镜像里拷出来的或者你在宿主机交叉编译得到的 arm64 版本。这个文件缺失时 docker build 会直接报 COPY failed: file not found。后面几行按需安装tzdata 配合ln -sf把时区固定到上海避免后续 Python 或 OpenCV 运行时报 timezone 警告libsqlite3-dev 是编译 Python 时 sqlite3 模块的依赖gcc 和 make 是源码编译必需libssl-dev 解决 Python 编译时 No module named _ssl 的问题zlib* 是很多第三方库编译时的通用依赖。这里有个优化点可以把多个 RUN 合并成一个 RUN用 连接减少镜像层数。我一般会把依赖安装合并成一条只有补充工具单独留一层便于后续修改。上面的 Dockerfile 保持了原始风格的拆分便于你看清每个包的作用实际构建时可以按我的习惯合并。4.2 docker build 构建上下文目录与镜像命名Dockerfile 写好后执行构建。这一步的关键不在命令本身而在你执行命令时所在的目录docker build -t nano_env_img .命令最后的点代表构建上下文目录docker 会把当前目录整个打包发给 dockerd。所以执行这行命令前必须确认已经在 docker-test 目录里而且 deviceQuery 文件就在这个目录下否则 COPY 指令找不到源文件。镜像名 nano_env_img 可以任意取我习惯带项目名和日期比如 jp46-ai-env-0215方便后面迁移时辨认。构建过程中如果 apt-get 走到一半卡住多数是网络波动直接重新跑一次 docker build 即可Docker 会复用已经完成的层不会从头再来这个特性在慢速网络下特别好用。构建完成后确认镜像存在docker images | grep nano_env_img docker run -it nano_env_img /bin/bash能进入容器说明基础构建通过。这一步先不挂设备节点因为接下来第五章才讲运行容器的完整参数。4.3 准备运行容器所需的路径清单运行 GPU 容器前先确认宿主机的设备节点和库目录都存在顺手检查一下别等 docker run 报错了再排查ls -l /dev/nvhost-* /dev/nvmap ls -l /usr/lib/aarch64-linux-gnu/tegra | head如果设备节点不存在说明 JetPack 的 BSP 驱动没加载完全后续挂载无从谈起如果 tegra 目录为空CUDA 运行库缺失需要重新刷 JetPack 或者在宿主机装对应的 CUDA-arm 包。这里提一下 arm 版 CUDA 工具链官方有单独的 aarch64 发布通道Jetson 上的 CUDA 版本必须和 JetPack SDK 版本对齐不能用 x86 的 CUDA 安装包替换。这两个检查都通过后第 5 章给出的运行命令才能真正生效。5. 避坑Jetson Nano GPU 容器最常见的 5 个翻车现场这一章是把前面每一处的踩坑记录集中起来。每条按现象、原因、解决展开你遇到哪个就直接定位到哪条。5.1 现象daemon.json 改完Docker 服务起不来sudo systemctl restart docker后systemctl 提示 faileddocker info直接报 Cannot connect to the Docker daemon。查 journalctl -u docker 会看到一段 dockerd 启动失败的日志但错误内容很泛看不出具体原因。原因基本就两个daemon.json 的 JSON 语法错误或者键名写错。最常见的是把 default-runtime 写进了 runtimes 对象内部或者 runtimes 误写成 runtime导致默认字段解析不到。解决方法是先把配置备份然后用sudo dockerd --config-file /etc/docker/daemon.json前台启动错误信息会精确到第几行第几个字符。修好后再用 systemctl 启动并重新执行docker info | grep -i runtime确认 Default Runtime 字段出现。5.2 现象容器能起来但 deviceQuery 报找不到 libcuda 或 tegra 相关库容器能进入 shell但执行 ./deviceQuery 时提示error while loading shared libraries: libcuda.so.1: cannot open shared object file。原因是在 Jetson 上GPU 运行库不在镜像里而在宿主机的 /usr/lib/aarch64-linux-gnu/tegra 目录下。单靠 nvidia-container-runtime 不会自动挂载这个目录必须手动通过 -v 参数映射到容器内相同路径。解决方式是在 docker run 里加上-v /usr/lib/aarch64-linux-gnu/tegra:/usr/lib/aarch64-linux-gnu/tegra并在 Dockerfile 里显式设置ENV LD_LIBRARY_PATH/usr/lib/aarch64-linux-gnu/tegra。只挂载目录但没设置环境变量还是可能因为 link 解析不到而失败两个动作一起做才稳。5.3 现象设备能识别但 deviceQuery 最终 ResultFAIL容器能启动deviceQuery 也能找到显卡型号但结果始终是 FAIL或者报 CUDA-capable device 数量为 0。原因是在容器启动时没有把 GPU 设备节点传进去。CUDA 在 Jetson 上访问 GPU 不依赖 /dev/nvidia0而是依赖一组 Tegra 专属节点/dev/nvhost-ctrl、/dev/nvhost-ctrl-gpu、/dev/nvhost-prof-gpu、/dev/nvmap、/dev/nvhost-gpu、/dev/nvhost-as-gpu。这些节点分别负责 GPU 控制、profiling、内存映射等漏传任何一个都可能导致初始化失败。解决方式是把六个节点全部用 --device 参数加进 docker run 命令。注意有些教程只传 nvhost-gpu 和 nvmap结果 container 能跑起来但 CUDA 的 context 创建失败因为缺了 nvhost-ctrl-gpu。完整的六个我列在第六章的运行命令里直接复制即可。5.4 现象容器内编译 Python报 No module named _ssl在容器内源码编译 Python 3.6.5make 顺利通过但运行 python 后在 import ssl 时报 No module named _sslpip 也完全没法用因为 pip 依赖 ssl 模块。原因是编译 Python 前没有安装 OpenSSL 的开发头文件configure 阶段检测不到 libssl于是生成的 Python 里没有 _ssl 模块。如果你没有在 Dockerfile 里安装 libssl-dev直接 ./configure make默认情况下 _ssl 是缺失的。解决方式是在编译前先apt-get install libssl-dev -yconfigure 时带--with-ssl然后再 make install。如果已经编译完成需要清理后重来在源码目录执行make clean补齐依赖后重新 configure 和 make。5.5 现象docker build 慢到怀疑人生中途还报网络错误在 Jetson Nano 上构建镜像apt-get update 经常卡在某个奇怪的 1%然后超时或者 docker build 反复提示 unable to resolve host构建进程断掉。原因有两方面一是小内存设备上 dockerd 和 build 进程资源竞争严重二是镜像里的 apt 源在板子网络环境下延迟很高。另外如果你在容器里不断执行单个 RUN apt-get 装包每一层都要重新解析依赖慢上加慢。解决方式是把多个 apt-get install 合并成一条 RUN减少层数和解析次数如果网络确实不稳定适当增加 apt 的重试参数RUN apt-get update -y \ apt-get install -y --no-install-recommends \ libsqlite3-dev gcc libssl-dev make zlib1g-dev wget \ rm -rf /var/lib/apt/lists/*--no-install-recommends 能少装很多无关推荐包显著缩短构建时间。rm -rf 清理 apt 缓存把镜像层体积压下来。这些改动不影响功能但对 Jetson Nano 这种小内存设备的构建体验提升非常大。6. 保存镜像并迁移到另一台 Jetsondocker commit 后的三个检查点环境配置完毕最划算的收尾方式是把容器固化成镜像然后迁移到其他 Jetson 设备上复用。我的做法是先把容器内环境调通再执行提交。docker commit nano_test nano_env_img:v1 docker save -o nano_env_img_v1.tar nano_env_img:v1第一条命令把当前运行的容器 nano_test 保存成新镜像 nano_env_img:v1里面对应的是你在容器内做的所有环境修改包括手动编译的 Python、安装的依赖库。第二条命令把镜像导出成 tar 文件拷贝到目标 Jetson 设备后用docker load -i nano_env_img_v1.tar导入。之所以用 docker save 而不是 docker export是保留镜像的层结构和构建历史后续还能基于它继续打补丁docker export 打出来的是扁平文件系统镜像元数据会丢掉。镜像迁移到新设备后有三件事必须重新检查少一件都可能白拷一次第一新设备的 daemon.json 是否也配置了 nvidia 默认运行时用docker info | grep -i Runtime确认 Default Runtime 为 nvidia第二新设备的/usr/lib/aarch64-linux-gnu/tegra 目录是否存在不同 JetPack 版本的 tegra 库版本可能不同如果新设备 JetPack 比构建时的版本新建议在容器里重新跑一次 deviceQuery 验证第三运行命令里的六个 --device 节点和 -v 挂载一个都不能少。最终运行命令我固定写成这样docker run -it \ --device/dev/nvhost-ctrl \ --device/dev/nvhost-ctrl-gpu \ --device/dev/nvhost-prof-gpu \ --device/dev/nvmap \ --device/dev/nvhost-gpu \ --device/dev/nvhost-as-gpu \ -v /usr/lib/aarch64-linux-gnu/tegra:/usr/lib/aarch64-linux-gnu/tegra \ --name nano_test nano_env_img:v1每条 --device 对应一个具体的 Tegra 硬件资源-v 把宿主机驱动库映射进容器。迁移后如果结果从 PASS 变成 FAIL优先比对这三项而不是重新装 runtime。从那以后我每次换设备部署 Jetson 容器都会强制走一遍这套检查流程docker info 看 runtime、ls tegra 目录看驱动、跑 deviceQuery 看结果三个检查做完才敢把环境交付出去。希望这份经验能帮你少走几趟弯路。本文还有配套的精品资源点击获取
返回列表