目录

Sonatype Nexus 3 从 H2 版本(latest) 降级至 OrientDB 版本(3.68.1) 的完整技术方案

# 实战指南:Sonatype Nexus 3 从 H2 版本(latest) 降级至 OrientDB 版本(3.68.1) 的完整技术方案

# 1. 背景与挑战

在容器化部署 Sonatype Nexus Repository Manager 3 时,许多运维人员习惯使用 sonatype/nexus3:latest 标签。然而,Nexus 3 在近期(3.69.0+ 及 3.70.0+)进行了底层的重大架构变革,将默认数据库从 OrientDB 迁移到了 H2

这就导致了一个严峻的问题:一旦容器自动升级到 latest 版本(H2架构),数据文件结构将被修改。此时若直接将镜像标签回滚至旧版本(如 3.68.1),服务将因无法识别新版数据库 Schema 而启动失败。

本文将详细介绍如何在无法直接回滚的情况下,通过 双实例并行迁移(Side-by-Side Migration) 的方式,安全地将数据“降级”迁移回长期稳定版 3.68.1。


# 2. 核心原理:为什么不能原地降级?

Nexus 3 的版本分水岭在于数据库架构:

  • 3.68.1 及以下:使用 OrientDB(嵌入式图数据库)。
  • 3.69.0 及以上:默认使用 H2(嵌入式关系型数据库),并支持迁移至 PostgreSQL。

升级过程包含不可逆的数据库 Schema 变更。因此,唯一的“降级”路径是:部署一个新的 3.68.1 实例,通过 API 或客户端工具将数据从高版本实例“搬运”到低版本实例。


# 3. 环境准备:部署目标实例 (Target)

我们需要启动一个新的 Nexus 3.68.1 容器。假设源实例(Source, latest版)运行在端口 15581,我们将新实例部署在 15582

# 3.1 目录权限修正(关键)

Nexus 容器内部使用 UID 200 运行,宿主机挂载目录必须赋予对应权限,否则会报 Permission Denied。

mkdir -p /opt/nexus3.68/data
chown -R 200:200 /opt/nexus3.68/data

1
2
3

# 3.2 Docker Compose 配置

创建 docker-compose.yml,锁定版本为 3.68.1(OrientDB 时代的最终稳定版):

version: '3'
services:
  nexus:
    image: sonatype/nexus3:3.68.1
    container_name: nexus3-target
    restart: always
    mem_limit: 4g
    environment:
      - "INSTALL4J_ADD_VM_PARAMS=-Xms2g -Xmx2g -XX:MaxDirectMemorySize=2g"
    ports:
      - "15582:8081"
    volumes:
      - ./data:/nexus-data
    ulimits:
      nofile:
        soft: 65536
        hard: 65536

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 4. 数据迁移方案

针对不同类型的仓库,需采用不同的迁移策略。

# 4.1 Maven / NPM / Raw / PyPI 仓库 (Shell 流式脚本)

对于文件型资产,我们使用 Shell 脚本结合 curljq 进行流式传输(无需下载到本地磁盘)。

脚本痛点与修正: 在编写 Shell 脚本时,若密码包含特殊字符(如 $, !, @),必须使用单引号包裹,否则 Shell 会尝试进行变量替换导致认证失败(HTTP 401)。

通用迁移脚本 (migrate_assets.sh):

#!/bin/bash

# === 配置区域 ===
# 必须使用单引号 '' 包裹密码,防止特殊字符被 Shell 解析
SOURCE_HOST="http://192.168.1.10:15581"
SOURCE_USER="admin"
SOURCE_PASS='Your$Complex!Pass'  
SOURCE_REPO="maven-releases"

TARGET_HOST="http://192.168.1.10:15582"
TARGET_USER="admin"
TARGET_PASS='Your$Complex!Pass'
TARGET_REPO="maven-releases"
# ===============

if ! command -v jq &> /dev/null; then
    echo "Error: jq is required."
    exit 1
fi

CONTINUATION_TOKEN=""

while true; do
    # 构造分页 API 请求
    API_URL="$SOURCE_HOST/service/rest/v1/assets?repository=$SOURCE_REPO"
    if [ -n "$CONTINUATION_TOKEN" ] && [ "$CONTINUATION_TOKEN" != "null" ]; then
        API_URL="${API_URL}&continuationToken=${CONTINUATION_TOKEN}"
    fi

    echo "Fetching list from: $API_URL"
    # 获取资产列表 JSON
    RESPONSE=$(curl -s -f -u "$SOURCE_USER:$SOURCE_PASS" "$API_URL")

    if [ $? -ne 0 ]; then
        echo "❌ Authentication Failed or Network Error."
        exit 1
    fi

    # 解析 JSON 并执行流式传输
    echo "$RESPONSE" | jq -r '.items[] | "\(.path)|\(.downloadUrl)"' | while IFS='|' read -r ASSET_PATH DOWNLOAD_URL; do
        if [[ "$ASSET_PATH" == *".index"* ]] || [[ "$ASSET_PATH" == *"maven-metadata"* ]]; then
            continue # 跳过元数据,建议迁移后重建
        fi

        echo "Migrating: $ASSET_PATH"
        TARGET_UPLOAD_URL="$TARGET_HOST/repository/$TARGET_REPO/$ASSET_PATH"
        
        # 管道操作:Source下载流 -> Target上传流
        curl -s -f -u "$SOURCE_USER:$SOURCE_PASS" "$DOWNLOAD_URL" | \
        curl -s -o /dev/null -w "%{http_code}" -u "$TARGET_USER:$TARGET_PASS" -T - "$TARGET_UPLOAD_URL"
        
        echo "" # 换行
    done

    # 处理分页
    CONTINUATION_TOKEN=$(echo "$RESPONSE" | jq -r '.continuationToken')
    if [ -z "$CONTINUATION_TOKEN" ] || [ "$CONTINUATION_TOKEN" == "null" ]; then
        break
    fi
done

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 4.2 Docker 镜像仓库

Docker 镜像无法通过上述文件方式迁移,必须保留 Layer 及其元数据结构。需使用客户端脚本:

  1. Login: 同时登录 Source 和 Target 仓库。
  2. Pull: 从 Source 拉取镜像。
  3. Tag: 修改 Tag 指向 Target 地址。
  4. Push: 推送到 Target。

# 5. 常见故障排查 (Troubleshooting)

在实施过程中,以下三个问题最为高频:

# Q1: 执行脚本提示“无法获取资产列表”或 401 错误

  • 现象:确认账号密码正确,但脚本一直报错。
  • 原因:Shell 脚本中密码使用了双引号 SOURCE_PASS="Pass$word"。Bash 会试图解释 $word 变量,导致发送的密码错误。
  • 解决:将脚本中的密码定义改为单引号:SOURCE_PASS='Pass$word'

# Q2: 浏览器访问一直卡在 "Loading Nexus..." 或 "Loading baseapp-prod.js"

  • 现象:切换版本后,Web 界面无限加载,F12 控制台报错 Uncaught SyntaxError
  • 原因:Nexus 是单页应用 (SPA)。浏览器缓存了 latest 版本的前端 JS 文件,但请求的是 3.68.1 的后端 API,版本不匹配导致前端崩溃。
  • 解决
  1. 强制刷新Ctrl + F5
  2. 无痕模式:使用浏览器无痕模式访问(最推荐)。
  3. 等待启动:确保 docker logs 显示 Started Sonatype Nexus OSS 后再刷新。

# Q3: 界面提示 "100,000 Components Threshold" 警告

  • 现象:Usage 页面显示红色的阈值进度条。
  • 解读:在 OSS 免费版中,这只是性能建议(Soft Limit)。它提示在使用嵌入式数据库(OrientDB/H2)时,建议组件数控制在 10万以内以保证最佳性能。
  • 结论:这不是硬性限制,不会阻断使用,忽略即可。

# 6. 最佳实践总结

  1. **生产环境禁用 :latest**:在 docker-compose.yml 中始终锁定具体的版本号(如 3.68.13.70.1-java17),避免意外升级。
  2. 数据备份:在执行任何升级或迁移操作前,通过 Nexus 自带的 Task ("Admin - Export databases for backup") 进行备份。
  3. 版本选择
  • 追求稳定且不想折腾数据库:选择 3.68.1 (OrientDB 最终版)。
  • 新项目启动:选择 3.70+ (H2 数据库),性能更好,面向未来。
上次更新: 2025/12/31, 15:46:16
最近更新
01
OpenClash Fake-IP 模式下 Hyper-V 虚拟机 Docker 镜像拉取超时问题
12-30
02
NVM 安装与 WebStorm 配置全指南
12-29
03
[故障排查] Docker 容器日志导致磁盘爆满的排查与日志轮转策略 (Portainer)
12-29
更多文章>