如何使用 Nexus 备份和恢复 nexus

前提条件

从集群中卸载 Nexus operator。

kubectl get subscription --all-namespaces | grep nexus-ce-operator | awk '{print "kubectl delete subscription "$2" -n "$1}' | sh

# Output:
# subscription.operators.coreos.com "nexus-ce-operator" deleted
为什么要卸载 Nexus operator?

在备份和恢复过程中,Operator 可能会对 Nexus 资源进行 reconcile,从而干扰操作。如果不卸载 Operator,可能会出现以下问题:

  1. Operator 可能会基于 Nexus CR 重新创建或重启工作负载,打断备份/恢复流程,导致数据不一致。
  2. 您临时删除的一些资源(例如 Service)可能会被重新创建,或者与从备份恢复的资源(例如 Ingress)产生冲突。
卸载 Nexus operator 的影响

卸载 Operator 后,对 Nexus CR 的修改将不会生效,例如调整资源或存储大小。

卸载 Operator 不会导致现有实例出现故障。

备份

备份包含四个步骤:

  1. 在 Nexus Web 上创建备份任务。
  2. 删除 nexus 实例的 Service 以防止数据变更。
  3. 备份 nexus 数据并验证备份。
  4. 备份完成后恢复 nexus 实例。

在 Nexus Web 上创建备份任务

以管理员身份登录 Nexus Web UI。导航至 Settings -> System -> Tasks -> Create task -> 选择 Admin - Backup H2 Database,填写:

  • Task enabled: true
  • Task name: nexus-backup(或任意您喜欢的名称)
  • Notification email: (可选)user@example.com
  • Send Notification on: (可选)Failure
  • Location: /nexus-data 下的任意子目录(例如 /nexus-data/backup-dir
  • Task frequency: Manual

删除 nexus 实例的 Service 以防止备份期间数据变更

为防止备份期间写入,暂时停止实例的服务端点,通过删除前端 Pod 的 Service 实现:

export NEXUS_NAMESPACE=<要备份的 NEXUS 实例所在命名空间>
export NEXUS_NAME=<要备份的 NEXUS 实例名称>
export NEXUS_BACKUP_DIR=<上一步设置的备份任务位置>
export POD_NAME=$(kubectl -n ${NEXUS_NAMESPACE} get pod ${NEXUS_NAME}-nxrm-ha-0 -o jsonpath='{.metadata.name}')

kubectl delete service -l release=${NEXUS_NAME} -n ${NEXUS_NAMESPACE}

#output:
# service "nexus-nxrm-ha" deleted
# service "nexus-nxrm-ha-hl" deleted

此操作不会删除 Pod 或数据,仅停止外部流量。

备份 nexus 数据并验证备份

备份 blob 存储

mkdir -p nexus-backup/blobs
kubectl -n ${NEXUS_NAMESPACE} cp ${POD_NAME}:/nexus-data/blobs nexus-backup/blobs

备份节点 ID keystore

mkdir -p nexus-backup/node
kubectl -n $NEXUS_NAMESPACE cp ${POD_NAME}:/nexus-data/keystores/node nexus-backup/node

导出并备份数据库

export NEXUS_ADMIN_USER=admin
export NEXUS_ADMIN_PASSWORD=<admin-password>

kubectl exec -ti $POD_NAME -n $NEXUS_NAMESPACE -- curl -u $NEXUS_ADMIN_USER:$NEXUS_ADMIN_PASSWORD 'localhost:8081/service/rest/v1/tasks?type=h2.backup.task'

# output:
#{
#  "items" : [ {
#    "id" : "b379f8cb-ea52-4bae-9eed-84aed7686a63",
#    "name" : "nexus-backup",
#    "type" : "h2.backup.task",
#    "message" : null,
#    "currentState" : "WAITING",
#    "lastRunResult" : null,
#    "nextRun" : null,
#    "lastRun" : null
#  } ],
#  "continuationToken" : null
#}

export TASK_ID=b379f8cb-ea52-4bae-9eed-84aed7686a63 # 替换为上面输出中的任务 ID
kubectl exec -ti $POD_NAME -n $NEXUS_NAMESPACE -- curl -s -o /dev/null -w "%{http_code}\n" -u $NEXUS_ADMIN_USER:$NEXUS_ADMIN_PASSWORD -X POST localhost:8081/service/rest/v1/tasks/${TASK_ID}/run

# Code	Description
# 204	任务已运行
# 404	任务未找到
# 405	任务已禁用
# output:
# 204

kubectl exec -ti $POD_NAME -n $NEXUS_NAMESPACE -- curl -u $NEXUS_ADMIN_USER:$NEXUS_ADMIN_PASSWORD localhost:8081/service/rest/v1/tasks/${TASK_ID}

#output(lastRun 和 lastRunResult 会有所不同):
# {
#   "id" : "b379f8cb-ea52-4bae-9eed-84aed7686a63",
#   "name" : "nexus-backup",
#   "type" : "h2.backup.task",
#   "message" : "Backup embedded h2 database to the specified location",
#   "currentState" : "WAITING",
#   "lastRunResult" : "OK",
#   "nextRun" : null,
#   "lastRun" : "2025-11-03T15:21:50.302+00:00"
# }

kubectl exec -ti $POD_NAME -n $NEXUS_NAMESPACE -- ls $NEXUS_BACKUP_DIR

# output:
# nexus-2025-11-03-15-12-34.zip

然后从 Pod 复制导出的文件:

mkdir -p nexus-backup/restore-from-backup
kubectl -n $NEXUS_NAMESPACE cp ${POD_NAME}:$NEXUS_BACKUP_DIR nexus-backup/restore-from-backup

验证备份内容

复制完成后,备份目录应至少包含以下文件夹:

  • nexus-backup/blobs
  • nexus-backup/node
  • nexus-backup/restore-from-backup

根据您的实例,目录中的文件名可能不同。

nexus-backup
├── blobs
   └── default
       ├── content
...
├── node
   ├── private.ks
   └── trusted.ks
└── restore-from-backup
    └── nexus-2025-11-03-15-12-04.zip

备份成功后,恢复 nexus 实例服务

进入 Administrator -> Marketplace -> Operator Hub 页面,切换到目标集群,然后重新部署 Alauda Build of Nexus Operator。

恢复

前提条件

确定目标命名空间,以及是恢复到原实例(覆盖数据)还是新实例。恢复期间确保 Operator 保持卸载状态以避免干扰。

恢复包含五个步骤:

  1. 如果尚未卸载,先从集群卸载 Nexus ce operator。
  2. 删除 nexus 实例的 Service 以防止恢复期间数据变更。
  3. 删除目标实例的现有数据。
  4. 重启 Pod 让 Nexus 从备份恢复。
  5. 清理临时数据并恢复正常服务。

如果尚未卸载,先从集群卸载 Nexus ce operator。

kubectl get subscription --all-namespaces | grep nexus-ce-operator | awk '{print "kubectl delete subscription "$2" -n "$1}' | sh

# Output:
# subscription.operators.coreos.com "nexus-ce-operator" deleted

删除 nexus 实例的 Service 以防止恢复期间数据变更

export NEXUS_NAMESPACE=<要恢复的 NEXUS 实例所在命名空间>
export NEXUS_NAME=<要恢复的 NEXUS 实例名称>
export POD_NAME=$(kubectl -n ${NEXUS_NAMESPACE} get pod ${NEXUS_NAME}-nxrm-ha-0 -o jsonpath='{.metadata.name}')

kubectl delete service -n $NEXUS_NAMESPACE -l release=$NEXUS_NAME

# output:
# service "kychen-nxrm-ha" deleted
# service "kychen-nxrm-ha-hl" deleted

删除目标实例的现有数据

删除目标 Pod 中的现有数据,避免旧数据与恢复数据混合。如果权限限制无法在 Pod 内删除,请根据您的存储类说明直接在挂载卷上删除数据。

kubectl -n $NEXUS_NAMESPACE exec -it ${POD_NAME} -- \
  rm -rf /nexus-data/blobs \
         /nexus-data/keystores/node \
         /nexus-data/db/

如果必须直接在节点/PV 上删除,也请删除相同目录。

重启 Pod 让 Nexus 从备份恢复

导入备份数据

kubectl -n $NEXUS_NAMESPACE cp nexus-backup/blobs ${POD_NAME}:/nexus-data
kubectl -n $NEXUS_NAMESPACE cp nexus-backup/node ${POD_NAME}:/nexus-data/keystores
unzip nexus-backup/restore-from-backup/nexus-*.zip -d /tmp/nexus-restore
kubectl -n $NEXUS_NAMESPACE cp /tmp/nexus-restore ${POD_NAME}:/nexus-data/db

确认 Pod 内以下目录包含数据:

  • /nexus-data/blobs
  • /nexus-data/keystores/node
  • /nexus-data/restore-from-backup

重启 Pod 以恢复

kubectl -n $NEXUS_NAMESPACE delete pod ${POD_NAME}

等待新 Pod 启动并让 Nexus 完成内部恢复流程。

清理临时数据并恢复正常服务

确认数据已恢复且实例正常后,删除临时恢复文件,避免未来重启时重复导入:

kubectl -n $NEXUS_NAMESPACE exec -it ${POD_NAME} -- rm -rf /nexus-data/restore-from-backup

进入 Administrator -> Marketplace -> Operator Hub 页面,切换到目标集群,然后重新部署 Alauda Build of Nexus Operator。

登录 Nexus Web,检查所有仓库和数据是否完整。

故障排查

备份任务失败并显示 AccessDeniedException

检查任务状态时,如果看到 lastRunResult 为 FAILED:

kubectl exec -ti $POD_NAME -n $NEXUS_NAMESPACE -- curl -u $NEXUS_ADMIN_USER:$NEXUS_ADMIN_PASSWORD localhost:8081/service/rest/v1/tasks/${TASK_ID}
Defaulted container "nxrm-app" out of: nxrm-app, request-log, audit-log, tasks-log, chown-nexusdata-owner-to-nexus-and-init-log-dir (init)
{
  "id" : "0a8a4597-1cee-4f4c-8d86-6d81eb2cd8eb",
  "name" : "nexus-backup",
  "type" : "h2.backup.task",
  "message" : "Backup embedded h2 database to the specified location",
  "currentState" : "WAITING",
  "lastRunResult" : "FAILED",
  "nextRun" : null,
  "lastRun" : "2025-11-05T06:23:31.010+00:00"
}

您可以查看任务日志获取更多细节:

kubectl logs $POD_NAME -n $NEXUS_NAMESPACE | grep $TASK_ID -A 10 -B 10
Defaulted container "nxrm-app" out of: nxrm-app, request-log, audit-log, tasks-log, chown-nexusdata-owner-to-nexus-and-init-log-dir (init)
2025-11-05 06:23:31,023+0000 INFO  [quartz-8-thread-20] *TASK org.sonatype.nexus.internal.datastore.task.H2BackupTask - Task configuration: {.name=nexus-backup, .id=0a8a4597-1cee-4f4c-8d86-6d81eb2cd8eb, .typeName=Admin - Backup H2 Database, .visible=true, .typeId=h2.backup.task, .notificationCondition=FAILURE, .updated=2025-11-05T06:19:54.814Z, .recoverable=false, .enabled=true, .message=Backup embedded h2 database to the specified location, location=/nexus/backup, .exposed=true, .created=2025-11-05T06:19:54.814Z}
2025-11-05 06:23:31,023+0000 INFO  [quartz-8-thread-20] *TASK org.sonatype.nexus.internal.datastore.task.H2BackupTask - Task log: /opt/sonatype/sonatype-work/nexus3/log/tasks/allTasks.log
2025-11-05 06:23:31,024+0000 ERROR [quartz-8-thread-20] *TASK org.sonatype.nexus.bootstrap.entrypoint.configuration.ApplicationDirectoriesImpl - Failed to create directory: /nexus/backup
2025-11-05 06:23:31,031+0000 WARN  [quartz-8-thread-20] *TASK org.sonatype.nexus.quartz.internal.task.QuartzTaskJob - Task 0a8a4597-1cee-4f4c-8d86-6d81eb2cd8eb : 'nexus-backup' [h2.backup.task] execution failure
java.lang.RuntimeException: java.nio.file.AccessDeniedException: /nexus

这表明 Nexus 没有权限写入指定的备份位置。请确保备份目录位于 /nexus-data 下。

进入 Administrator -> Marketplace -> Operator Hub 页面,切换到目标集群,重新部署 Alauda Build of Nexus Operator,登录 Nexus Web,修改备份任务,使用 /nexus-data 下的目录,如 /nexus-data/backup