首页 / 资讯中心 / 文章详情

Nexus npm依赖下载失败排查:负缓存与上游配置实战

Nexus npm依赖下载失败排查:负缓存与上游配置实战 ★ FEATURED ARTICLE
如果你们团队也用 Nexus 统一管 npm 依赖某天早上同事突然在群里喊npm install报错而你刚好是那个要负责收尾的人那下面这条排查路线应该能帮上忙。这次事故发生在公司内网环境现象是部分 npm 包能下载部分包一律失败而且失败的类型不止一种有直接 404 的有报 502 的还有几台机器提示证书校验不过。折腾了大半天最终定位到 Nexus 的 proxy 仓库上游配置和负缓存问题。这篇文章把完整过程写下来包括我当时怎么一层层缩小范围、每个阶段验证了什么、最后怎么收尾希望对同样被内网依赖问题折磨的人有点用。1. 问题现场不是所有包都装不上1.1 现象描述周一早上研发同学A反馈新项目的依赖装不了npm install跑一半就挂。 我让他把完整报错发出来截图里典型的一段是这样的npm ERR! code EINTEGRITY npm ERR! sha512-xxxxx integrity checksum failed when using sha512 npm ERR! code 404 npm ERR! 404 Not Found - GET http://nexus.内网域名/repository/npm-group/xxx/-/xxx-1.2.3.tgz第一反应是怀疑本地缓存坏了。但紧接着又有两个同事说自己的老项目也装不上新依赖而老项目之前一直都能正常安装这说明客户端缓存不是主因。进一步了解现场公司内网所有开发机的 npm registry 都指向 Nexus 的 group 仓库地址Nexus 部署在内网几台虚拟机里版本是 3.x。开发环境是 Windows、macOS 混合CI 机器是 Linux同样报错。全平台都失败基本可以排除客户端差异导致的偶发问题。1.2 让人迷惑的部分能装、部分不能装有意思的是报错集中在某些新版本号上。比如foo包的 1.2.2 能装1.2.3 不能装bar包无论哪个版本都能正常拉下来。这种局部失败很容易让人误判成某个包上传有问题。实际上这正是 Nexus proxy 仓库回源机制的典型特征如果 Nexus 本地缓存里有这个版本的 tarball哪怕上游现在完全不通它也能照常返回给客户端只有那些没缓存过、需要实时去上游拉取的新包才会因为上游请求失败而报错。所以能装与不能装的边界本质上是缓存命中与需要回源的边界。这句话是后面整个排查的钥匙我先记下来。2. 排查前先理解 Nexus 的仓库结构2.1 三种仓库类型Nexus 里和 npm 相关的仓库有三种类型搞清楚它们的分工排查时才知道每个报错对应哪一段。proxy 仓库负责向公网 npm 源发起请求把拉取到的包缓存到本地。它的作用是中转缓存内网用户请求一个包Nexus 先看自己有没有没有就去上游拿一份存下来再返回给用户。hosted 仓库存放团队自己发布的私有包比如组件库、公共工具库。这种仓库不访问外部只接受本地上传。group 仓库把 proxy 和 hosted 组合成一个统一入口对外只暴露一个地址客户端只需要配置这一个地址即可。开发机上的 registry 通常配置成 group 仓库地址。一个请求进来后Nexus 会按 group 内的配置顺序查找先看 hosted 有没有没有再走 proxyproxy 也没缓存就去上游拉取整个过程对客户端透明用户完全感知不到后端还有这么多层。2.2 在链路上标明每段可能出问题的地方一次npm install的依赖请求实际要走过这么一条链路npm 客户端 - Nexus group - Nexus proxy - 公网上游 - Nexus 缓存 - 返回客户端任何一个环节出问题最终都表现为同一个现象内网下载 npm 依赖失败。但不同环节故障的报错形态不一样客户端配置的地址错误报ECONNREFUSED或ENOTFOUNDgroup 组合顺序放错或权限没配好常见 404、403proxy 上游不通最典型的是 502、504、超时上游通了但包确实不存在则返回 404而且 404 可能被缓存一段时间。我在排查时把这张链路图写在纸上每验证完一步就打个勾。这个小习惯帮我避免了在同一个地方反复打转也让每一步验证都有了明确目的我在验哪一段通过什么现象判断这一段正常还是异常。3. 逐步排查从客户端一路查到上游3.1 先验证客户端配置避免白跑排查的第一站永远是客户端。先让汇报问题的同事跑几条命令npm config get registry npm config list确认 registry 确实是内网 group 地址而不是某位同事自己改的公共地址。然后看项目根目录有没有.npmrc有些项目模板会自带一个旧的 registry 地址它会覆盖全局配置。这一步我们确实发现了一台机器指向了公网镜像源但它只是孤例改回内网地址后依旧报错说明这不是根因但它提醒我客户端配置确实存在局部不统一的问题后面必须一并收口。再清一下本地缓存排除干扰npm cache verify如果还不放心可以用npm cache clean --force代价是下次安装会慢一些但能排除本地 cache 里的 metadata 和 Nexus 返回的不一致这一类问题。清理完重跑npm install仍然失败问题定位从客户端转移。3.2 用 curl 复现确定问题在 Nexus 这一侧客户端配置和缓存都处理完问题依旧。接下来我用 curl 直接携带同样的 URL 去请求 Nexus绕开 npm 自身的行为curl -i http://nexus.内网域名/repository/npm-group/foo报错的那个包返回 404正常的包返回 200 和完整的 packumentnpm 的元数据文档。这就说明问题不在 npm 客户端而在 Nexus 这一侧。为了确认不是 group 聚合的某个子仓库出了问题我分别请求了 proxy 仓库和 hosted 仓库的地址结果 proxy 仓库对那个包同样返回 404。到这里嫌疑范围从客户端 Nexus 整体缩小到了proxy 仓库的缓存与上游链路。这一步在整个排查里是非常关键的转折它把看起来像客户端问题的事故正式拉到了服务端。3.3 翻 Nexus 日志看上游到底发生了什么Nexus 的日志默认在安装目录下的sonatype-work/nexus3/log/重点关注两个文件nexus.log和request.log。我用报错的包名去 grepgrep foo sonatype-work/nexus3/log/nexus.log | tail -50日志里出现了明确的上游连接失败记录大意是请求某个公网 npm 源上的foo包时连接超时几秒后放弃并返回了错误响应。看到这行日志基本坐实proxy 仓库在尝试回源时连不上上游。为什么连不上这就要提到网络环境。我们公司的 Nexus 服务器部署在隔离网段服务器出公网需要经过防火墙白名单管控而 proxy 仓库的上游地址是多年前配好的公网源地址。之前一直没有问题是因为常规依赖的 tarball 都在本地缓存里请求全部命中不需要回源只有当某个没人拉过的新版本出现需要临时回源时这个问题才会暴露出来。3.4 发现负缓存网络恢复了包还是 404在等运维协调防火墙策略的间隙我做了个实验用curl -i请求一个上游根本不存在的包名第一次返回 404 之后间隔几分钟再请求依然是瞬间返回 404响应时间不到 300 毫秒。这个速度说明 Nexus 根本没有再次回源它直接复用了上一次的失败结论。查了 proxy 仓库的配置果然开启了负缓存默认 TTL 是 1440 分钟也就是整整一天。于是整个故障链条完整了内网服务器访问公网偶尔被限流新包回源时超时或者上游临时返回了错误状态Nexus 把这次回源失败的结论缓存了一天等到运维放通了网络Nexus 依然优先返回自己缓存里的失败结论导致客户端持续报错。也就是说即使网络这个最初的诱因解除了负缓存机制还会让故障继续延续这是这次事故里最容易被忽略的一点。4. 根因确认与解决清理负缓存 修正上游策略4.1 负缓存到底在缓存什么负缓存negative cache在很多包管理组件里都有。常规缓存存的是成功结果拿到包就存一份下次直接用。负缓存存的是失败结果请求上游超时、返回 502、404Nexus 也会把这个失败结论记下来在一个时间窗口内直接复用不再向上游发真实请求。它有两个好处第一上游短暂故障时避免每个客户端请求都催着 Nexus 反复试探把昂贵的回源消耗在不必要的尝试上第二内网大量客户端频繁请求同一个不存在的包时Nexus 能快速返回失败保护自身性能。副作用就是这次遇到的上游恢复以后Nexus 依然告诉客户端这个包不存在而客户端本地又没有缓存于是连锁反应就是网络通了仍然装不上。如果用生活里的例子类比相当于快递站不仅把送到家的包裹记了账还把你家附近没有这个包裹的字条也贴了一天第二天人家明明送过来了快递站仍然先看字条说不存在。4.2 清理负缓存的操作路径清理负缓存并没有一个图形界面的按钮叫清空负缓存实际处理时有几种办法调低甚至临时关闭负缓存 TTL。在 proxy 仓库的配置里找到负缓存相关字段比如 Maximum Negative Cache TTL / Negative Cache TTL临时改成 0 或一个很小的值比如 1 分钟然后重新请求一次那个包让 Nexus 真正访问一次上游。用强制回源的方式验证个别包。确认上游正常后用带Cache-Control: no-cache的请求去触发回源不过这种方式在不同 Nexus 版本上表现不一致不一定都能生效只能作为辅助手段。重启 Nexus 服务。如果调整配置后仍不生效重启能让内部状态重新初始化。Nexus 的缓存数据在 blob store 里重启不会清除仓库里的普通缓存但负缓存状态会重建所以重启在很多场景下是最快的兜底手段。我们在实际操作中先申请了公网访问放行再把该 proxy 仓库的负缓存 TTL 临时调成 1 分钟用 curl 分别触发那几个报错包的请求确认返回 200 后把 TTL 调回原来的值。整个过程大概二十分钟没有等一天。注意不同 Nexus 版本里这些配置字段的名字和位置不完全一样以你安装版本的实际界面为准。改动前先把原配置截图留底调完记得改回去。4.3 上游策略让 proxy 仓库有一个承诺可达的源清理负缓存只是治标治本要让 proxy 仓库的上游源保持可靠。这里有三条路可以选如果公司内网可以访问公网就把上游配成官方源或距离近的镜像源并让防火墙放行对应域名和端口。如果内网与公网完全隔离就不要让 proxy 仓库直接指向公网源而是改成指向公司内部维护的离线包镜像源或者干脆定期用离线包灌入 hosted 仓库。如果希望日常请求尽量不依赖回源可以给 Nexus 加一层预取策略用脚本提前把团队常用依赖的常用版本批量请求一次让缓存始终是热的。真实用户请求时命中本地缓存回源只发生在新包出现时。我们最后采用的方式是申请放行上游域名 调低负缓存 TTL 对常用依赖做预取。这个组合的好处在于日常绝大多数请求走缓存只有新版本出现才回源而回源失败的概率在防火墙放行之后降到了很低负缓存即使偶尔生效影响面也小了很多。4.4 客户端侧的统一规范lockfile 和 registry 必须一致这次排查还暴露了一个隐藏雷部分同事本地 npm 配置曾经指向公网镜像源因此生成 lockfile 时resolved字段写的是公网地址。等配置统一切回内网后他们把带着公网地址的 lockfile 提交到了代码仓库其他同事npm install时会发现 resolved 和 registry 不一致导致同一份依赖在不同机器上解析到不同下载地址偶尔还会被镜像源的内容差异坑一下。这里建议做两件事团队内统一 registry 配置文档里明确开发机一律使用内网 group 地址必要时通过公司自建的初始化脚本自动写入全局~/.npmrc从源头避免手工配置导致的偏差。修改 registry 之后让每个开发者在自己的分支上删除node_modules和 lockfile重新生成一次再提交保证 lockfile 里的 resolved 统一指向内网地址。这个过程只需要几分钟但能省掉后续大量换环境安装失败的排查时间。5. 常见失败形态与速查清单5.1 一张表看懂错误表现 - 原因 - 处理基于这次排查和此前积累的经验我把这类问题常见的表现整理成了速查表下次直接对照着看错误表现大概率原因快速验证方法处理方式所有包 404registry 地址配错或 group 仓库 URL 不对用 curl 打 group 地址看返回修正客户端 registry 配置个别新版本 404旧版本正常proxy 仓库负缓存或上游确实缺失该版本curl 请求该包并观察响应耗时清理负缓存/调整 TTL确认上游502 / 504 / 超时proxy 上游不可达或响应太慢在 Nexus 所在服务器上直接 curl 上游地址检查防火墙策略/更换上游源401 / 403认证信息失效或权限不足检查.npmrc中的 token查看 Nexus 用户权限重新生成 token检查仓库权限EINTEGRITY 校验失败npm 本地缓存损坏或镜像内容不一致清理本地缓存后重试npm cache verify必要时删除 lockfile 重新生成证书校验报错Nexus 使用了自签证书客户端未信任curl -k能通正常请求失败安装并信任内网证书或联系基础设施团队统一签发这张表不一定覆盖所有情况但在我接触过的团队里至少能命中七八成类似的内网 npm 下载失败问题。尤其是前两行遇到频率非常高值得优先排查。5.2 预防措施别等出事故再排查这次事故之后回头看有些预防手段其实应该更早做。下面这几条是我打算直接写进团队运维手册的给 proxy 仓库设置探活定期用一个稳定不变的包名去请求 Nexus如果连续几次失败就告警。探活包可以用团队自己的 hosted 包也可以选一个几乎不变更的公共包。监控 Nexus 日志中的上游错误把 nexus.log 里的上游超时、连接失败等关键字接上告警通道回源失败不用等用户发现运维就能第一时间看到。合理设置负缓存 TTL太小会让 Nexus 频繁回源上游不稳定时反而把故障放大太大就会出现这次的网络恢复但依然失败。我个人建议先在 30 分钟到 2 小时之间取个值观察一段时间再微调。定期做依赖预取如果团队固定使用某些框架和组件库写一个脚本每周把常用包的常用版本批量请求一遍让缓存始终是热的。规划 Nexus 升级低版本 Nexus 在并发回源、负缓存控制方面都比较粗糙新版本通常有更细的配置项和更好的日志值得安排升级窗口。5.3 我自己的复盘心得这次排查花了差不多一个工作日最大的教训是不要被错误类型带着乱跑。一开始看到 404 就以为是包没上传看到 EINTEGRITY 又去折腾本地缓存绕了很大一圈。实际上只要先把链路图画出来每个环节用 curl 和日志验证一次问题范围会收得非常快。另一个教训是Nexus 这类组件的缓存机制平时是好事但恰恰是这些透明的机制在故障恢复后最容易留下尾巴。以后凡是动了 proxy 仓库的配置我都会顺手看一眼负缓存相关设置确保不会再次出现上游恢复服务却还停留在过去的尴尬。最后再分享一个小技巧如果你已经确认上游没问题又不想等负缓存 TTL 自然过期最快的办法是在 proxy 仓库配置里临时把负缓存 TTL 改成 0重启一次 Nexus 服务然后 curl 触发一次你想恢复的包的请求确认返回 200 之后再改回原值并再次重启。别问我为什么知道重启两次才稳妥这个坑我已经替你们踩过了。
阅读完成 · 觉得有帮助?
咨询建站