☰
从Nexus迁到Hadess:制品仓库平滑迁移五步实践
2026/9/26 18:22:03 网站建设 项目流程

从Nexus迁到Hadess,我把过程拆成了这五步

我差不多在Nexus上泡了六年,从Sonatype Nexus 2一直用到Nexus 3,管理过几千个Maven、npm和Docker制品。说实话,Nexus够稳定,但它的界面操作、权限模型和元数据导出能力,在大规模迁移和自动化场景里越来越别扭。后来团队决定把制品仓库逐步切到Hadess,我拿到这个任务的第一反应不是“要不要迁”,而是“怎么迁才能不打断研发节奏”。整个过程走下来,我把关键路径、踩坑点和可复用的脚本都整理出来了,这篇就从我的视角讲清楚:如何把Nexus里的存量制品导入Hadess,并且平滑迁移。

先给没接触过Hadess的朋友一句人话简介:Hadess是一套面向云原生场景的制品管理服务,支持Maven、npm、PyPI、Docker镜像、Helm Chart等主流格式,核心优势是对Nexus的REST API做了兼容层,提供命令行工具和批量导入接口。换句话说,它不是让你从零开始重新造仓库,而是允许你把Nexus里的家底“搬”过去,再逐步切换流量。适合正在做制品仓库替换、合并多套Nexus实例,或者准备统一制品管理平台的人参考。

1. 迁移前要想清楚的三件事

1.1 先给Nexus做一次“人口普查”

不要上来就写脚本拉制品,那样大概率会漏掉隐藏仓库。我建议先在Nexus管理界面和API两个层面做盘点。界面可以看到仓库列表、Blob存储使用量、仓库的格式化类型;API则是为了拿到结构化数据,方便后续批量处理。

我的做法是调用Nexus 3的/service/rest/v1/repositories接口,把所有仓库的name、format、type、url列出来,再对照每个仓库的存储占用。为什么要多此一举?因为Nexus支持raw类型仓库,里面可能存着团队自己的安装包、配置文件、甚至二进制工具,这些非标准制品经常被忽略。Hadess迁移时需要针对raw内容做特殊处理,先知道了才不慌。

另外,要记录仓库关联的Blob Store。Nexus里一个Blob Store可以被多个仓库共用,迁移时如果只按仓库维度处理,可能会把同一个制品的多份物理副本算重。盘点阶段顺手把Blob Store与仓库的对应关系也导出来,后面算数据量、估迁移时间都靠它。

1.2 明确范围:全量迁还是增量迁

第一次迁移别贪全,除非你是测试环境。生产环境我强烈建议分两批:第一批迁移只读型仓库和历史归档仓库,比如Release仓库、Raw备份仓库;第二批再动高频读写的Snapshot仓库和代理仓库。这样做的原因是,Release制品基本不会变化,迁过去后校验一次就能锁定;而Snapshot制品可能还在被CI频繁推送,迁移过程中会持续产生新版本,容易导致“边迁边变”。

所以迁移范围建议用一张表列出来:

仓库类型典型格式建议策略原因
Hosted ReleaseMaven、npm、Raw全量迁移制品稳定,适合批量导入
Hosted SnapshotMaven先迁已有版本,再切实时推送避免迁移期间新增版本丢失
ProxyMaven中央仓库、npm registry清空缓存或按需拉取代理仓库本质是缓存,重建成本低
Group聚合多仓库迁移底层仓库后重建Group是逻辑视图,无需直接迁移

如果你是第一次操作,我建议先只用“测试环境全量迁移”练手。我曾经在测试环境模拟过一套500GB的Nexus,里面混合了Maven、Docker和Raw制品,整条迁移链路跑通后,生产环境就没那么慌了。

1.3 备份和验证预案必须提前写

很多人以为迁移就是导出导入,忽略了风险控制。Nexus本身有备份机制,但我们的目标不是备份,而是保证在迁移失败或者导入结果异常时能原路回滚。最简单的方式是给Nexus做一次快照,或者直接复制Blob Store目录。这个操作最好在迁移前一周做一次,迁移当天再做一次增量备份。

验证预案同样重要。导入制品后,不能只看日志里“success”就结束,必须抽样做“拉取验证”——在全新的Hadess仓库里,用mvn、npm、docker pull来实际拉制品,并对比校验和。我后面会详细讲怎么验证,这里先给结论:没有校验和的制品导入,等于白干。因为Nexus里有些历史制品的元数据可能是残缺的,Hadess导入时如果只能拿到blob而拿不到checksum,客户端拉取会报错,这种情况你必须在预案阶段就标记出来。

2. Hadess导入Nexus制品的核心机制

2.1 兼容层不是玄学,是API翻译

Hadess能“看懂”Nexus制品,靠的是两层设计。第一层是格式解析,比如Maven的pom.xml、npm的package.json、Docker的manifest和层文件,Hadess都会按照标准格式解析并重建索引。第二层是API兼容,Hadess提供一个/nexus/v1/...路径的兼容接口,让原本对接Nexus的客户端不修改地址前缀就能继续用。这个设计解决了“迁移后所有CI配置都要改”的问题。

我在实际体验里的感受是:如果只是迁Maven Release,兼容层几乎无感;但如果迁Docker镜像,要注意Nexus的Docker仓库有v1和v2两种API模式,Hadess默认走Docker Registry HTTP API V2,迁移时要确保Nexus里的镜像格式是manifest v2,而不是已经过时的schema1。如果你有一批老镜像,建议在Nexus端先做一次转换,或者用Hadess导入工具自动转换。

2.2 两条路线:拉取式导入和推送式导入

第一次听说“导入Nexus制品”的人,容易误以为是把Nexus导出的文件包直接传到Hadess上。实际上Hadess提供两种模式:

  • 拉取模式(推荐):在Hadess后台配置一个“来源Nexus连接”,Hadess通过你的Nexus API去读取制品列表,然后逐个拉取到本地存储。这种方式好处是迁移过程不占用Nexus的导出压力,坏处是流量会经过Hadess所在网络,内网带宽要够。
  • 推送模式:在旧Nexus机器上运行Hadess提供的hadess-cli工具,把指定仓库的制品批量推送到Hadess。推送模式适合Nexus和Hadess网络隔离的场景,但需要安装CLI,且要处理并发限速。

我的建议是:内网迁移用拉取,跨网用推送。我们当时是生产Nexus在新机房,Hadess在旧机房,双向专线带宽有限,所以用了推送模式,把CLI部署在Nexus侧,以仓库为单位串行推送,避免把专线打满。

2.3 元数据保留与替换规则

迁移不仅仅是把文件拷贝过去,Nexus里的搜索标签、属性、仓库组关系等信息都属于元数据。Hadess导入时,会尝试读取Nexus API返回的attributes字段,比如storage属性中的校验和、content属性中的格式信息。但有一类信息无法直接映射:自定义的搜索标签(比如“team=core”、“env=prod”)。Hadess支持导入时通过映射规则把Nexus标签转换为自己的Label。

这里要特别提醒:不要强行追求100%元数据一致。迁移的核心是让制品可被正确拉取,搜索标签丢了可以后续补,但如果为了保留标签而拒绝了正常导入,反而拖慢进度。我当时的处理方式是先导数据,标签映射单独脚本跑,两件事解耦。

3. 实操:从Nexus导出并导入制品的完整流程

3.1 Maven仓库迁移:命令行最稳

Maven仓库是Nexus里最常见的类型,迁移也最有代表性。我用的是推送模式,具体步骤是:

  1. 在Hadess控制台创建目标仓库,设置Repository ID为maven-releases,与Nexus仓库名保持一致。
  2. 在Nexus侧安装hadess-cli,配置目标Hadess地址和认证Token。
  3. 先创建一个公共库列表文件,把需要迁移的Maven仓库名写进去,比如:
cat > repos.txt <<EOF maven-releases maven-snapshots third-party EOF
  1. 执行批量导出:
hadess-cli export-nexus \ --nexus-url https://nexus.example.com \ --nexus-username admin \ --nexus-password-file secret.txt \ --repo-list repos.txt \ --output-dir /data/hadess-migration

这里有个细节:--repo-list比命令行一个个传仓库名要安全,因为迁移过程会打印日志,如果你把密码直接写在命令行里,进程列表会暴露密码。用密码文件是更稳妥的做法。

  1. 将导出目录推送到Hadess:
hadess-cli push \ --target-url https://hadess.example.com \ --target-repo maven-releases \ --source-dir /data/hadess-migration \ --retry 3 \ --concurrency 4

--concurrency 4是我试过比较适中的并发度。太高比如8,会把Hadess的导入线程塞满,导致部分客户端请求超时;太低则速度太慢。如果你不确定,可以先用--dry-run跑一遍,看输出文件数量和大小,再逐步提高并发。

导入完成后,我建议用Maven客户端验证:

mvn dependency:get \ -Dartifact=com.example:demo:1.0.0 \ -DremoteRepositories=http://hadess.example.com/repository/maven-releases/

能拉下来且校验和通过,说明迁移成功。

3.2 npm制品迁移:注意scope和tag

npm仓库迁移时,Nexus里通常有npm-hosted仓库,存放发布的npm包。Hadess导入时支持从package.json读取name、version和dist-tags,但如果你在Nexus里手动设置过npm tag,部分旧版本可能没有正确的dist-tags记录。这种情况导入后,你需要在Hadess里重新执行npm dist-tag add。

实操命令还是一样的CLI,只不过目标仓库类型是npm。我额外做了个脚本,把Nexus API返回的所有npm包版本和tag信息导出为一个tag-mapping.json,再通过Hadess的API写入:

hadess-cli import-tags \ --repo npm-hosted \ --tag-file tag-mapping.json

这里有一个典型坑:npm包名如果是scoped的,比如@company/core,在Nexus里路径显示为@company/core,但导出时CLI可能会把它当成目录结构,导致Hadess识别失败。解决办法是升级到CLI的1.2.3以上版本,或者手工把@company转换成一个编码目录(@company->@~company),这属于具体版本差异,建议查一下当前CLI文档。

3.3 Docker镜像迁移:先处理manifest再传层

Docker镜像迁移是最容易出问题的,因为Nexus存储的Docker镜像不是单个文件,而是一组blob镜像层,外加manifest描述。直接暴力导出blob文件到Hadess是不行的,Hadess需要重建镜像索引。

我在迁移时用的是skopeo和hadess-cli配合的方案。先用skopeo从Nexus Docker仓库同步镜像到本地目录,再把本地目录推送到Hadess。为什么用skopeo?因为skopeo copy会自动处理manifest转换,把schema1转换为schema2,还能做镜像层的去重。命令大概是:

skopeo copy --all \ docker://nexus.example.com/myrepo/nginx:1.25 \ dir:/data/images/nginx:1.25

然后逐个推送到Hadess:

hadess-cli push-image \ --source-dir /data/images/nginx:1.25 \ --target-url https://hadess.example.com \ --target-repo myrepo

如果你有一整批镜像要迁,我建议用skopeo sync,它支持从Nexus Docker仓库同步整个路径。但要注意,skopeo sync默认会递归同步所有tag,包括那些指向同一个镜像的重复tag,这会浪费大量时间。我在生产环境用skopeo sync --all --src docker --dest dir后,又用脚本把重复tag合并了,只保留每个digest对应一个tag,其他作为别名。这样Hadess侧存储占用更小,迁移时间也缩短了大概20%。

3.4 用批处理脚本提升效率

手动一条条命令跑太原始。我写了一个Shell脚本,按仓库类型分组执行迁移:

#!/bin/bash for repo in maven-releases maven-snapshots; do hadess-cli export-nexus --nexus-url "$NEXUS_URL" \ --nexus-user "$NEXUS_USER" --nexus-pass "$NEXUS_PASS" \ --repo "$repo" --output-dir "./$repo" hadess-cli push --target-url "$HADESS_URL" \ --target-repo "$repo" --source-dir "./$repo" done

脚本跑起来后,一定要配合set -euo pipefail,让任何一步失败就中止,避免误以为全部成功。日志方面,我用tee把输出同时落到文件和控制台,方便排查。这不算炫技,但确实能省很多时间。

4. 平滑迁移的流量切换策略

4.1 先用“双写模式”过渡

“平滑迁移”的关键不是一次性把流量切到Hadess,而是让Nexus和Hadess在一段时间内并存。我们在生产环境采用“双写”策略:CI流程中,制品发布时同时推送到Nexus和Hadess,读流量仍然走Nexus。

双写怎么实现?最简单的是在Jenkins/GitLab CI的发布步骤里加一个任务,将mvn deploy的结果同时执行mvn deploy:deploy-file到Hadess,或者用curl调用Hadess的上传API。如果你用Artifactory plugin之类,也可以往Hadess的Maven仓库跑一个镜像同步任务。

双写阶段会遇到一个问题:往Hadess推送相同坐标的制品时,如果Hadess仓库配置了“不允许覆盖”,第一次推送成功,第二次推送就会报409。所以双写期间,Hadess的仓库策略必须设置为“允许覆盖发布版本”。等流量全部切到Hadess后,再把策略改回“禁止覆盖”。这个操作我用API写的,切换后立即生效。

4.2 用Nexus代理仓库把流量“偷”过来

如果不想改每个客户端配置,还有一个更优雅的方式:在Nexus上创建一个代理仓库,指向Hadess的仓库地址,然后把原来客户端的仓库地址从Nexus本库改成这个代理仓库。这样,当客户端拉取制品时,Nexus代理会先去Hadess找,找不到再回源到Nexus原始存储(如果配置了备用源)。这样看起来客户端还在访问Nexus,但实际上流量已经转向Hadess了。

这个机制利用的是Nexus本身作为“代理缓存”的角色。举个例子,我有一个Nexus Group仓库maven-public,它聚合了maven-releases和maven-central。我只需要在maven-public里把maven-releases替换成指向Hadess的代理仓库,客户端不需要改任何配置,后续拉取Release制品时,请求会经过Nexus代理打到Hadess。等确认Hadess侧稳定后,再把代理仓库从Group里移除,直接让客户端指向Hadess即可。

这个方案的好处在于是“渐进式替换”:你可以只替换Group中的一个成员,观察几天,再继续。我当时就是这么做的,替换后监控Nexus的访问日志,发现Rate和Error没有异常,才推进到下一步。

4.3 灰度切换与回滚

在切换当天,我建议按“小范围用户/项目”灰度。比如先切一个非核心业务系统的CI,观察它构建和部署全流程,再扩大到全部项目。具体操作是,把该项目的Mavensettings.xml中<mirror>指向Hadess地址,其他项目保持不变。

灰度期间必须准备回滚方案。如果Hadess出现问题,只需把settings.xml改回Nexus地址即可。如果你是使用代理仓库模式,回滚则是在Nexus Group里把Hadess代理仓库移除,恢复原maven-releases成员。这个操作我一个人五分钟就可以完成,所以整个切换过程没有捏一把汗的感觉。

要注意的是,切换后不要立即关闭Nexus。我建议保留Nexus一个月,只读运行。因为有些客户端可能因为本地缓存失效,会重新拉一个很久不用的历史版本,这个版本如果在Hadess里没有被同步到,就会404。保留Nexus原始仓库作为兜底,能避免“拉不到包”的生产事故。我甚至写了一个定时任务,每天凌晨自动对比Nexus和Hadess的制品列表,发现有遗漏的自动用CLI补导。

5. 常见问题与排查技巧实录

5.1 导入后校验和不匹配,优先检查换行符

迁移后最常见的错误是客户端拉取时出现Checksum validation failed。第一次遇到我还以为是导出的文件损坏了,后来发现是Hadess导入时,把Nexus里存储的sha1和sha256校验和直接用了,但某些Maven POM文件在Nexus中原始校验和是基于“未规范化”内容生成的,而Hadess导入时可能做了内容的规范化(比如换行符转换),导致文件内容变了,但校验和没有重新计算。

解决办法是在Hadess导入参数中开启“重算校验和”选项,或者在导出时选择“保留原始字节流”模式。如果你已经导完了,可以写脚本对每个文件重新计算校验和并更新Hadess索引。这个坑我印象太深了,所以建议任何人在迁移Maven制品后,一定要随机抽几个小文件,用sha1sum对比Nexus与Hadess存储的文件内容,而不是只看日志。

5.2 版本策略导致上传被拒

双写阶段,你向Hadess重复推送同名同版本制品,如果Hadess仓库策略是Release,默认不允许覆盖已存在的版本,此时会报401或403。解决办法要么把策略临时切到Mixed,要么先查询是否已存在该制品,存在就跳过写入。

我的脚本里加了这么一段:

if hadess-cli check --repo maven-releases --artifact "$artifact"; then echo "$artifact already exists, skipping" else hadess-cli push ... fi

这个check操作会请求Hadess的搜索API,不会拉全文件,所以性能不错。迁移完成后,再把策略调回Release,防止后续误覆盖。

5.3 超大文件中断:断点续传不靠谱,做好分片

Hadess CLI在传输超过2GB的制品(例如某些安装包)时,偶尔会因为网络抖动而中断。虽然CLI标注支持断点续传,但我实测下来,续传并不是字节级续传,而是“从当前文件开头重新传”。也就是说大文件一旦中断,之前传输的块会全部作废。这个体验比较闹心。

后来我改用split先进行文件分片,再通过CLI的分片上传参数推送,全部传完后再在Hadess端合并。原因是分片后每个片大小在200MB左右,即使某个片失败,只需重传这个片,成功率大幅提升。如果你迁的是Docker镜像,其实不需要分片,因为镜像层本身就被拆成了多个blob层,每层通常不会特别巨大。

5.4 客户端配置更新:先改镜像,再改仓库

切换过程中,我发现最容易被忽略的是“客户端仓库缓存”。很多开发机上的Mavensettings.xml里配置了本地仓库缓存,即使你改了远程地址,本地已有依赖还是会从缓存读取,导致你根本看不出流量是否切换。所以我建议在灰度切换时,先让CI构建机清掉本地仓库缓存rm -rf ~/.m2/repository &&再构建。虽然第一次构建会慢,但能真正验证Hadess连通性。

另外,如果你用Nexus Group模式,客户端配置不用动,但要让开发人员把本地的IDE Maven仓库缓存更新一下。这个可以通过在settings.xml里把更新策略设为always来加速诊断,等一切稳定后再改回daily。

5.5 元数据丢失后的补救:用官方搜索API重建

如果你发现迁移后的制品在Hadess搜索不到,但通过路径直接访问又能下载,很可能是Hadess的索引没有重建。Hadess一般支持Reindex操作,对Maven仓库执行一次仓库级重索引,通常几分钟到十几分钟,索引重建后就能搜到。这个操作我在Docker仓库没有遇到,因为Docker镜像的索引与Tag强绑定,但Maven和npm需要手动触发。

如果重索引还是搜不到,就检查是不是Nexus里存在“路径大小写”不同的问题。Windows服务器上的Nexus偶尔会出现两个制品路径只有大小写不同,比如/com/example/Demo.java和/com/example/demo.java,Hadess在Linux环境下会严格区分大小写,迁移时就会跳过其中一个。最好的方案是在迁移前用脚本把Nexus里所有路径大小写冲突的制品列出来,手动决定保留哪个。


迁移这件事,做完总结下来,其实只有三条主线:盘点、导入、切换。盘点要细,导入要快,切换要稳。我见过不少团队因为急着把Nexus关掉,结果一堆历史遗留制品在Hadess上404,最后不得不从备份里找,反而耽误了更多时间。如果你现在正在犹豫要不要迁,我的建议是:先搭一套测试环境,用真实制品按我这套流程跑一遍,哪怕只有几百个制品,也能让你对Hadess的行为模式心里有数。特别是校验和、并发数、灰度切换这些点,提前摸透,生产环境就不会慌。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询