使用 Velero 备份与恢复 HARBOR
本指南演示如何使用 Velero,一款开源的云原生灾难恢复工具,对 HARBOR 进行备份与恢复操作。
目录
适用范围
本方案适用于运行 2.12 版本及以上 的 HARBOR 实例。
DANGER
本方案不支持使用 HostPath 存储部署的 HARBOR 实例。
术语说明
术语 | 说明 |
---|
源实例 | 备份前的 HARBOR 实例 |
目标实例 | 恢复后的 HARBOR 实例 |
源命名空间 | 源实例所在的命名空间 |
目标命名空间 | 目标实例所在的命名空间 |
HARBOR CR 资源 | 描述 HARBOR 部署配置的自定义资源,由 Operator 用于部署 HARBOR 实例 |
前提条件
- 部署 MinIO 对象存储:备份与恢复方案依赖对象存储保存备份数据,因此需提前部署 MinIO 实例。ACP 提供了 。
- 部署 Velero:Velero 是备份与恢复工具。ACP 提供了
Alauda Container Platform Data Backup for Velero
,可在 Administrator
视图的 Marketplace
-> Cluster Plugins
中搜索 Velero
进行部署。
- 安装 mc 命令行工具:mc 是 MinIO 的命令行管理工具。安装说明请参见 MinIO 官方文档。
- 安装 kubectl 命令行工具:kubectl 是 Kubernetes 的命令行管理工具。安装说明请参见 Kubernetes 官方文档。
为方便后续操作,请先设置以下环境变量:
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 备份仓库名称> # 示例:harbor-backup-repo
export HARBOR_NAMESPACE=<待备份 HARBOR 实例所在命名空间>
export HARBOR_NAME=<待备份 HARBOR 实例名称>
运行以下命令配置 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 备份仓库:
harbor_backup_repo_credentials_secret_name="harbor-backup-repo-credentials"
minio_harbor_backup_bucket="${VELERO_BACKUP_BUCKET:-backup}"
minio_harbor_backup_bucket_directory="harbor"
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: ${harbor_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: ${harbor_backup_repo_credentials_secret_name}
objectStorage:
bucket: ${minio_harbor_backup_bucket}
prefix: ${minio_harbor_backup_bucket_directory}
provider: aws
EOF
# 输出
# secret/harbor-backup-repo-credentials created
# backupstoragelocationrepo.ait.velero.io/harbor-backup-repo created
执行备份
手动备份包含四步:
- 将 Harbor 实例设置为只读模式
- 创建备份 Pod
- 执行备份
- 备份成功后,关闭 Harbor 只读模式
将 Harbor 实例设置为只读模式
登录 Harbor,进入 管理 -> 配置 -> 系统设置 -> 仓库只读,设置为只读模式。
修改后,Harbor 顶部会显示提示信息:“Harbor 已设置为只读模式,删除仓库、制品、标签及推送镜像操作在只读模式下将被禁用。”
创建备份 Pod
此步骤创建一个挂载 HARBOR PVC 的 Pod,供 Velero 完成 PVC 数据备份。
image=''
if [ -z "${IMAGE}" ]; then
image=$(kubectl get deployment -n ${HARBOR_NAMESPACE} -l release=${HARBOR_NAME} -o jsonpath='{range .items[0].spec.template.spec.containers[]}{.image}{"\n"}{end}' | head -n1)
fi
PVC_NAMES=($(kubectl get pvc -n ${HARBOR_NAMESPACE} -l release=${HARBOR_NAME} -o jsonpath='{range .items[*]}{.metadata.name}{" "}{end}'))
VOLUME_MOUNTS=""
VOLUMES=""
INDEX=0
for pvc in ${PVC_NAMES[@]}; do
VOLUME_MOUNTS="${VOLUME_MOUNTS}
- name: data-${INDEX}
mountPath: /mnt/data-${INDEX}"
VOLUMES="${VOLUMES}
- name: data-${INDEX}
persistentVolumeClaim:
claimName: ${pvc}"
INDEX=$((INDEX+1))
done
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: ${HARBOR_NAME}-backup-pod
namespace: ${HARBOR_NAMESPACE}
labels:
release: ${HARBOR_NAME}
spec:
containers:
- name: backup
image: ${image}
command: ["/bin/sh", "-c", "sleep 86400"]
resources:
limits:
cpu: 1
memory: 1Gi
volumeMounts:${VOLUME_MOUNTS}
volumes:${VOLUMES}
restartPolicy: Never
EOF
kubectl wait --for=condition=ready pod -n ${HARBOR_NAMESPACE} ${HARBOR_NAME}-backup-pod
# 输出
# pod/xxxx-harbor-backup-pod created
# pod/xxxx-harbor-backup-pod condition met
执行备份
运行以下命令创建备份计划并触发备份任务:
export BACKUP_POLICY_NAME=${BACKUP_POLICY_NAME:-harbor-backup}
export VELERO_BACKUP_REPO_NAME=${VELERO_BACKUP_REPO_NAME:-backup}
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:
- ${HARBOR_NAMESPACE}
includedResources:
- '*'
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:
- ${HARBOR_NAMESPACE}
includedResources:
- "*"
itemOperationTimeout: 4h0m0s
snapshotMoveData: false
storageLocation: ${VELERO_BACKUP_REPO_NAME}
ttl: 720h0m0s
EOF
# 输出
# schedule.velero.io/harbor-backup created
# backup.velero.io/harbor-backup-r6hht created
查看备份日志:
kubectl logs -f -n cpaas-system -l app.kubernetes.io/instance=velero --max-log-requests 100 | grep harbor-backup
# 输出
# time="2025-07-01T07:29:54Z" level=info msg="PodVolumeBackup completed" controller=PodVolumeBackup logSource="pkg/controller/pod_volume_backup_controller.go:244" pvb=harbor-backup-xxx-q7prc
检查任务进度,若状态为 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}
# 输出
# Completed
备份成功后,关闭 Harbor 只读模式
登录 Harbor,取消勾选 管理 -> 配置 -> 系统设置 -> 仓库只读。
恢复
准备工作
恢复准备包含四步:
- 选择要恢复的备份
- 确定恢复目标命名空间
- 卸载 HARBOR CE 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
# 输出:
# harbor-backup-xxx Completed 2025-07-01T07:29:19Z
设置 BACKUP_NAME 环境变量:
export BACKUP_NAME=<选择的备份记录名称>
确定目标命名空间
建议恢复到新命名空间。设置以下环境变量:
export NEW_HARBOR_NAMESPACE=<新命名空间名称>
kubectl create namespace ${NEW_HARBOR_NAMESPACE}
卸载 HARBOR CE Operator
运行以下命令卸载 HARBOR CE Operator:
kubectl get subscription --all-namespaces | grep harbor-ce-operator | awk '{print "kubectl delete subscription "$2" -n "$1}' | sh
# 输出:
# subscription.operators.coreos.com "harbor-ce-operator" deleted
为什么要卸载 HARBOR Operator?
恢复过程中,Velero 需要启动备份 Pod 来恢复 PVC 数据,耗时较长。如果不卸载 Operator,可能出现以下问题:
- Operator 会根据 HARBOR CR 重新创建工作负载,导致恢复的 Pod 被重启或重新创建,最终造成恢复中断或失败。
- 部分恢复的资源可能与现有资源冲突,如 Ingress。
卸载 HARBOR CE Operator 的影响
卸载 Operator 后,对 HARBOR CR 的修改将不生效,例如调整资源或存储大小。
卸载 Operator 不会导致现有实例异常。
恢复操作
恢复操作包含五步:
- 创建恢复配置文件
- 创建恢复任务
- 清理资源
- 修改 HARBOR CR 资源
- 部署 HARBOR CE Operator
创建恢复配置文件
请仔细阅读 YAML 注释并根据需要修改(如更改存储类)后再创建。
# 如需更改存储类,请设置以下环境变量
OLD_STORAGECLASS_NAME='' # 原存储类名称
NEW_STORAGECLASS_NAME='' # 新存储类名称
if [ -n "${OLD_STORAGECLASS_NAME}" ] && [ -n "${NEW_STORAGECLASS_NAME}" ] && [ ${NEW_STORAGECLASS_NAME} != ${OLD_STORAGECLASS_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_STORAGECLASS_NAME}
- operation: replace
path: /spec/storageClassName
value: ${NEW_STORAGECLASS_NAME}
- conditions:
groupResource: persistentvolume
resourceNameRegex: .*
namespaces:
- "*"
patches: *a1
- conditions:
groupResource: persistentvolumeclaims
resourceNameRegex: .*
namespaces:
- "*"
patches:
- operation: test
path: "/metadata/annotations/meta.helm.sh~1release-namespace"
value: ${HARBOR_NAMESPACE}
- operation: replace
path: "/metadata/annotations/meta.helm.sh~1release-namespace"
value: ${NEW_HARBOR_NAMESPACE}
kind: ConfigMap
metadata:
labels:
component: velero
name: harbor-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: test
path: "/metadata/annotations/meta.helm.sh~1release-namespace"
value: ${HARBOR_NAMESPACE}
- operation: replace
path: "/metadata/annotations/meta.helm.sh~1release-namespace"
value: ${NEW_HARBOR_NAMESPACE}
kind: ConfigMap
metadata:
labels:
component: velero
name: harbor-restore-modifier
namespace: cpaas-system
EOF
fi
创建恢复任务
运行以下命令创建恢复任务:
kubectl create -f - <<EOF
apiVersion: velero.io/v1
kind: Restore
metadata:
generateName: ${HARBOR_NAME}-restore-
namespace: cpaas-system
spec:
backupName: ${BACKUP_NAME}
hooks: {}
includedNamespaces:
- ${HARBOR_NAMESPACE}
includedResources:
- persistentvolumeclaims
- persistentvolumes
- secrets
- pods
- harbors.operator.alaudadevops.io
itemOperationTimeout: 10h0m0s
namespaceMapping:
${HARBOR_NAMESPACE}: ${NEW_HARBOR_NAMESPACE}
resourceModifier:
kind: configmap
name: harbor-restore-modifier
EOF
查看恢复日志:
kubectl logs -f -n cpaas-system -l app.kubernetes.io/instance=velero --max-log-requests 100 | grep harbor-restore
# 输出
# time="2025-07-01T08:01:41Z" level=info msg="Async fs restore data path completed" PVR=xxxx-HARBOR-restore-mv6l5-sc4pk controller=PodVolumeRestore logSource="pkg/controller/pod_volume_restore_controller.go:275" pvr=xxxx-HARBOR-restore-mv6l5-sc4pk
# time="2025-07-01T08:01:41Z" level=info msg="Restore completed" controller=PodVolumeRestore logSource="pkg/controller/pod_volume_restore_controller.go:327" pvr=xxxx-HARBOR-restore-mv6l5-sc4pk
检查任务进度,若状态为 Completed
,则恢复成功。
kubectl get restore -n cpaas-system -o jsonpath="{range .items[*]}{.metadata.name}{'\t'}{.status.phase}{'\t'}{.status.startTimestamp}{'\n'}{end}" | grep ${HARBOR_NAME}-restore
# 输出
# xxx-harbor-restore-xxx InProgress 2025-07-01T10:18:17Z
清理资源
确保恢复操作完成后再执行!
运行以下命令清理资源:
kubectl delete configmap,secrets,sa -n ${NEW_HARBOR_NAMESPACE} -l release=${HARBOR_NAME}
kubectl get pod -n ${NEW_HARBOR_NAMESPACE} | grep ${HARBOR_NAME} | awk '{print $1}' | xargs kubectl delete pod -n ${NEW_HARBOR_NAMESPACE}
kubectl get secret -n ${NEW_HARBOR_NAMESPACE} | grep sh.helm.release.v1.${HARBOR_NAME} | awk '{print $1}' | xargs kubectl delete secret -n ${NEW_HARBOR_NAMESPACE}
修改 HARBOR CR 资源
kubectl edit harbor ${HARBOR_NAME} -n ${NEW_HARBOR_NAMESPACE}
新实例 CR 资源可能需要做以下修改,请根据实际情况调整:
- 域名:
- 适用场景:原实例使用域名部署
- 原因:旧实例和新实例 Ingress 资源中相同域名会冲突,导致新实例 Ingress 创建失败
- 建议:
- (推荐)将原实例域名改为临时域名,保持新实例不变
- 或保持原实例不变,将新实例改为新域名
- 修改方式:参见 配置实例网络访问
- NodePort:
- 适用场景:原实例使用 NodePort 部署
- 原因:旧实例和新实例 Service 资源中相同 NodePort 会冲突,导致新实例 Service 创建失败
- 建议:
- (推荐)将原实例 NodePort 改为临时端口,保持新实例不变
- 或保持原实例不变,将新实例改为新端口
- 修改方式:参见 配置实例网络访问
- 存储类:
- 适用场景:新旧实例存储类不同(如原使用 NFS,新使用 Ceph)
- 原因:不修改时,Operator 仍使用旧存储类创建 PVC,与恢复的 PVC 冲突
- 建议:修改为正确的存储类
- 修改方式:参见 配置实例存储
- PostgreSQL 连接信息:
- 适用场景:新旧实例使用不同的 PostgreSQL 实例或数据库
- 原因:不修改时,新实例仍连接旧数据库
- 建议:修改为正确的连接信息
- 修改方式:参见 配置 PostgreSQL 凭据
部署 HARBOR CE Operator
进入 Administrator
视图,Marketplace -> OperatorHub
页面,重新部署 Alauda Build of HARBOR
Operator。
Operator 部署完成后,会根据 HARBOR CR 部署新实例,可在实例详情页查看进度。
实例状态恢复正常后,登录 HARBOR 检查数据是否恢复成功,检查项包括但不限于:
同时关闭恢复后 Harbor 实例的只读模式(登录 Harbor,取消勾选 管理 -> 配置 -> 系统设置 -> 仓库只读)。