老炮踩坑录 · 番外篇 · 翻车现场系列
基于「基础企业档案」真实项目代码,复盘一次"本地构建正常、CI 上内部包 404"的排查
关键词:mirrorOf · settings.xml · 镜像 · !排除写法 · effective-settings
专业术语:CI(持续集成)
👋 欢迎阅读
🏠个人主页: 知守观
📘我的专栏: 老炮踩坑录
💻当前内容:Maven 镜像拦截规则
前言
上一篇《# 同事的 SNAPSHOT 更新了,我三天后才拉到》讲了 _remote.repositories 的来源校验。那篇的坑出在仓库 id 对不上,这篇的坑还是出在仓库 id 上------在 settings.xml 文件里一行 mirror 配置,匹配的对象就是仓库 id。
时间线接着上一篇:私服迁移完成,old-nexus 的账清干净了,新私服用了两周,一切正常。然后 CI 时却挂了。
现象:本地能跑,CI 上 404
周五下午,我提交了一版代码,CI 构建失败,报错信息如下:
text
[ERROR] Failed to execute goal on project base-archives-web:
Could not resolve dependencies for project cn.xiaomayi:base-archives-web:jar:0.0.1-SNAPSHOT:
Could not find artifact cn.xiaomayi:base-archives-common:jar:0.0.1-SNAPSHOT
in alimaven (https://maven.aliyun.com/repository/public) -> [Help 1]
base-archives-common 是内部模块,就在公司私服上,上午同事还 deploy 过。
我在本地把同样的代码拉下来,同样的命令构建了一遍,结果是 BUILD SUCCESS。
同一份代码,同一份 pom,本地能下到的包,CI 上却报 404错误。
排查:先怀疑私服,再怀疑 pom
第一个怀疑对象是私服本身。于是直接用浏览器访问了私服上 common 的目录,jar 在,时间戳是今天上午的,说明私服没问题。
第二个怀疑对象是 CI 的网络。让运维同事看了下,CI 服务器网络也正常。
然后我回头看报错。in alimaven (https://maven.aliyun.com/repository/public) 这一段引起了我的注意:alimaven 这个 id,我们 pom 里没配过,仓库地址是阿里云的公网镜像。
一个公司内部的 groupId,出现在公网镜像的报错里。
公司内部包只存在于私服,去阿里云找必然 404。问题变了:CI 上的 Maven 为什么拿着内部包的坐标,去公网镜像下载依赖?
让运维把 CI 服务器上的 settings.xml 贴出来:
xml
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
报错里的 alimaven,就是它。
原因:mirrorOf 匹配的是仓库 id,* 匹配所有
mirror 的工作方式:Maven 发起仓库请求之前,先过一遍 settings.xml 里的 mirror 列表,看要访问的仓库 id 命中哪个 mirror 的 <mirrorOf> 规则。命中了,这个请求就交给镜像处理------URL 换成镜像的地址,id 换成镜像的 id,原仓库不再出现。
mirrorOf=* 的意思是接管所有仓库。私服 id 是 nexus,命中 *,于是下载 cn.xiaomayi:base-archives-common 的请求被换到了阿里云的地址。阿里云上没有这个包,所以报 404错误。
这份 settings.xml 是运维前一天换的。旧服务器的 settings.xml 好久没人维护了,他找了篇博客抄了一份新的,博客作者配阿里云镜像是为了加速,用的 *。抄过来的时候,没人想到公司里还有个私服。
三个关键事实,分开讲。
- mirrorOf 匹配的是仓库 id,不是 URL。 很多人以为
*匹配的是"所有地址",以为写个私服的 URL 就能绕开。不行,匹配的是 pom 和 settings 里<repository>的 id。想排除私服,得在 mirrorOf 里写它的 id:
xml
<mirrorOf>*,!nexus</mirrorOf>
! 是排除写法,意思是"除了 id 为 nexus 的仓库,其他全部用镜像"。
-
mirror 管不到 deploy。 同事的 deploy 走的是 distributionManagement,和 mirror 是两套配置,互不相干。所以这次事故里 deploy 一直成功、只有拉取 404,两边看起来矛盾,实际上各走各的路。
-
报错里的 id 和地址是镜像的。 请求被镜像接管之后,日志和报错里出现的就是镜像的 id 和 URL,原仓库的名字不会出现。排查时看到陌生的仓库 id,先别急着翻 pom,去 settings.xml 的 mirrors 里找。 mirrorOf 的完整写法,四种:
xml
<mirrorOf>*</mirrorOf> <!-- 所有仓库 -->
<mirrorOf>central</mirrorOf> <!-- 只接管 central -->
<mirrorOf>*,!nexus,!snapshots</mirrorOf> <!-- 全部,但排除 nexus 和 snapshots -->
<mirrorOf>external:*</mirrorOf> <!-- 所有非本机的仓库(排除 localhost 和 file://) -->
修复
把 CI 的 settings.xml 改成显式排除私服:
xml
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>*,!nexus</mirrorOf>
</mirror>
改完重新构建,BUILD SUCCESS。
我倾向更保守的写法:只镜像 central,不用 *:
xml
<mirrorOf>central</mirrorOf>
中央仓库走阿里云加速,其他仓库(私服、jitpack、各种第三方仓库)原样直连。镜像的范围越小,意外接管的面就越小。
还有一个连环坑也要提醒。如果 CI 的本地仓库是持久化的,这次事故期间下载过的所有第三方依赖,_remote.repositories 里登记的都是 alimaven。哪天把 mirror 整个撤了,仓库 id 又对不上了------上一篇 F08 的坑会原样重现。撤 mirror 的时候,要么保持镜像 id 不变,要么清一次本地仓库。
这个坑为什么难排查
报错指向是歪的。看到 Could not find artifact ... in alimaven,第一反应是"pom 里什么时候配了个阿里云",翻完 pom 翻父 pom,什么都找不到。镜像接管这个环节在日志里不露痕迹,INFO 级别的输出里看不到"请求被换地址"这件事。
本地和 CI 的配置差异藏得深。settings.xml 不在代码仓库里,本地一份、CI 一份、每个同事一份,改任何一份都没有记录可查。这次的事故现场(CI 服务器)和问题源头(运维抄的配置)之间还隔着一个人,信息又多了一道衰减。
* 号这个写法流传太广。搜"Maven 阿里云镜像",前排文章清一色 mirrorOf=*,抄的人默认它是标准配置。对纯开源依赖的项目它确实没问题,一旦公司有私服,它就是定时炸弹。
自查清单

老炮点评
settings.xml 这东西,入职配一次,抄来的,之后几年不碰。它的内容很少有人逐行读过,可它对构建的控制权比 pom 还大------pom 决定"要什么",settings 决定"去哪拿",后者改一行,前者的所有配置都可能落空。
"本地能跑,CI 却挂"这类问题,先 diff 环境,再怀疑代码。这次我从 404 错误到定位,绕的弯路全在"怀疑私服、怀疑 pom"上,方向反了。
延伸阅读 : 前两篇番外篇讲了多模块没install和SNAPSHOT拉不到,这篇讲mirrorOf把私服拦去公网。三篇构成了Maven仓库三部曲,建议一起看
下期预告 :《IDEA 里藏着的第二份 settings.xml》
settings.xml 明明改对了,命令行构建也正常了,IDEA 里的依赖还是红的。翻遍设置才发现,IDEA 的 Maven 配置里 override 了另一份 settings.xml,路径指向一个两年前就不存在的目录。
下期讲 IDEA 和命令行各用各的配置时,怎么让它们说同一种话。
如果本文对你有帮助,欢迎:
👍 点赞 | ⭐ 收藏 | 👤 关注| 💬 留言
我是老炮,Java 老兵,仍在一线。关注「Java老炮踩坑录」,看真实案例,少踩坑。