搞制品仓库搬迁这件事,绝大多数情况都是“平时没感觉,一旦要动就全是坑”。我在接手公司持续集成平台改造的时候,第一个要解决的就是Nexus里面的三万多件制品怎么安全搬到Hadess里去。如果你也正打算把Nexus仓库中的npm包、Python包、通用二进制等制品迁移到Hadess,这篇指南应该能在你动手之前就把大部分雷先排掉。我不打算展开讲官网文档里那套概念,重点说从“评估”到“切换”这条完整链路里,哪些开关必须打开、哪些坑必须提前填。适合DevOps、后端架构和负责平台运维的同学阅读,尤其是团队规模不大、没有专职数据迁移工程师的环境——我一开始就属于后者,所以整套流程我没有用商业迁移服务,全部靠脚本和平台自带能力完成。
1. 迁移前的架构设计与准入评审
1.1 为什么从Nexus迁到Hadess,先想清楚这几个点
首先得承认:Nexus本身是成熟的制品仓库,老团队用得很顺的情况下,主动搬迁的理由无非是那么几条——授权成本、维护人力、存储配额、云原生场景下需要更细的制品元数据管理。Hadess恰好在我遇到的场景里补足了这几个短板:它支持主流包格式,提供REST API,同时也能兼容Nexus生态的仓库路径和文件协议。我选择它并不是因为它比Nexus功能多,恰恰相反,它的功能更收敛,收敛意味着配置项少、出问题的面也小。
再从运维成本看。Nexus部署起来不复杂,但高性能场景下通常要调JVM堆、调blob store存储策略,出了问题排查链很长。Hadess在这方面的开销我实测是低一个量级的,它把编排、存储、访问控制集中在同一条配置链路上,出问题基本上可以在五分钟内定位到是仓库配置、权限还是网关层。选型时别光看Feature列表,对于制品仓库这种天天被CI调用的基础设施,稳定和可排查性比多一个插件更重要。
我整理了一张我在选型时关注的维度表,供参考:
| 对比维度 | Hadess | Nexus |
|---|---|---|
| 配置复杂度 | 低,YAML/REST统一管理 | 中高,Admin UI加系统属性 |
| 制品格式覆盖 | npm、Maven、PyPI、Raw、Docker | 同样覆盖 |
| API生态 | 面向YAML/REST | Groovy脚本体系 |
| 资源占用 | 轻量,不依赖大JVM堆 | 依赖JVM,内存占用偏高 |
| 迁移适配度 | 仓库路径兼容Nexus常见布局,迁移成本低 | 作为源端,导出能力完整 |
确定迁移之后,还要在团队内部明确一个核心指标:什么是“平滑”。我当时的定义是三条——CI不中断、下载地址不失效、历史构建记录里的产物链接依然可追踪。带着这三个目标去做迁移方案,就不会被工具层面的小问题带偏。
1.2 盘点Nexus存量:数量、格式、依赖关系
迁移的第一个动作不是开环境,而是把存量摸清楚。我在Nexus管理界面导出过一次仓库资产清单,统计出来总共有3.6万个资产,累计1.8TB,分布在5个repository上。数据量并不算大,但格式分散:既有npm和PyPI这类“包管理型”制品,也有大量的raw二进制包,还有Docker镜像。镜像因为不在Nexus里,迁移时另走一套流程,所以盘点时就要分清楚边界。
盘点需要登记的信息包括:仓库名称、格式类型、制品数量、总容量、最近活跃时间区间、匿名访问是否开启、是否有代理仓库、是否有定时清理任务。这些数据决定了迁移脚本怎么设计、先迁哪个库、以及切换顺序。建一个简单的表格模板就能用:
- 仓库名 | 格式 | 制品数 | 总量 | 活跃度 | 权限模型 | 备注
拿到这些数据后还有一个动作不能省:确认Nexus里的“代理仓库”有没有缓存大量公网制品。代理仓库缓存的是远端公网制品,比如npmjs.org的依赖包。这类制品是否迁移取决于网络策略。如果内网本来就允许直连公网,代理仓库完全可以在Hadess里重建一个同样的配置,而不是搬运缓存。反过来说,如果是一片隔离网络,那就只能老实把代理缓存也导过去,否则切了Hadess以后CI拉不到外部依赖。
1.3 网络与存储方案选型:先把通道搬到水面上
制品仓库迁移最大的卡点不是软件本身,而是“通道”。我特别强调这一点,是因为我最初在测试环境里推入了1000多个制品一切正常,但到生产切换时,第一波流量就把网关压垮了。
网络方案上,建议顺序设计三层:
- 存储层:Hadess的数据盘和元数据库要有足够的吞吐能力,SSD和机械盘的差异在大量小文件上传下非常悬殊。我用的是4块SSD组RAID10,针对1到10MB的小文件并发上传,效果提升非常明显。
- 入口层:在Nginx上同时对Nexus和Hadess做反向代理,通过权重调整灰度流量。
- DNS解析层:保持客户端访问域名不变,只改内部DNS解析目标,保留旧解析记录用于回滚。
如果没有内网DNS控制权,可以用客户端hosts文件做定向切换,但生产环境不建议用hosts,不好追踪。仓储目录规划一并说掉:在Hadess里,仓库名与格式尽量与Nexus保持一致,比如Nexus里叫nodejs-npm,在Hadess里也叫nodejs-npm。这样好处非常多——包管理工具下载地址不用改,构建脚本只用改host,甚至很多配置可以原封不动地使用,平滑性就体现在这些细节上。
2. 制品导入实操全流程
2.1 在Hadess上建立目标仓库
先说怎么在Hadess里建库。支持两种方式:UI手动创建和REST API创建。我建议用配置文件加REST API的方式,原因是配置可回放、可版本化,下次重建环境或者扩容节点,同一套配置可以直接生效。UI创建适合单次实验,不适合作为迁移交付物。
仓库类型我选的是raw和npm两类。raw用来承接Nexus里的generic制品,npm用来承接前端构建产物。有一个关键点必须注意:Hadess的仓库需要和Nexus保持相同的“Repository Path”参数,也就是URL里仓库段的名称必须一致,否则迁移完成后,历史构建记录里的绝对下载地址会失效。比如Nexus里仓库地址是/repository/nodejs-npm/,Hadess里就必须也叫nodejs-npm,不能自己改叫node-frontend。
创建仓库时的关键参数包括:名称、格式、可见类型(公开/私有)、存储容量和保留策略。以我的经验,不要一上来就开“公开”或“内部公开”,先保持私有,等切换策略确定后再放开访问。另外保留策略也要提前设好,否则Hadess默认保留全部分历史版本,存储会被打满。
2.2 认证机制:API Token比账号密码靠谱
Nexus迁移到Hadess时,认证是容易被忽略的部分。直接用账号密码上传当然可以,但生产环境有三个问题:末级权限不好控制、密码轮换影响面大、审计日志不直观。Hadess的API Token机制就合适得多。
我建议对每个迁移任务创建单独Token,并严格限制该Token只能访问目标仓库,迁移完成直接删除。比如三个并行迁移任务:一个npm、一个pypi、一个raw,就分别创建三组Token,互不影响。这样即使某个Token在推送日志里泄露,影响面也只是对应仓库,不会波及全部。Token范围与权限越小,后续审计越省心。
命令行配置示例大致长这样:
# npm仓库 npm config set //hadess.example.com/repository/nodejs-npm/:_authToken=hadess_token_xxxPyPI客户端则在~/.pypirc里填写:
[distutils] index-servers = hadess [hadess] repository = https://hadess.example.com/repository/pypi/ username = admin password = hadess_token_xxxMaven需要在settings.xml里增加server配置,注意server的id要和pom.xml里的repositoryId一致,这个很容易配错。配置完以后可以先用一个小制品试推,确认认证通了再批量跑。
2.3 逐类型迁移制品:先小后大,先静态后动态
实操层面,我建议按“小文件优先、大文件后置”的顺序推动,原因是小文件数量多、总量小,迁移成本低,更容易暴露权限和路径配置问题;大文件体积大,迁移时间取决于带宽,放后面可以和业务低谷期对齐。
给一个raw制品的推送脚本示例,可以直接拿去改:
#!/usr/bin/env bash # raw制品批量入库脚本 set -euo pipefail SOURCE_DIR="/data/nexus-export/raw" TARGET_URL="https://hadess.example.com/repository/raw-repo" TOKEN="hadess_token_xxx" find "$SOURCE_DIR" -type f | while read -r file; do rel_path="${file#$SOURCE_DIR/}" code=$(curl -s -o /dev/null -w "%{http_code}" \ -u "admin:$TOKEN" \ -X PUT \ --data-binary "@$file" \ "$TARGET_URL/$rel_path") if [ "$code" -eq 201 ] || [ "$code" -eq 200 ]; then echo "OK $rel_path" else echo "FAIL($code) $rel_path" >> upload_errors.log fi done注意这里用--data-binary而不是-F,确保二进制内容不做意外改写。很多raw制品是压缩包或者可执行文件,一旦被form表单编码处理,内容就坏了。
npm制品迁移可以分两步。先把npm包打包成tgz,再通过curl PUT到Hadess对应的npm仓库路径。如果你的环境允许临时配置registry,更省事的方案是直接改.npmrc指向Hadess,然后对每个包执行npm publish。不过批量发布前一定要确认包的版本号不会和已有制品冲突,否则会把旧版本覆盖掉。
PyPI制品用twine就能处理:
twine upload --repository-url https://hadess.example.com/repository/pypi/ dist/*Maven制品则用deploy插件:
mvn deploy:deploy-file \ -Durl=https://hadess.example.com/repository/maven-releases/ \ -DrepositoryId=hadess \ -Dfile=./app-1.0.0.jar \ -DpomFile=./pom.xmlDocker镜像如果原来在Nexus私有仓库,可以用docker pull再加docker tag指到Hadess的registry地址,最后docker push。更高效的方式是用skopeo copy,它不需要在本地起docker daemon,批量处理镜像时快很多,网络中断后重试也方便。
2.4 依赖引用地址统一收敛:平滑迁移的心脏
迁移做完数据,真正决定“平滑”的是引用地址。构建机上的.npmrc、pom.xml、requirements.txt、Dockerfile里面到处都可能写着Nexus地址。如果只是把制品搬到Hadess但客户端还在请求旧地址,就等于没有迁移。
我采用的收敛策略分三步:
- 直接替换:把CI流水线里的Nexus URL统一替换为Hadess URL。由于仓库路径段保持一致,这是纯字符串级替换。
- 兼容过渡:在Nginx上保留旧域名解析,针对旧Nexus地址的请求按path rewrite到Hadess。
- 更新缓存:包管理器有本地缓存和lockfile,要清理CI缓存或重新生成lockfile,否则会一直拿到旧包。
Nginx的兼容过渡配置参考如下:
server { listen 443 ssl; server_name old-nexus.example.com; location /repository/ { proxy_pass https://hadess.example.com/repository/; proxy_set_header Host hadess.example.com; } }这样即使有构建机没改配置,只要它在请求old-nexus.example.com/repository/nodejs-npm/xxx,也能实际落到Hadess上。这一步为灰度切换提供了足够的缓冲时间。
还有一件事必须有:团队里一定有人喜欢把完整下载URL写死在文档和脚本里,迁移后要做一次全库扫描,把旧域名一次性替换掉。我后来写了个sed脚本扫了全组织30多个Git仓库,才把漏网之鱼清完。建议在收尾阶段专门建一个“地址清理”任务卡,逐仓确认。
3. 数据迁移与安全校验
3.1 备份策略:先备份再动手,拒绝裸奔
准备阶段的最后一道保险是备份。Nexus层面,我建议把元数据库、blob存储目录以及配置文件都做一次快照式备份。Hadess层面,在迁移期间开启仓库回收站或快照功能。迁移过程一旦发现某个制品上传校验失败,可以从备份或回收站恢复。
实际迁移时我踩过一次坑:备份只做了blob存储目录,没有备份元数据库,结果想对比某个制品的原始上传时间时,Nexus已经拿不出可靠时间线了。所以备份务必是“存储+元数据+配置”三者合一。
另外要设好迁移过程中的写入门禁。生产环境如果Nexus还在被CI持续写入,马上全量同步会导致两边数据不一致。我的做法是给Nexus仓库设置只读策略,暂停新的发布动作,存量数据拉平后再统一切流量。如果没有只读开关,至少要在流水线里加一个“迁移期间禁止发布”的熔断开关,等Hadess数据校验通过后再恢复发布。
3.2 迁移报告:成功、失败、跳过,一个都不能少
上传脚本建议每一条记录都输出状态码,落到单独的文件。迁移完成以后,会看到三类记录:
- 成功:HTTP 200/201,制品已入库并可以通过路径下载校验。
- 失败:大部分是403、404、504。403多为Token权限范围没覆盖目标仓库,404多为路径段配错,504则是网关超时。
- 跳过:例如目标仓库里已经存在同路径且哈希完全一致的制品,可以跳过不传。
迁移报告的价值不只是给管理层看,更重要的是能作为重跑脚本的输入。我在脚本里加了--retry-only参数,只对状态码非2xx的行重新推送,配合sleep退避,第二轮就能把绝大多数失败项清掉。报告里还应该包括哈希比对结果,否则只能证明“文件上传到了”,不能证明“文件内容没坏”。
哈希校验建议在迁移结束后抽查5%到10%的制品,用curl下载到临时目录再算SHA256,和源端记录做比对。不要全量校验,全量太耗时;也不要只抽查几个,样本太小说明不了问题。
3.3 权限模型:从Nexus到Hadess别直接1:1搬
Nexus里的权限模型通常伴随着大量历史包袱,什么“开发一组的只读”“测试组的临时写”“管理员全权”,直接1:1对照搬过去,十分钟能建十多个角色,后期维护成本极高。我在迁移时索性收敛到三级权限:reader、contributor、admin,一个仓库一个组对应一套角色。
- reader:只能解析和下载制品。
- contributor:可以推送新制品,覆盖同版本。
- admin:管理仓库配置、Token、保留策略。
在Hadess上创建角色后,再将Nexus里的用户按业务线映射到Hadess的组。简单的原则是“先收敛后放权”——先给最小组权限,灰度两周再加权,避免权限过度开放出现误删事故。
还有一块容易漏:匿名访问。Nexus有些老仓库开了匿名可读,迁移到Hadess后必须重新审视。制品仓库里有些内部包名本身就能透露业务架构,匿名暴露的风险比多数人想象的大,建议一律关闭,除非有明确的制品分发需求。如果确实需要对外开放部分制品,单独建一个public仓库,不要让匿名访问渗透到内部仓库。
4. 常见问题与排查技巧实录
4.1 上传后哈希对不上,问题出在HTTP传输层
迁移过程中最让我头疼的一类问题:raw制品传上去之后,从Hadess下载下来的SHA256和本地源文件不一致。排查一轮后发现,原因是curl在连接Hadess时走了HTTP/2的gzip压缩响应,下载侧拿到的是压缩流重算后的哈希,自然对不上。
解决办法:下载校验时强制使用HTTP/1.1并禁用内容编码。
curl --http1.1 --compressed -sS \ -u "admin:$TOKEN" \ -o /tmp/check.bin \ "${BASE_URL}/$rel_path"这里再补一句,当网关开启了传输压缩而源站没有标记Content-Length时,这类问题就会暴露。更稳健的做法是直接对比上传时记录的ETag和下载响应的ETag。ETag一致,基本可以认定内容没有变化。
4.2 大体积制品上传中断、超时,和网关关系很大
超过2GB的二进制制品在批量迁移时特别容易超时。第一轮迁移我遇到214个超过1GB的文件,大多数压在网关默认60秒超时上。处理方式有三个方向:
- 调整Nginx代理超时时间:proxy_read_timeout 300s、proxy_send_timeout 300s、client_max_body_size 0(不限制)。
- 客户端断点续传:用支持断点续传的工具,或者把大文件切成多个分片并行传。
- 错峰迁移:大文件全排在夜间窗口跑,并加入失败自动重试。
脚本里加重试逻辑时要注意幂等性。制品上传天然具备幂等性,同一仓库同一路径覆盖上传不会产生重复资产,所以重试是安全的。但要注意重试间隔,建议指数退避,避免失败任务刚恢复又把网关打满。
4.3 客户端解析不到新域名,先查缓存再查解析
迁移后最常见的“看起来一切正常但就是拉不到包”场景:Hadess管理界面能登录、REST API也通,但构建机上的npm install一点就超时。第一反应查DNS,但我遇到过不是DNS问题的情况,而是npm缓存里存了旧仓库的metadata。此时删掉.npmrc中的注册地址后还需清缓存。
npm cache clean --force rm -rf node_modules package-lock.json npm install --registry=https://hadess.example.com/repository/nodejs-npm/同时,内网构建机的hosts解析未刷新的问题也很常见,建议在CI镜像里预设好新域名解析,或在构建前加一个快速连通性检查,直接curl Hadess仓库的健康检查地址,通不过就直接fail快速报错,而不是让后面一串任务挂在超时上。尤其是Jenkins的agent节点,经常有老镜像还在跑,它们里面的证书信任列表也可能没有更新,导致HTTPS握手失败。
4.4 要不要保留旧Nexus?我的答案是两个过渡期
平滑迁移不代表“旧系统当天退役”。我的实践是保留两个过渡期:
- 观察期(2周):Nexus保持只读在线,Hadess接收全部读写流量。期间如果发现Hadess有功能缺失或数据异常,随时把DNS权重切回Nexus只读恢复。
- 归档期(再2周):确认无问题后,把Nexus转为离线归档存储,不再对外提供下载,但仍保留数据盘一个月。期间不再接收新制品,只用于审计和追溯。
如果在观察期发现某个制品在Hadess上下载速度稳定且校验一致,就可以把这份资产在Nexus归档中标记为“已确认迁移”,这样后续管理更省心。再分享一个小技巧:在Jenkins或GitLab CI里加一个“迁移后回归”任务,每次新构建产物同时推送到Hadess和Nexus,持续两周,两边都成功才放行。用这个办法可以在不感知用户的情况下把双写做实,最后切流量的那一刻其实没多少流量要切了。
全部流程走完之后,我对“平滑迁移”的理解和一开始完全不同。真正让迁移平顺的,不是某个工具的高明,而是迁移前把仓库名、路径段、Token范围、缓存清理、权限收敛这些细节全部压实。我踩得最深的坑是哈希校验和客户端缓存,这两类问题在测试环境几乎不会暴露,只有在并发量上来后才会冒头。如果你也要做Nexus向Hadess的制品迁移,我建议第一周先只迁一个中小型仓库跑通全流程,把脚本和校验逻辑固化下来,再往全量推。先把流程走痛一次,后面就容易多了。