使用 Velero 备份与恢复 HARBOR

本指南演示如何使用 Velero,一款开源的云原生灾难恢复工具,对 HARBOR 进行备份与恢复操作。

目录

适用范围

本方案适用于运行 2.12 版本及以上 的 HARBOR 实例。

DANGER

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

术语说明

术语说明
源实例备份前的 HARBOR 实例
目标实例恢复后的 HARBOR 实例
源命名空间源实例所在的命名空间
目标命名空间目标实例所在的命名空间
HARBOR CR 资源描述 HARBOR 部署配置的自定义资源,由 Operator 用于部署 HARBOR 实例

前提条件

  1. 部署 MinIO 对象存储:备份与恢复方案依赖对象存储保存备份数据,因此需提前部署 MinIO 实例。ACP 提供了
  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 官方文档

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

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 配置正确。

备份

准备工作

备份准备包含两步:

  1. 创建 Bucket
  2. 配置 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

执行备份

手动备份包含四步:

  1. 将 Harbor 实例设置为只读模式
  2. 创建备份 Pod
  3. 执行备份
  4. 备份成功后,关闭 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,取消勾选 管理 -> 配置 -> 系统设置 -> 仓库只读。

恢复

准备工作

恢复准备包含四步:

  1. 选择要恢复的备份
  2. 确定恢复目标命名空间
  3. 卸载 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,可能出现以下问题:

  1. Operator 会根据 HARBOR CR 重新创建工作负载,导致恢复的 Pod 被重启或重新创建,最终造成恢复中断或失败。
  2. 部分恢复的资源可能与现有资源冲突,如 Ingress。
卸载 HARBOR CE Operator 的影响

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

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

恢复操作

恢复操作包含五步:

  1. 创建恢复配置文件
  2. 创建恢复任务
  3. 清理资源
  4. 修改 HARBOR CR 资源
  5. 部署 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 资源可能需要做以下修改,请根据实际情况调整:

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

部署 HARBOR CE Operator

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

Operator 部署完成后,会根据 HARBOR CR 部署新实例,可在实例详情页查看进度。

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

  • 用户组
  • 仓库
  • 镜像

同时关闭恢复后 Harbor 实例的只读模式(登录 Harbor,取消勾选 管理 -> 配置 -> 系统设置 -> 仓库只读)。