使用 Velero 备份和恢复 GitLab

本指南演示如何使用 Velero,一款开源的云原生灾难恢复工具,实现 GitLab 的备份和恢复操作。

目录

适用范围

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

TIP

如果您的 GitLab 实例已配置对象存储,建议优先使用官方备份和恢复方案

如何判断是否启用了对象存储?

GitLab 数据主要包含三个部分:PostgreSQL 数据库、Git 仓库和上传的制品(包括用户头像、评论附件等)。本方案仅支持 Git 仓库和上传制品的备份。PostgreSQL 数据库需根据您的数据库服务商备份策略,单独进行备份。

DANGER

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

术语说明

术语定义
Source Instance备份前的 GitLab 实例
Target Instance恢复后的 GitLab 实例
Source Namespace源实例所在的命名空间
Target Namespace目标实例所在的命名空间
GitLab CR Resource描述 GitLab 实例部署配置的资源,由 Operator 用于部署 GitLab 实例

前置准备

  1. 部署 MinIO 对象存储:当前备份恢复方案依赖对象存储保存备份数据,需预先部署 MinIO 实例。ACP 提供了 MinIO Object Storage,可通过 进行部署。
  2. 部署 Velero:Velero 是备份和恢复工具。ACP 提供了 Alauda Container Platform Data Backup for Velero,您可以在 Administrator 视图的 Marketplace -> Cluster Plugins 页面搜索 Velero 并部署。
  3. 安装 mc 命令行工具:mc 是 MinIO 的命令行工具,用于管理 MinIO 实例。安装说明请参考 MinIO 官方文档
  4. 安装 kubectl 命令行工具:kubectl 是 Kubernetes 的命令行工具,用于管理 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 备份仓库名称> # 例如:gitlab-backup-repo

export GITLAB_NAMESPACE=<待备份 GitLab 实例所在命名空间>
export GITLAB_NAME=<待备份 GitLab 实例名称>

执行以下命令配置 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 备份仓库,用于存储备份数据:

gitlab_backup_repo_credentials_secret_name="gitlab-backup-repo-credentials"
minio_gitlab_backup_bucket="${VELERO_BACKUP_BUCKET:-backup}"
minio_gitlab_backup_bucket_directory="gitlab"

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: ${gitlab_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: ${gitlab_backup_repo_credentials_secret_name}
  objectStorage:
    bucket: ${minio_gitlab_backup_bucket}
    prefix: ${minio_gitlab_backup_bucket_directory}
  provider: aws
EOF

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

执行备份

手动备份包含六个步骤:

  1. 给资源打备份标签
  2. 停止 GitLab 服务
  3. 创建备份 Pod
  4. 创建备份计划
  5. 执行备份
  6. 恢复 GitLab 服务

给资源打备份标签

执行以下脚本:

if [ -z "${GITLAB_NAMESPACE}" ] || [ -z "${GITLAB_NAME}" ]; then
    cat <<EOF
    请设置正确的命名空间和实例名称:
    export GITLAB_NAME=<原 GitLab 实例名称>
    export GITLAB_NAMESPACE=<原命名空间名称>
EOF
    return
fi

kubectl label gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} release=${GITLAB_NAME} --overwrite

secrets_to_label=()

root_password_secret_name=$(kubectl get gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} -o jsonpath='{.spec.helmValues.global.initialRootPassword.secret}')
secrets_to_label+=(${root_password_secret_name})

rails_secret_name=$(kubectl get gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} -o jsonpath='{.spec.helmValues.global.railsSecrets.secret}')
if [ -n "${rails_secret_name}" ]; then
    echo "发现 rails secret: ${rails_secret_name}"
    secrets_to_label+=(${rails_secret_name})
fi

pg_connect_secret_name=$(kubectl get gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} -o jsonpath='{.spec.helmValues.global.psql.password.secret}')
secrets_to_label+=(${pg_connect_secret_name})

ingress_secret_name=$(kubectl get gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} -o jsonpath='{.spec.helmValues.global.ingress.tls.secretName}')
if [ -n "${ingress_secret_name}" ]; then
    echo "发现 ingress secret: ${ingress_secret_name}"
    secrets_to_label+=(${ingress_secret_name})
fi

redis_connect_secret_name=$(kubectl get gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} -o jsonpath='{.spec.helmValues.global.redis.auth.secret}')
if [ -n "${redis_connect_secret_name}" ]; then
    echo "发现 redis auth secret: ${redis_connect_secret_name}"
    secrets_to_label+=(${redis_connect_secret_name})
fi

redis_sentinel_connect_secret_name=$(kubectl get gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} -o jsonpath='{.spec.helmValues.global.redis.sentinelAuth.secret}')
if [ -n "${redis_sentinel_connect_secret_name}" ]; then
    echo "发现 redis sentinel connect secret: ${redis_sentinel_connect_secret_name}"
    secrets_to_label+=(${redis_sentinel_connect_secret_name})
fi

object_store_secret_name=$(kubectl get gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} -o jsonpath='{.spec.helmValues.global.appConfig.object_store.connection.secret}')
if [ -n "${object_store_secret_name}" ]; then
    echo "发现对象存储连接 secret: ${object_store_secret_name}"
    kubectl label secret ${object_store_secret_name} -n ${GITLAB_NAMESPACE} release=${GITLAB_NAME}
fi

for secret_name in "${secrets_to_label[@]}"; do
  echo "为 secret 添加标签: ${secret_name}"
  kubectl label secret ${secret_name} -n ${GITLAB_NAMESPACE} release=${GITLAB_NAME} --overwrite
done

kubectl get pvc -n ${GITLAB_NAMESPACE} --no-headers | grep ${GITLAB_NAME} | awk '{print $1}' | xargs -I {} kubectl label pvc {} release=${GITLAB_NAME} -n ${GITLAB_NAMESPACE}

kubectl get secret -n ${GITLAB_NAMESPACE} --no-headers | grep ${GITLAB_NAME} | awk '{print $1}' | xargs -I {} kubectl label secret {} release=${GITLAB_NAME} -n ${GITLAB_NAMESPACE}

# 输出示例:
# secret/sh.helm.release.v1.xxx-gitlab.v9 未打标签

如果需要备份其他资源,请参考给额外资源打备份标签部分。

停止 GitLab 服务

备份期间必须停止所有 GitLab 组件:

kubectl annotate statefulset -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} skip-sync="true"
kubectl scale statefulset -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} --replicas=0

kubectl annotate deployment -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} skip-sync="true"
kubectl scale deployment -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} --replicas=0

kubectl delete pod -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} --ignore-not-found

# 输出示例:
# statefulset.apps/xxxx-gitlab-gitaly annotated
# deployment.apps/xxxx-gitlab-webservice-default annotated
# deployment.apps/xxxx-gitlab-toolbox scaled
# deployment.apps/xxxx-gitlab-webservice-default scaled

创建备份 Pod

该步骤创建一个挂载 GitLab PVC 的 Pod,供 Velero 完成 PVC 数据备份。

image=''

if [ -z "${IMAGE}" ]; then
  image=$(kubectl get deployment -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME},app=gitlab-shell -o jsonpath='{range .items[0].spec.template.spec.containers[]}{.image}{"\n"}{end}' | head -n1)
fi

PVC_NAMES=($(kubectl get pvc -n ${GITLAB_NAMESPACE} -l release=${GITLAB_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: ${GITLAB_NAME}-backup-pod
  namespace: ${GITLAB_NAMESPACE}
  labels:
    release: ${GITLAB_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 ${GITLAB_NAMESPACE} ${GITLAB_NAME}-backup-pod

# 输出示例:
# pod/xxxx-gitlab-backup-pod created
# pod/xxxx-gitlab-backup-pod condition met

创建备份卷策略

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

export BACKUP_POLICY_NAME=${BACKUP_POLICY_NAME:-gitlab-backup}
export VELERO_BACKUP_REPO_NAME=${VELERO_BACKUP_REPO_NAME:-backup}

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/gitlab-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:
+      - 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:-gitlab-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:
    - ${GITLAB_NAMESPACE}
    includedResources:
    - '*'
    resourcePolicy:
      kind: configmap
      name: ${BACKUP_POLICY_NAME}-volume-policy
    orLabelSelectors:
    - matchLabels:
        release: ${GITLAB_NAME}
    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:
    - ${GITLAB_NAMESPACE}
  includedResources:
    - "*"
  itemOperationTimeout: 4h0m0s
  resourcePolicy:
    kind: configmap
    name: ${BACKUP_POLICY_NAME}-volume-policy
  orLabelSelectors:
    - matchLabels:
        release: ${GITLAB_NAME}
  snapshotMoveData: false
  storageLocation: ${VELERO_BACKUP_REPO_NAME}
  ttl: 720h0m0s
EOF

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

查看备份日志:

kubectl logs -f -n cpaas-system -l app.kubernetes.io/instance=velero | grep gitlab-backup

# 输出示例:
# time="2025-07-01T07:29:54Z" level=info msg="PodVolumeBackup completed" controller=PodVolumeBackup logSource="pkg/controller/pod_volume_backup_controller.go:244" pvb=gitlab-backup-xxx-q7prc

检查任务进度,若状态为 Completed,说明备份成功。

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

# 输出示例:
# Completed

恢复 GitLab 服务

删除备份 Pod 并恢复所有 GitLab 组件。

kubectl delete pod -n ${GITLAB_NAMESPACE} ${GITLAB_NAME}-backup-pod

kubectl annotate statefulset -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} skip-sync-
kubectl scale statefulset -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} --replicas=1

kubectl annotate deployment -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} skip-sync-
kubectl scale deployment -n ${GITLAB_NAMESPACE} -l release=${GITLAB_NAME} --replicas=1

所有组件恢复完成后,访问 GitLab 实例,确保其正常运行。

恢复

准备工作

恢复准备包含四步:

  1. 恢复 PostgreSQL 数据库(可选)
  2. 选择要恢复的备份
  3. 确定恢复目标命名空间
  4. 卸载 Gitlab CE Operator

恢复 PostgreSQL 数据库(可选)

若新实例仍使用相同的 PostgreSQL 实例和数据库,无需恢复 PostgreSQL 数据库。

若新实例使用与旧实例不同的 PostgreSQL 实例或数据库,则需先恢复 PostgreSQL 数据库。请参考您的 PostgreSQL 服务商备份恢复文档。

选择要恢复的备份数据

执行以下命令查看成功备份记录列表,根据开始时间选择所需备份。

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

# 输出示例:
# gitlab-backup-xxx     Completed       2025-07-01T07:29:19Z

设置 BACKUP_NAME 环境变量:

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

确定目标命名空间

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

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

卸载 Gitlab CE Operator

执行以下命令卸载 GitLab CE Operator:

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

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

恢复过程中,Velero 需要启动备份 Pod 来恢复 PVC 中的数据,耗时较长。若恢复时不卸载 operator,可能导致:

  1. operator 根据 GitLab CR 重新创建工作负载资源,导致恢复的 Pod 重启或被重新创建,最终导致恢复中断或失败。
  2. 部分恢复的资源可能冲突,例如 operator 根据恢复的 GitLab CR 创建的 ingress 资源与旧实例的 ingress 资源冲突。
卸载 Gitlab CE Operator 的影响

卸载 operator 后,修改 GitLab CR 资源将不生效,例如调整资源或存储大小。

卸载 operator 不会导致已有实例异常。

恢复操作

恢复操作包含五步:

  1. 创建恢复配置文件
  2. 创建恢复任务
  3. 清理资源
  4. 修改 GitLab CR 资源
  5. 部署 Gitlab CE Operator

创建恢复配置文件

请仔细阅读 YAML 中的注释,根据实际情况(如更改存储类)进行修改,然后创建修改后的 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: ${GITLAB_NAMESPACE}
          - operation: replace
            path: "/metadata/annotations/meta.helm.sh~1release-namespace"
            value: ${NEW_GITLAB_NAMESPACE}
kind: ConfigMap
metadata:
  labels:
    component: velero
  name: gitlab-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: ${GITLAB_NAMESPACE}
          - operation: replace
            path: "/metadata/annotations/meta.helm.sh~1release-namespace"
            value: ${NEW_GITLAB_NAMESPACE}
kind: ConfigMap
metadata:
  labels:
    component: velero
  name: gitlab-restore-modifier
  namespace: cpaas-system
EOF
fi

创建恢复任务

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

kubectl create -f - <<EOF
apiVersion: velero.io/v1
kind: Restore
metadata:
  generateName: ${GITLAB_NAME}-restore-
  namespace: cpaas-system
spec:
  backupName: ${BACKUP_NAME}
  hooks: {}
  includedNamespaces:
    - ${GITLAB_NAMESPACE}
  includedResources:
    - persistentvolumeclaims
    - persistentvolumes
    - secrets
    - pods
    - gitlabofficials.operator.alaudadevops.io
    - volumesnapshots
    - volumesnapshotcontents
  itemOperationTimeout: 10h0m0s
  namespaceMapping:
    ${GITLAB_NAMESPACE}: ${NEW_GITLAB_NAMESPACE}
  resourceModifier:
    kind: configmap
    name: gitlab-restore-modifier
EOF

查看恢复日志:

kubectl logs -f -n cpaas-system -l app.kubernetes.io/instance=velero | grep gitlab-restore

# 输出示例:
# time="2025-07-01T08:01:41Z" level=info msg="Async fs restore data path completed" PVR=xxxx-gitlab-restore-mv6l5-sc4pk controller=PodVolumeRestore logSource="pkg/controller/pod_volume_restore_controller.go:275" pvr=xxxx-gitlab-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-gitlab-restore-mv6l5-sc4pk

检查任务进度,若状态为 Completed,说明恢复成功。

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

# 输出示例:
# xxx-gitlab-restore-xxx    InProgress      2025-07-01T10:18:17Z

清理资源

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

执行以下命令完成资源清理。

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

kubectl get secret -n ${NEW_GITLAB_NAMESPACE} | grep sh.helm.release.v1.${GITLAB_NAME} | awk '{print $1}' | xargs kubectl delete secret -n ${NEW_GITLAB_NAMESPACE}

修改 GitLab CR 资源

kubectl edit gitlabofficial ${GITLAB_NAME} -n ${NEW_GITLAB_NAMESPACE}

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

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

部署 Gitlab CE Operator

进入 Administrator 视图,打开 Marketplace -> OperatorHub 页面,重新部署 Alauda Build of Gitlab operator。

部署 Operator 后,operator 会根据 GitLab CR 部署新实例。您可在实例详情页查看实例部署进度。

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

  • 仓库
  • 用户
  • 合并请求

常见问题解答

给额外资源打备份标签

此步骤为可选操作。如果您希望备份同一命名空间下的其他资源,可为对应资源添加 release 标签,标签值为原 GitLab 实例名称,例如:

metadata:
  labels:
    release: gitlab-g6zd4

如何判断 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 驱动必须支持快照操作