本文来自一次真实迁移。服务器、地址、域名、仓库数量、组件数量和企业配置均已脱敏。示例 Secret 和密码均为占位符。

系列导航:Kubernetes 重建迁移教程系列。建议先完成“在 Kubernetes 从零部署 Nexus Repository 3”教程。

一套运行多年的 Nexus Repository 2,通常已被 Maven、Jenkins、开发机和发布脚本深度依赖。真正困难的不是启动 Nexus3,而是在不丢制品、不破坏权限、不过度修改客户端的前提下迁移。

这次迁移的最终目标是:

  • Nexus2 的仓库、用户、角色和配置进入 Nexus3。
  • Nexus3 以单副本 StatefulSet 运行在 Kubernetes。
  • Blob Store 使用独立持久卷。
  • 元数据从 H2 迁移到 PostgreSQL。
  • 保留原有域名、VIP、端口和 /nexus 上下文路径。
  • 旧 Maven URL 在过渡期继续兼容。
  • 迁移失败时能够快速切回 Nexus2。

Sonatype 已在 2025 年 6 月 30 日停止维护 Nexus Repository 2,不再提供新功能和缺陷修复,并建议尽快迁移到 Nexus3。参见 Nexus Repository 2 Version Status。

最终迁移链路

flowchart LR
  A["Nexus2 最终版<br/>旧服务器"] -->|"官方 Upgrade"| B["Nexus3 中转版<br/>H2 / Kubernetes"]
  B -->|"原地版本升级"| C["Nexus3 目标版<br/>H2 / Kubernetes"]
  C -->|"Database Migrator"| D["Nexus3 目标版<br/>PostgreSQL / Kubernetes"]
  E["Maven / Jenkins"] --> F["原域名或 VIP"]
  F --> D
  D --> G["Blob Store PV"]
  D --> H["PostgreSQL"]

最关键的设计是把迁移拆成两个阶段:

  1. Nexus2 → Nexus3,并继续使用 H2。
  2. Nexus3 升到目标版本后,再做 H2 → PostgreSQL。

不要把仓库转换、版本升级、数据库迁移和流量切换混成一个步骤。

为什么 Nexus2 数据目录不能直接挂给 Nexus3

Nexus3 重新设计了组件元数据和 Blob Store。Nexus2 的工作目录不是 Nexus3 可直接读取的数据卷。

正确路径是:

  • 把 Nexus2 升到官方支持的最终版本。
  • 使用 Nexus2 Upgrade Agent 和 Nexus3 Upgrade Capability。
  • 迁移仓库内容和服务配置。
  • 等同步完成后执行 Finalize。

官方流程见 Upgrade from Nexus Repository 2。中转版本与目标版本必须根据执行时的官方升级矩阵确认,不能把某次成功组合当作永久规则。

迁移前盘点

类别需要记录
运行时Nexus、Java、操作系统、安装目录和工作目录
仓库Hosted、Proxy、Group、格式、远程地址和成员顺序
数据组件、资产、Blob 容量和 checksum 抽样
权限用户、角色、Realm、匿名访问
客户端Maven settings、Jenkins Credential、上传 URL
网络域名、VIP、端口、上下文路径、代理规则
任务清理、索引、备份任务
扩展插件、脚本、自定义 Realm

尤其要扫描 Nexus2 旧路径:

/nexus/content/repositories/releases/
/nexus/content/groups/public/

Nexus3 的现代路径通常是:

/nexus/repository/releases/
/nexus/repository/public/

客户端无法一次性全部修改时,应在外层代理配置明确的重写规则,并为旧路径设置移除日期。

完整备份 Nexus2

至少保留:

  1. Nexus2 程序目录。
  2. Nexus2 工作目录。
  3. 仓库存储和校验记录。

在写入冻结后执行文件系统备份:

systemctl stop nexus

tar -C /opt -czf /backup/nexus2-program.tar.gz nexus-2
tar -C /data -czf /backup/nexus2-work.tar.gz sonatype-work
sha256sum /backup/nexus2-*.tar.gz > /backup/SHA256SUMS

运行中直接复制目录,可能产生元数据与制品不一致的备份。

Kubernetes 中的存储设计

Nexus 的 Blob Store 读写频繁。本次使用独立本地 PV,并设置:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nexus-data
spec:
  capacity:
    storage: <capacity>
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nexus-local
  local:
    path: /data/nexus
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values: [repo-node]

Retain 很重要:误删 StatefulSet 或 PVC 时,历史制品不会随之被自动销毁。

本地 PV 会把 Pod 固定在特定节点,因此它不是高可用方案。对单副本社区版而言,这个取舍是用更清晰的故障边界换取更低的存储复杂度。

保持上下文路径和健康检查

为减少客户端变更,继续使用 /nexus:

application-port=8081
application-host=0.0.0.0
nexus-context-path=/nexus
nexus.datastore.enabled=true

探针使用状态接口,而不是首页 HTML:

startupProbe:
  httpGet:
    path: /nexus/service/rest/v1/status
    port: http
  failureThreshold: 60
  periodSeconds: 10
  timeoutSeconds: 5

首次升级和索引重建可能持续数分钟。connection refused 只表示进程尚未监听端口,真正原因要看 nexus.log。

Nexus2 → Nexus3 的正确顺序

先让 Nexus3 使用 H2:

  1. 创建 Upgrade Agent / Upgrade Capability。
  2. 建立连接并执行预检查。
  3. 迁移 repository content。
  4. 迁移 server configuration。
  5. 等同步队列归零。
  6. 冻结 Nexus2 上传。
  7. 执行 Finalize 和 Done。

Finalize 前至少验证 Hosted 最新版本、Proxy 拉取、Group 顺序、匿名访问和真实 Maven 构建。

Upgrade Wizard 不是双向同步系统。迁移开始后,不要继续修改 Nexus2 的用户、角色和仓库结构。

先升级 H2,再迁 PostgreSQL

完成 Nexus2 迁移后,仍使用 H2 将 Nexus3 升到目标版本。升级前同时备份 H2 和 Blob Store,确保两者来自同一恢复点。

启动新版本后等待内部 schema、upgrade、browse 和 search 任务结束。Pod Ready 不等于升级任务完成,任务运行期间不要反复重启实例。

准备 PostgreSQL

CREATE ROLE nexus LOGIN PASSWORD '<random-password>';
CREATE DATABASE nexus OWNER nexus;

\connect nexus

CREATE SCHEMA nexus AUTHORIZATION nexus;
GRANT USAGE, CREATE ON SCHEMA nexus TO nexus;
ALTER ROLE nexus SET search_path TO nexus;

密码仅保存在 Kubernetes Secret 中:

kubectl -n repository create secret generic nexus-db \
  --from-literal=username=nexus \
  --from-literal=password="$(openssl rand -base64 36)"

Base64 是编码,不是加密。Secret 的明文和输出不能进入 Git 或 CI 日志。

使用 Database Migrator

迁移器版本必须与 Nexus 目标版本匹配,并校验 SHA256。官方说明见 Migrating to a New Database。

sha256sum nexus-db-migrator-<version>.jar

从 Nexus Repository 3.93.0 起,自建部署至少需要 Java 25,执行迁移器前也要确认运行时要求。参见 2026 Release Notes。

迁移前完全停止 Nexus:

kubectl -n repository scale statefulset/nexus --replicas=0
kubectl -n repository wait --for=delete pod/nexus-0 --timeout=180s

若密码包含 +、&、%、?、# 等字符,写入 JDBC URL 前必须编码,而且禁止打印完整 URL。

迁移 Job Complete 不是唯一标准,还要检查最终汇总是否存在 skipped 或 failed 记录。失败后不能直接对已有数据的 schema 重跑,应先定位原因并恢复到干净的目标库。

迁移完成后,在 Nexus 仍停止时执行:

VACUUM (FULL, ANALYZE);

连接 PostgreSQL 启动最终版本

env:
  - name: NEXUS_DATASTORE_NEXUS_JDBCURL
    value: jdbc:postgresql://postgresql.repository.svc.cluster.local:5432/nexus?currentSchema=nexus
  - name: NEXUS_DATASTORE_NEXUS_USERNAME
    value: nexus
  - name: NEXUS_DATASTORE_NEXUS_PASSWORD
    valueFrom:
      secretKeyRef:
        name: nexus-db
        key: password

启动后等待 Browse/Search 重建任务完成,再验证搜索、下载和上传。

五层验收

  1. Pod 与日志:Ready、重启稳定、无数据库和 Blob 错误。
  2. 状态接口:/nexus/service/rest/v1/status 正常。
  3. 仓库:Hosted、Proxy、Group 各自验证。
  4. 真实构建:执行 Maven 构建,覆盖 POM、metadata、checksum 和认证。
  5. 上传:用临时版本验证 Hosted 上传并清理。

只用浏览器打开管理页面,无法证明 Maven 构建链路正常。

VIP 无感切换与回滚

大量历史客户端依赖固定地址时,可以把旧地址作为 VIP 切到新节点。

切换前必须满足:

  • 旧 Nexus2 已停止。
  • 旧节点不再持有 VIP。
  • 新 Nexus3 已通过内部地址验证。
  • 添加 VIP 后重新执行下载、上传和构建。

任何时刻都不能让两台机器同时持有同一个 IP,否则会出现 ARP 抖动和随机落点。

回滚顺序:冻结新上传、移除新节点 VIP、确认网络收敛、启动旧 Nexus2、恢复旧入口。新环境一旦接受写入,回滚还必须处理两端数据差异。

社区版用量治理

截至本文发布时,Nexus Repository Community Edition 的使用限制为 40,000 个组件或每天 100,000 个请求;超限时不能新增组件,指标更新可能延迟约一小时。参见 Usage Center。

治理原则:

  • 优先清理长期未使用的 Proxy 缓存。
  • Hosted releases、snapshots 和内部 third-party 使用独立保留策略。
  • 清理前备份 PostgreSQL。
  • 先软删除,过观察期后再 Compact Blob Store。
  • 不要仅凭页面短时间未刷新就判断清理失败。

Proxy 可从上游重新拉取,Hosted 很可能是企业唯一来源,二者不能使用同一清理策略。

最容易踩的坑

  • 在错误版本上迁数据库。
  • Nexus 未停止就运行 Migrator。
  • 只授权数据库,没有授权 schema。
  • JDBC 密码未编码。
  • Job Complete 后立即重启,忽略索引任务。
  • 只备份数据库,不备份 Blob Store。
  • 只验证管理页面。
  • 提前销毁旧服务器和恢复点。

总结

Nexus2 升级 Nexus3 的核心不是“换镜像”,而是控制四个边界:

  1. 内容和元数据通过支持的路径转换。
  2. 版本升级和数据库迁移分阶段完成。
  3. 数据库、Blob Store 与客户端访问路径共同验证。
  4. 每个不可逆步骤之前都有独立恢复点。

坚持先备份、分阶段、逐层验证、最后切流量,才能把停机集中在最终冻结、数据库迁移和入口切换窗口,而不是让整个项目长期处于不可用状态。