如何使用 Velero 备份和恢复 nexus

适用范围

本方案适用于运行版本为 3.76 及以上的 Nexus 实例。

WARNING

本方案不支持使用 HostPath 存储部署的 Nexus 实例。

如果需要备份使用 HostPath 存储的实例,请参考如何使用 Nexus 自带功能备份和恢复 nexus

术语说明

术语含义
Original Instance备份前的工具实例
New Instance恢复后的工具实例
Original Namespace备份前工具实例所在的命名空间
New Namespace恢复后工具实例所在的命名空间
Nexus CR Resource用于描述 Nexus 实例部署方式的资源。Nexus Operator 会基于该资源部署 Nexus 实例。

前置条件

  1. 部署 MinIO 对象存储:本备份恢复方案依赖对象存储保存备份数据,因此需提前部署 MinIO 实例。ACP 提供了 快速创建 MinIO 实例

  2. 部署 Velero:Velero 是备份恢复工具。ACP 提供 Alauda Container Platform Data Backup for Velero,可在 Administrator 视图的 Marketplace -> Cluster Plugins 中搜索 Velero 进行部署。

  3. 安装 mc 命令行工具:mc 是 MinIO 的命令行管理工具。安装说明请参见 MinIO 官方文档

  4. 安装 kubectl 命令行工具:kubectl 是 Kubernetes 的命令行管理工具。安装说明请参见 Kubernetes 官方文档

  5. 从集群中卸载 nexus operator。

    kubectl get subscription --all-namespaces | grep nexus-ce-operator | awk '{print "kubectl delete subscription "$2" -n "$1}' | sh
    
    # 输出示例:
    # subscription.operators.coreos.com "nexus-ce-operator" deleted
    为什么要卸载 Nexus operator?

    恢复过程中,Velero 需要启动备份 Pod 来恢复 PVC 数据,耗时较长。如果不卸载 Operator,可能出现以下问题:

    1. Operator 可能基于 Nexus CR 重新创建工作负载,导致恢复的 Pod 被重启或重新创建,最终导致恢复中断或失败。
    2. 部分恢复的资源可能与已有资源冲突,如 Ingress。
    卸载 Nexus operator 的影响

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

    卸载 Operator 不会导致现有实例异常。

为方便后续操作,请先设置以下环境变量:

export MINIO_HOST=<MinIO 实例访问地址> # 示例:http://192.168.1.100:32008
export MINIO_ACCESS_KEY=<MinIO 实例访问密钥> # 示例:minioadmin
export MINIO_SECRET_KEY=<MinIO 实例密钥> # 示例:minioadminpassword
export MINIO_ALIAS_NAME=<MinIO 实例别名> # 示例:myminio

export VELERO_BACKUP_BUCKET=<Velero 备份桶名称> # 示例:backup
export VELERO_BACKUP_REPO_NAME=<Velero 备份仓库名称> # 示例:nexus-backup-repo

export NEXUS_NAMESPACE=<待备份的 NEXUS 实例命名空间>
export NEXUS_NAME=<待备份的 NEXUS 实例名称>

运行以下命令配置 mc 工具并测试连接:

mc alias set ${MINIO_ALIAS_NAME} ${MINIO_HOST} ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
mc ping ${MINIO_ALIAS_NAME}

# 示例输出:
#   1: http://192.168.131.56:32571:32571   min=98.86ms    max=98.86ms    average=98.86ms    errors=0   roundtrip=98.86ms
#   2: http://192.168.131.56:32571:32571   min=29.57ms    max=98.86ms    average=64.21ms    errors=0   roundtrip=29.57ms
#   3: http://192.168.131.56:32571:32571   min=29.57ms    max=98.86ms    average=52.77ms    errors=0   roundtrip=29.88ms

如果能成功 ping 通 MinIO 实例,说明 mc 配置正确。

备份

准备工作

备份准备包括两步:

  1. 创建 Bucket
  2. 配置 Velero 备份仓库

创建 Bucket

运行以下命令创建用于存储备份数据的 Bucket:

mc mb ${MINIO_ALIAS_NAME}/${VELERO_BACKUP_BUCKET}

# 输出示例:
# Bucket created successfully `myminio/backup`.

配置 Velero 备份仓库

运行以下命令创建 Velero 备份仓库:

nexus_backup_repo_credentials_secret_name="nexus-backup-repo-credentials"
minio_nexus_backup_bucket="${VELERO_BACKUP_BUCKET:-backup}"
minio_nexus_backup_bucket_directory="nexus"

kubectl apply -f - <<EOF
apiVersion: v1
stringData:
  cloud: |
    [default]
    aws_access_key_id = ${MINIO_ACCESS_KEY}
    aws_secret_access_key = ${MINIO_SECRET_KEY}
kind: Secret
metadata:
  labels:
    component: velero
    cpaas.io/backup-storage-location-repo: ${VELERO_BACKUP_REPO_NAME}
  name: ${nexus_backup_repo_credentials_secret_name}
  namespace: cpaas-system
type: Opaque
---
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
  name: ${VELERO_BACKUP_REPO_NAME}
  namespace: cpaas-system
spec:
  config:
    checksumAlgorithm: ""
    insecureSkipTLSVerify: "true"
    region: minio
    s3ForcePathStyle: "true"
    s3Url: ${MINIO_HOST}
  credential:
    key: cloud
    name: ${nexus_backup_repo_credentials_secret_name}
  objectStorage:
    bucket: ${minio_nexus_backup_bucket}
    prefix: ${minio_nexus_backup_bucket_directory}
  provider: aws
EOF

# 输出示例:
# secret/nexus-backup-repo-credentials created
# backupstoragelocationrepo.ait.velero.io/nexus-backup-repo created

执行备份

手动备份包括四步:

  1. 删除 nexus 实例服务以防止数据变更
  2. 创建备份卷策略
  3. 执行备份
  4. 备份成功后恢复 nexus 实例服务

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

运行以下命令删除 nexus 实例服务:

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

# 输出示例:
# service "nexus-nxrm-ha" deleted
# service "nexus-nxrm-ha-hl" deleted

创建备份卷策略

卷策略用于指定 PVC 的备份方式。默认策略是使用 fs-backup 方式备份 PVC。

export BACKUP_POLICY_NAME=${BACKUP_POLICY_NAME:-nexus-backup}
export VELERO_BACKUP_REPO_NAME=${VELERO_BACKUP_REPO_NAME:-nexus-backup-repo}

kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: ${BACKUP_POLICY_NAME}-volume-policy
  namespace: cpaas-system
data:
  resourcepolicies: |
    version: v1
    volumePolicies:
      - conditions:
          csi: {}
        action:
          type: fs-backup
      - conditions:
          csi: {}
        action:
          type: fs-backup
EOF

# 输出示例:
# configmap/nexus-backup-volume-policy created

如果 PVC 支持快照,可以使用快照方式备份 PVC 以加快备份速度。需要修改卷策略 ConfigMap,指定存储类使用快照备份。例如,若 ceph 存储类支持快照备份,则需添加以下配置(新增条件应放在最前以获得更高优先级):

如何判断 PVC 是否支持快照?

WARNING

如果使用 pvc 快照备份,恢复时不能更改存储类,请根据实际情况调整存储类。

apiVersion: v1
kind: ConfigMap
metadata:
  name: ${BACKUP_POLICY_NAME}-volume-policy
  namespace: cpaas-system
data:
  resourcepolicies: |
    version: v1
    volumePolicies:
# 如果存储类支持快照,可以使用快照备份 pvc 以加快备份速度。
# 在卷策略 ConfigMap 中添加以下配置。
      - conditions:
          storageClass:
            - ceph
        action:
          type: snapshot
# 快照配置结束
      - conditions:
          csi: {}
        action:
          type: fs-backup
      - conditions:
          csi: {}
        action:
          type: fs-backup
WARNING

如果 velero 未启用 CSI 快照功能,备份时会出现如下错误:

Skip action velero.io/csi-pvc-backupper for resource persistentvolumeclaims:xxx, because the CSI feature is not enabled.

解决方法是给 velero 部署添加 --features=EnableCSI 参数。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: velero
spec:
  template:
    spec:
      containers:
      - args:
        - server
        - --uploader-type=restic
+        - --features=EnableCSI
        - --namespace=cpaas-system
        command:
        - /velero
        name: velero

执行备份

运行以下命令创建备份计划并触发备份任务:

export BACKUP_POLICY_NAME=${BACKUP_POLICY_NAME:-nexus-backup}
export VELERO_BACKUP_REPO_NAME=${VELERO_BACKUP_REPO_NAME:-nexus-backup-repo}

kubectl apply -f - <<EOF
apiVersion: velero.io/v1
kind: Schedule
metadata:
  name: ${BACKUP_POLICY_NAME}
  namespace: cpaas-system
spec:
  schedule: '@every 876000h'
  template:
    defaultVolumesToFsBackup: true
    hooks: {}
    includedNamespaces:
    - ${NEXUS_NAMESPACE}
    includedResources:
    - '*'
    resourcePolicy:
      kind: configmap
      name: ${BACKUP_POLICY_NAME}-volume-policy
    storageLocation: ${VELERO_BACKUP_REPO_NAME}
    ttl: 720h0m0s
EOF

kubectl create -f - <<EOF
apiVersion: velero.io/v1
kind: Backup
metadata:
  labels:
    velero.io/schedule-name: ${BACKUP_POLICY_NAME}
    velero.io/storage-location: ${VELERO_BACKUP_REPO_NAME}
  generateName: ${BACKUP_POLICY_NAME}-
  namespace: cpaas-system
spec:
  csiSnapshotTimeout: 10m0s
  defaultVolumesToFsBackup: true
  includedNamespaces:
    - ${NEXUS_NAMESPACE}
  includedResources:
    - "*"
  itemOperationTimeout: 4h0m0s
  resourcePolicy:
    kind: configmap
    name: ${BACKUP_POLICY_NAME}-volume-policy
  snapshotMoveData: false
  storageLocation: ${VELERO_BACKUP_REPO_NAME}
  ttl: 720h0m0s
EOF

# 输出示例:
# schedule.velero.io/nexus-backup created
# backup.velero.io/nexus-backup-r6hht created

查看备份日志:

kubectl logs -f -n cpaas-system -l app.kubernetes.io/instance=velero --max-log-requests 100 | grep nexus-backup

# 输出示例:
# time="2025-11-03T12:08:00Z" level=info msg="PodVolumeBackup completed" backup=cpaas-system/nexus-backup-n4lxm controller=podvolumebackup logSource="pkg/controller/pod_volume_backup_controller.go:244" pvb=cpaas-system/nexus-backup-n4lxm-5hs4m

检查任务进度,状态为 Completed 表示备份成功。

kubectl get backup -n cpaas-system -o jsonpath="{range .items[*]}{.metadata.name}{'\t'}{.status.phase}{'\t'}{.status.startTimestamp}{'\n'}{end}" | grep ${BACKUP_POLICY_NAME}

# 输出示例:
# nexus-backup-n4lxm	Completed	2025-11-03T12:07:53Z

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

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

恢复

准备工作

恢复准备包括四步:

  1. 选择要恢复的备份
  2. 确定恢复目标命名空间
  3. 从集群卸载 Nexus operator

选择要恢复的备份

运行以下命令查看所有成功的备份记录,根据开始时间选择需要恢复的备份。

kubectl get backup -n cpaas-system -o jsonpath="{range .items[*]}{.metadata.name}{'\t'}{.status.phase}{'\t'}{.status.startTimestamp}{'\n'}{end}" | grep ${BACKUP_POLICY_NAME} | grep Completed

# 输出示例:
# nexus-backup-n4lxm	Completed	2025-11-03T12:07:53Z

设置 BACKUP_NAME 环境变量:

export BACKUP_NAME=<选中的备份记录名称>

确定目标命名空间

建议恢复到新命名空间。设置以下环境变量:

export NEW_NEXUS_NAMESPACE=<新命名空间名称>
kubectl create namespace ${NEW_NEXUS_NAMESPACE}

从集群卸载 Nexus operator

运行以下命令卸载 Nexus operator:

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

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

恢复过程中,Velero 需要启动备份 Pod 来恢复 PVC 数据,耗时较长。如果不卸载 Operator,可能出现以下问题:

  1. Operator 可能基于 Nexus CR 重新创建工作负载,导致恢复的 Pod 被重启或重新创建,最终导致恢复中断或失败。
  2. 部分恢复的资源可能与已有资源冲突,如 Ingress。
卸载 Nexus operator 的影响

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

卸载 Operator 不会导致现有实例异常。

恢复操作

恢复操作包括五步:

  1. 创建恢复配置文件
  2. 创建恢复任务
  3. 清理资源
  4. 修改 Nexus CR 资源
  5. 从集群部署 Nexus operator

创建恢复配置文件

请仔细阅读 YAML 注释并根据需要修改(如更改存储类)后再创建。

# 如果需要更改存储类,设置以下环境变量
OLD_STORAGE_CLASS_NAME='' # 原存储类名称
NEW_STORAGE_CLASS_NAME='' # 新存储类名称

if [ -n "${OLD_STORAGE_CLASS_NAME}" ] && [ -n "${NEW_STORAGE_CLASS_NAME}" ] && [ ${NEW_STORAGE_CLASS_NAME} != ${OLD_STORAGE_CLASS_NAME} ]; then
kubectl apply -f - <<EOF
apiVersion: v1
data:
  resourcemodifier: |
    version: v1
    resourceModifierRules:
      - conditions:
          groupResource: persistentvolumeclaims
          resourceNameRegex: .*
          namespaces:
            - "*"
        patches: &a1
          - operation: test
            path: /spec/storageClassName
            value: ${OLD_STORAGE_CLASS_NAME}
          - operation: replace
            path: /spec/storageClassName
            value: ${NEW_STORAGE_CLASS_NAME}
      - conditions:
          groupResource: persistentvolume
          resourceNameRegex: .*
          namespaces:
            - "*"
        patches: *a1
kind: ConfigMap
metadata:
  labels:
    component: velero
  name: nexus-restore-modifier
  namespace: cpaas-system
EOF

else

kubectl apply -f - <<EOF
apiVersion: v1
data:
  resourcemodifier: |
    version: v1
    resourceModifierRules:
      - conditions:
          groupResource: persistentvolumeclaims
          resourceNameRegex: .*
          namespaces:
            - "*"
        patches:
          - operation: add
            path: "/metadata/annotations/meta.helm.sh~1release-namespace"
            value: ${NEW_NEXUS_NAMESPACE}
kind: ConfigMap
metadata:
  labels:
    component: velero
  name: nexus-restore-modifier
  namespace: cpaas-system
EOF
fi

# 输出示例:
# configmap/nexus-restore-modifier created

创建恢复任务

运行以下命令创建恢复任务:

kubectl create -f - <<EOF
apiVersion: velero.io/v1
kind: Restore
metadata:
  generateName: ${NEXUS_NAME}-restore-
  namespace: cpaas-system
spec:
  backupName: ${BACKUP_NAME}
  hooks: {}
  includedNamespaces:
    - ${NEXUS_NAMESPACE}
  includedResources:
    - persistentvolumeclaims
    - persistentvolumes
    - configmaps
    - secrets
    - pods
    - nexuses.operator.alaudadevops.io
    - volumesnapshots
    - volumesnapshotcontents
  itemOperationTimeout: 10h0m0s
  namespaceMapping:
    ${NEXUS_NAMESPACE}: ${NEW_NEXUS_NAMESPACE}
  resourceModifier:
    kind: ConfigMap
    name: nexus-restore-modifier
EOF

# 输出示例:
# restore.velero.io/<nexus-name>-restore-m2n8f created

查看恢复日志:

kubectl logs -f -n cpaas-system -l app.kubernetes.io/instance=velero --max-log-requests 100 | grep ${NEXUS_NAME}-restore

# 输出示例:
# time="2025-11-03T12:27:38Z" level=info msg="Async fs restore data path completed" PVR=<nexus-name>-restore-m2n8f-n2tx2 controller=PodVolumeRestore logSource="pkg/controller/pod_volume_restore_controller.go:275" pvr=<nexus-name>-restore-m2n8f-n2tx2
# time="2025-11-03T12:27:38Z" level=info msg="Restore completed" controller=PodVolumeRestore logSource="pkg/controller/pod_volume_restore_controller.go:327" pvr=<nexus-name>-restore-m2n8f-n2tx2

检查任务进度,状态为 Completed 表示恢复成功。

kubectl get restore -n cpaas-system -o jsonpath="{range .items[*]}{.metadata.name}{'\t'}{.status.phase}{'\t'}{.status.startTimestamp}{'\n'}{end}" | grep ${NEXUS_NAME}-restore

# 输出示例:
# <nexus-name>-restore-m2n8f    InProgress      2025-11-03T12:27:38Z

清理资源

确保恢复操作完成后再执行!

运行以下命令清理资源:

kubectl delete configmap,secrets,sa -n ${NEW_NEXUS_NAMESPACE} -l release=${NEXUS_NAME}

kubectl get pod -n ${NEW_NEXUS_NAMESPACE} | grep ${NEXUS_NAME} | awk '{print $1}' | xargs kubectl delete pod -n ${NEW_NEXUS_NAMESPACE}

kubectl get secret -n ${NEW_NEXUS_NAMESPACE} | grep sh.helm.release.v1.${NEXUS_NAME} | awk '{print $1}' | xargs kubectl delete secret,configmap -n ${NEW_NEXUS_NAMESPACE}
kubectl delete configmap ${NEXUS_NAME}-nxrm-ha-logback-tasklogfile-override -n ${NEW_NEXUS_NAMESPACE}

修改 Nexus CR 资源

kubectl edit nexus ${NEXUS_NAME} -n ${NEW_NEXUS_NAMESPACE}

新实例 CR 资源可能需要做以下修改,请根据实际情况调整:

  1. 域名
    • 适用场景:原实例使用域名部署
    • 原因:旧实例和新实例 Ingress 资源中相同域名会冲突,导致新实例 Ingress 创建失败
    • 建议
      • (推荐)将原实例域名改为临时域名,新实例保持不变
      • 或保持原实例不变,新实例改为新域名
    • 修改方法:参见配置实例网络访问
  2. NodePort
    • 适用场景:原实例使用 NodePort 部署
    • 原因:旧实例和新实例 Service 资源中相同 NodePort 会冲突,导致新实例 Service 创建失败
    • 建议
      • (推荐)将原实例 NodePort 改为临时端口,新实例保持不变
      • 或保持原实例不变,新实例改为新端口
    • 修改方法:参见配置实例网络访问
  3. 存储类
    • 适用场景:新旧实例存储类不同(如原使用 NFS,新使用 Ceph)
    • 原因:不修改时,Operator 仍会使用旧存储类创建 PVC,与恢复的 PVC 冲突
    • 建议:改为正确的存储类
    • 修改方法:参见配置实例存储

从集群部署 Nexus operator

进入 Administrator 视图,Marketplace -> OperatorHub 页面,重新部署 Alauda Build of Nexus Operator。

Operator 部署后,将根据 Nexus CR 部署新实例,可在实例详情页查看进度。

实例状态恢复正常后,登录 Nexus 检查数据是否恢复成功,检查项包括但不限于:

  • 用户
  • 仓库
  • 任务

问答

如何判断 PVC 是否支持快照?

判断 PVC 是否支持快照,需要查看其底层 StorageClass 是否支持快照功能。若集群中存在 VolumeSnapshotClass,其 driver 与 StorageClass 的 provisioner 匹配,则该 StorageClass(及其 PVC)支持快照功能。

示例配置:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ceph
provisioner: rook-ceph.cephfs.csi.ceph.com
parameters:
  clusterID: rook-ceph
  csi.storage.k8s.io/controller-expand-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  fsName: cephfs
  pool: cephfs-data0
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-cephfs-snapshotclass
deletionPolicy: Delete
driver: rook-ceph.cephfs.csi.ceph.com
parameters:
  clusterID: rook-ceph
  csi.storage.k8s.io/snapshotter-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/snapshotter-secret-namespace: rook-ceph

关键点:

  • StorageClass 的 provisioner 必须与 VolumeSnapshotClass 的 driver 完全匹配
  • 两个资源均需存在于集群中
  • CSI 驱动必须支持快照操作