如何使用 Velero 备份和恢复 nexus
适用范围
本方案适用于运行版本为 3.76 及以上的 Nexus 实例。
术语说明
前置条件
-
部署 MinIO 对象存储:本备份恢复方案依赖对象存储保存备份数据,因此需提前部署 MinIO 实例。ACP 提供了 快速创建 MinIO 实例。
-
部署 Velero:Velero 是备份恢复工具。ACP 提供 Alauda Container Platform Data Backup for Velero,可在 Administrator 视图的 Marketplace -> Cluster Plugins 中搜索 Velero 进行部署。
-
安装 mc 命令行工具:mc 是 MinIO 的命令行管理工具。安装说明请参见 MinIO 官方文档。
-
安装 kubectl 命令行工具:kubectl 是 Kubernetes 的命令行管理工具。安装说明请参见 Kubernetes 官方文档。
-
从集群中卸载 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,可能出现以下问题:
- Operator 可能基于 Nexus CR 重新创建工作负载,导致恢复的 Pod 被重启或重新创建,最终导致恢复中断或失败。
- 部分恢复的资源可能与已有资源冲突,如 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 配置正确。
备份
准备工作
备份准备包括两步:
- 创建 Bucket
- 配置 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
执行备份
手动备份包括四步:
- 删除 nexus 实例服务以防止数据变更
- 创建备份卷策略
- 执行备份
- 备份成功后恢复 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。
恢复
准备工作
恢复准备包括四步:
- 选择要恢复的备份
- 确定恢复目标命名空间
- 从集群卸载 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,可能出现以下问题:
- Operator 可能基于 Nexus CR 重新创建工作负载,导致恢复的 Pod 被重启或重新创建,最终导致恢复中断或失败。
- 部分恢复的资源可能与已有资源冲突,如 Ingress。
卸载 Nexus operator 的影响
卸载 Operator 后,对 Nexus CR 的修改将不会生效,如调整资源或存储大小。
卸载 Operator 不会导致现有实例异常。
恢复操作
恢复操作包括五步:
- 创建恢复配置文件
- 创建恢复任务
- 清理资源
- 修改 Nexus CR 资源
- 从集群部署 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 资源可能需要做以下修改,请根据实际情况调整:
- 域名:
- 适用场景:原实例使用域名部署
- 原因:旧实例和新实例 Ingress 资源中相同域名会冲突,导致新实例 Ingress 创建失败
- 建议:
- (推荐)将原实例域名改为临时域名,新实例保持不变
- 或保持原实例不变,新实例改为新域名
- 修改方法:参见配置实例网络访问
- NodePort:
- 适用场景:原实例使用 NodePort 部署
- 原因:旧实例和新实例 Service 资源中相同 NodePort 会冲突,导致新实例 Service 创建失败
- 建议:
- (推荐)将原实例 NodePort 改为临时端口,新实例保持不变
- 或保持原实例不变,新实例改为新端口
- 修改方法:参见配置实例网络访问
- 存储类:
- 适用场景:新旧实例存储类不同(如原使用 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 驱动必须支持快照操作