ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ROS rosdep update 超时解决:换源、调参与离线化

2026/10/2 19:52:00 拓冰建站 浏览量
ROS rosdep update 超时解决:换源、调参与离线化 装 ROS 的朋友大概都经历过这个场面新系统刚配好rosdep init一路顺风紧接着敲下rosdep update终端停在某一行不动了等两三分钟最后甩出一句Read timed out重试几次还是同样的位置卡死。更气人的是你照着网上某个教程改了两个文件重新跑一遍看起来过了第二天换个终端又失败——因为那条命令其实从来没真正修好过只是恰好在某一刻网络通畅。这篇内容就是把rosdep update出现 time out 这件事拆到根上请求到底发去哪、卡在哪一步、哪些改法是真有效、哪些改法只是碰运气。解决思路我会分三层给你——换索引源、调参数与超时、彻底离线化。不管你是刚装完 ROS 的桌面用户还是在做气隙环境下的容器镜像构建这三层里都有一条能直接抄的路径。文中所有路径和命令都以 Ubuntu 系 ROS Noetic/ROS 2 常见环境为基准其他发行版把路径里的 Python 版本号换成你自己的即可。1. rosdep update 超时的链路拆解请求到底发去了哪里1.1 rosdep init 和 rosdep update 根本不是一回事很多人把这两个命令当成一件事一出错就一起重装这是排查效率最低的做法。rosdep init做的事情非常轻它只是在/etc/ros/rosdep/sources.list.d/目录下写一个20-default.list文件里面几行文本描述数据源从哪来本身几乎不产生网络请求所以它很少失败一旦失败多半是权限问题没用 sudo而不是网络问题。真正产生网络流量的是rosdep update它读取这个 list 文件里的每一条来源把索引和规则文件逐个拉下来落盘到~/.ros/rosdep/sources.cache。理解了分工你就知道为什么init 早就成功了update 还是超时——这两个命令的成功条件完全不重叠。我还见过一种典型误解以为删掉/etc/ros/rosdep/sources.list.d/20-default.list重新 init 就能解决超时。实际上重新 init 生成的还是同一份地址只是把同一个坑又踩了一遍。源地址不改init 一万次也没用。1.2 一次 update 要发多少个请求为什么这么容易断rosdep update的请求是串行的大致是三类第一类是 sources list 里列的那几个通用规则文件base、python、ruby 这类第二类是发行版索引文件第三类是索引指向的每个发行版的具体描述文件。这几个文件本体都非常小加一起也就一两百 KB。所以这条命令卡住几乎从来不是带宽问题而是每一次连接握手能不能在超时时间内完成的问题——跨境链路抖动、DNS 解析慢、TLS 握手被拖长任何一个环节慢一点整个串行流程就断在那里。串行是这里最要命的设计。假设你有 10 个请求每个成功率 90%整体成功率就掉到 35% 左右。这解释了那个让人抓狂的现象手动curl那个地址是通的但rosdep update就是过不去。不是你运气差是它在很短的时间里连着压了太多次独立请求只要有一次超时本轮就整体失败而失败之后重试又是从头发起。1.3 先判断是慢还是根本不通动手改配置之前先花两分钟定位能省掉大量无用功。第一步rosdep update --debug跑一遍看它停在哪一行输出。如果停在查询索引那一步说明是索引地址的问题如果已经打印出正在下载索引然后才断说明索引能到达问题在这一步之后的规则文件链接上。位置不同要改的文件也不同这是后面所有操作的前提。第二步直接探活。用curl -sS -o /dev/null -w %{http_code} %{time_total}\n 你的索引地址看返回码和耗时。如果耗时在两三秒以上那基本可以确定不是偶发问题必须换源或离线化别指望多试几次能过。第三步看缓存目录ls -l ~/.ros/rosdep/sources.cache/里面已经有内容的说明上次至少部分成功了这种半成功状态最容易误导人——你以为是修复生效其实是旧缓存被复用。注意排查阶段不要急着大改。先确认卡点再决定改哪一处。我见过太多人一上来就把三个文件全改了结果不知道是哪个改动起了作用后面升级 rosdep 出问题时完全无法回滚。2. 换索引源把 rosdep 的数据请求指向可稳定访问的镜像2.1 第一步永远是 grep而不是照着记忆改文件不同版本 rosdep 的源码结构差异不小网上教程里写的行号在你机器上大概率对不上。所以第一步不是打开编辑器而是先找出现场grep -rn raw.githubusercontent.com \ /usr/lib/python3/dist-packages/rosdistro/ \ /usr/lib/python3/dist-packages/rosdep2/ \ /etc/ros/rosdep/ 2/dev/null这条命令会把所有硬编码的海外地址位置一次性列出来。实战中最常见的命中点是三个/etc/ros/rosdep/sources.list.d/20-default.listlist 文件几行 yaml 来源、rosdistro/__init__.py默认索引地址常量、rosdep2/sources_list.py下载规则文件时的地址处理函数。如果你想确认本机实际生效的索引地址可以跑一句python3 -c import rosdistro; print(rosdistro.get_index_url())它会告诉你当前用的是哪个 URL比翻源码猜要靠谱得多。2.2 三个位置分别怎么改为什么都要改先说 list 文件。用 sudo 打开/etc/ros/rosdep/sources.list.d/20-default.list把里面所有指向海外地址的行域名部分替换成可稳定访问的镜像域名路径保持原样。这一步解决的是通用规则文件的下载。再说rosdistro/__init__.py。找到那个默认索引地址常量把它替换成镜像站上的索引地址。这里有个坑索引文件名跟 rosdep 版本相关老版本是index.yaml中间版本index-v3.yaml新版是index-v4.yaml。别凭记忆写先探一下镜像站上到底有哪个文件curl -sI https://mirrors.tuna.tsinghua.edu.cn/rosdistro/index-v4.yaml | head -1返回 200 再用这个名字。如果返回 404就把index-v3、index依次试一下或者直接看镜像目录列表里存在什么。这一步解决的是发行版索引的下载。最后是rosdep2/sources_list.py。索引文件里记录的每个发行版描述文件的地址通常是写死的完整 URL所以即使你把索引换成了镜像索引拉下来之后它往下指的地址可能还是海外地址。这个文件里有一段地址拼接/替换的逻辑把里面的域名基址替换成镜像域名才能让第三步的请求也走镜像。这一步最容易被漏掉也最容易造成改了索引还是超时的迷惑现象。改完之后不要忘了删缓存重跑否则你判断不了生效的是新地址还是旧数据rm -rf ~/.ros/rosdep/sources.cache rosdep update --rosdistro $ROS_DISTRO --debug提示改/usr/lib/python3/dist-packages/下的文件属于打补丁被 apt 升级覆盖是迟早的事。改之前先cp xxx.py xxx.py.bak留个备份或者干脆把这几条 sed 写成一个脚本存到自己的 dotfiles 里升级完重新执行一次。2.3 镜像同步延迟带来的key 找不到换源之后很多人会遇到另一种报错Cannot locate rosdep definition for [xxx]或者某个包的依赖解析不出来。这不是超时问题而是镜像同步延迟或索引与规则文件版本不一致导致的。最常见的原因是半换源——索引指向 A 镜像规则文件还指向原地址或另一个 B 镜像两边的快照时间不一样于是索引里声明的 key 在当前规则文件里还没出现。处理办法很直接全部统一到同一个镜像并且优先选同步频率高的站点如果只是要装当前这一个发行版用rosdep update --rosdistro $ROS_DISTRO只更新对应发行版能显著减少需要同步的文件数量也就减少了版本错配的概率。另外别在容器构建过程中临时改源又临时改回来构建缓存一旦把中间层留下来后续排查会非常痛苦。2.4 用环境变量兜住 apt 升级的覆盖问题改包文件最大的缺点是不可持久。arm 架构的板子、长期不关机的开发机、需要重复初始化的 CI 机器都建议用不改包文件的方式兜底。新版 rosdistro 支持通过环境变量ROSDISTRO_INDEX_URL覆盖索引地址部分版本还会读取/etc/ros/rosdistro.yaml里的index_url字段。用哪种都行关键是用python3 -c import rosdistro; print(rosdistro.get_index_url())验证它真的读到了你配置的值。这里有个非常隐蔽的坑环境变量对sudo不生效。你现在这个 shell 里配好了rosdep update前面加个 sudo环境就被重置了读回去的还是默认地址然后你盯着终端怀疑人生。要么用sudo -E要么把变量写进/etc/environment或/etc/profile.d/下的一个脚本里。我个人倾向后者因为它对所有用户、所有登录方式都生效容器镜像构建时也不容易漏。3. 参数与超时调优不完全依赖镜像的第二种活法3.1 先把手头这版支持哪些开关摸清楚rosdep update的参数在不同版本之间差异挺大与其照搬别人的命令不如先看清楚自己这一版有什么rosdep update --help rosdep --version几个通用性比较强的思路可以先用上。限定发行版是最有效的一招rosdep update --rosdistro noeticROS 2 里换成对应的发行版名只更新一个发行版的数据请求数量直接砍掉一多半。--include-eol-distros这类开关则是反向操作它会去拉已经停止维护的历史发行版纯属给自己增加超时概率非必要不要加。--debug用来定位卡点排查完成后就别在自动化脚本里带着了日志噪音太大。3.2 把无超时的等待改成有限超时 重试rosdep底层用的是标准库的 urlopen早期版本没有显式设置超时遇到握手慢的连接就会长时间挂住。这是一种比直接报错更糟糕的状态你不知道它是在等还是在死。可以给下载调用补上超时和重试思路大概是这样# 仅示意修改位置与思路具体行号以你本机安装的版本为准 for attempt in range(3): try: data urlopen(url, timeout15).read() break except Exception as e: if attempt 2: raise time.sleep(2)为什么要加超时而不是把超时调得更大因为这条链路的特点是慢而不稳把超时从默认值拉到 60 秒只会让每次失败多等 45 秒成功率并不会提升。反而是短超时 多次重试的整体期望耗时更低、成功率更高。这是我在这类跨境访问场景里踩了很多次坑之后总结出的规律跟不稳定链路打交道快速失败比耐心等待划算。3.3 一个能一次跑通的组合命令把上面几件事串起来我通常会用这样一条组合先删缓存保证状态干净再限定发行版、开 debug 观察rm -rf ~/.ros/rosdep/sources.cache rosdep update --rosdistro ${ROS_DISTRO} --debug 21 | tee /tmp/rosdep-update.logtee这一步看着多余实际很值报错信息里往往只有最后一行Read timed out前面的关键上下文被截在滚动缓冲里找不到了。把完整输出落成文件回头对比这次卡在第几步、上次卡在第几步能很快判断出你的修改有没有真正把请求位移到新的地址上。判断标准很简单日志里出现的域名应该是你配置的镜像域名如果还是原来的海外域名说明前面某处没改干净。4. 完全离线把 rosdistro 数据搬进本地一次做成永久的4.1 版本升级导致的补丁丢失不如一次离线化前三节都属于绕开不稳定连接而离线化是不再需要连接。它的价值在两类场景下特别明显一是内网或气隙环境根本不给你访问外部地址的机会二是容器镜像构建你希望构建结果可复现而不是今天能过明天不能过。思路是把 rosdistro 数据整体复制到本地某个目录然后把所有指向网络的 URL 都改写成本地文件路径。准备工作是拿到一份 rosdistro 数据的快照可以从镜像站的归档下载也可以在另一台网络条件好的机器上拿到整份数据后拷过来。放到比如/opt/rosdistro下面保证里面有索引文件和rosdep/目录。4.2 用 file:// 把两处地址都改写掉拿到本地副本后先把副本内部所有互相引用的地址改成file://形式cd /opt/rosdistro grep -rl raw.githubusercontent.com . | xargs -r sed -i \ s#https://raw.githubusercontent.com/ros/rosdistro/master#file:///opt/rosdistro#g再改 list 文件让起点也指向本地sudo tee /etc/ros/rosdep/sources.list.d/20-default.list /dev/null EOF # local offline sources yaml file:///opt/rosdistro/rosdep/base.yaml yaml file:///opt/rosdistro/rosdep/python.yaml yaml file:///opt/rosdistro/rosdep/ruby.yaml EOF然后照常rosdep update。因为走的是本地文件整个流程基本是瞬间完成也再不会有超时。这里要注意的是路径必须用三个斜杠的file:///两个斜杠会被解析成 host很多人卡在这一步。另外本地副本的目录权限要保证执行 rosdep 的那个用户可读容器里往往是以非 root 用户运行的。4.3 在能联网的机器上把 rosdep init 做完再搬离线环境还有个细节rosdep init需要往/etc/ros/写文件如果目标机器权限受限可以换一种做法——在一台能联网、环境相同的机器上完整跑通 init update然后把/etc/ros/rosdep/sources.list.d/和~/.ros/rosdep/sources.cache两个目录整体打包拷到目标机器对应位置。这样连 init 都不需要在目标机上执行。打包时注意两点一是缓存目录属于哪个用户就要放到哪个用户的家目录下权限用chown -R修正否则 rosdep 会认为缓存不可用而重新尝试联网二是记录一下这份快照的时间点半年后排查依赖问题时你会需要知道当时的规则文件是哪个版本。这个习惯我是吃过亏才养成的——某次线上构建突然报一个依赖查不到翻回来看才发现离线快照已经是一年前的。4.4 固化进 Docker 镜像的正确姿势容器场景下把离线源直接写进 Dockerfile 是最省事的COPY rosdistro /opt/rosdistro COPY 20-default.list /etc/ros/rosdep/sources.list.d/20-default.list RUN rosdep update --rosdistro noetic rm -rf /var/lib/apt/lists/*几个坑说一下。第一rosdep update的缓存默认写在执行用户的~/.ros下构建时通常是 root也就是/root/.ros如果后面切了非 root 用户运行缓存就找不到了得用-u或HOME环境变量控制位置或者把缓存放到/opt/ros这类共享路径并在运行时设置HOME。第二别把rosdep update和网络依赖的步骤混在一个 RUN 里否则网络抖动会让整个构建层缓存失效每次都要从这层重来。第三离线源的目录别放在构建完就删的临时目录里运行时如果需要rosdep install它还要读这些规则文件。4.5 怎么验证离线源是真的可用装完之后别急着走做一次验收rosdep resolve rviz rosdep install --simulate --from-paths src --ignore-src -r--simulate是关键它只做依赖解析不实际安装能快速暴露规则文件缺 key 的问题。如果输出里出现Cannot locate rosdep definition说明你的离线快照不完整缺了某些发行版或 OS 相关的规则文件回到副本目录里核对一下是不是漏拷了某个子目录。这一步能在真正编译前把问题拦住比编译到一半报依赖缺失要省事得多。5. 排查顺序与几个想当然的坑5.1 一张对照表照着排查比瞎试快得多现象大概率原因优先动作卡在查询索引后超时索引地址不可达换索引地址或改ROSDISTRO_INDEX_URL先探活再改索引下载完了才超时规则文件地址仍是原地址检查 sources list 与源码里的域名基址是否都换了改了配置但日志里域名没变改的不是生效的那一份或缓存复用用which -a rosdep定位删缓存重跑看日志sudo 下依然超时环境变量没被 sudo 继承用/etc/environment或sudo -E偶发成功偶发失败串行请求的累积成功率问题限定单一发行版减少请求数或直接离线化解析报 key 找不到索引与规则文件版本不一致统一到同一个源必要时用离线快照5.2 最容易踩的五个想当然第一个以为只改一处就够了。索引和规则文件是两段独立的请求只改其中一段另一半照样超时。判断方法就是看 debug 日志里出现的域名两个域名都应该是镜像域名才算改干净。第二个改完不删缓存。~/.ros/rosdep/sources.cache里已经落盘的旧数据会被复用你根本分不清生效的是新配置还是旧内容。改配置和删缓存这两步永远绑在一起做。第三个系统里存在多个 rosdep。用 apt 装的python3-rosdep和用 pip 装的 rosdep 可能同时存在which -a rosdep会列出所有可执行文件路径python3 -c import rosdep2; print(rosdep2.__file__)能告诉你当前解释器实际加载的是哪一份。改错文件是最浪费时间的坑因为所有操作看起来都对就是不生效。第四个把第三方定制工具和原生 rosdep 混着用。社区里有针对国内网络做过源适配的定制版本能省事但它的缓存目录、配置文件位置和原生版本不一定一致混用之后会出现明明更新过命令用的还是老数据这类诡异问题。我的建议是团队里统一选一种并且写进环境搭建文档别让两个人用两套。第五个以为版本升级后补丁还在。用 apt 升级过 rosdep 相关包之后/usr/lib/python3/dist-packages/下被改过的文件会被覆盖回默认版本如果你只是手动改过没留脚本下次升级完超时问题会原样复现。用dpkg -S /usr/lib/python3/dist-packages/rosdistro/__init__.py可以确认这个文件归属哪个包方便你判断哪些改动会被覆盖。5.3 我现在的标准操作顺序给一个我实际在用的顺序从零到可用大概五分钟先curl探活确认镜像索引文件存在再grep -rn找出所有海外地址位置统一替换域名改前备份把ROSDISTRO_INDEX_URL写进/etc/profile.d/下的脚本让 sudo 也能读到删缓存跑rosdep update --rosdistro $ROS_DISTRO --debug并把日志落盘最后用rosdep resolve和--simulate做一次解析验收。这一套跑下来我不再需要多试几次碰运气成不成功当场就能看出来。另外分享一个小习惯把镜像索引文件用curl -o下载一份存到项目目录下同时记下下载时间。下次遇到依赖解析不出来可以直接对比是不是镜像同步落后了不用重新猜。这个习惯在跨团队协作的时候特别有用别人复现不了你的构建时你能立刻给出当时的镜像快照信息。至于最终选哪条路我的经验是这样个人桌面开发机换镜像 环境变量兜底足够了CI 和容器构建直接上离线快照别跟网络抖动较劲省下来的时间远比做一次离线化的成本高气隙环境没得选只能离线化而且越早做越省钱——等到构建流水线里几十个任务都依赖网络源的时候再改工作量是现在的十倍。