使用 Velero 进行 GitLab 备份和恢复

本指南演示如何使用开源云原生灾难恢复工具 Velero 执行 GitLab 备份和恢复操作。

适用范围

此解决方案适用于运行 17.8 或更高版本的 GitLab 实例。

TIP

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

如何确定是否已启用对象存储?

GitLab 数据由三个主要部分组成:PostgreSQL 数据库、Git repositories 和上传的制品(包括用户头像、评论附件等)。此解决方案仅支持备份 Git repositories 和上传的制品。PostgreSQL 数据库需要根据您的数据库服务提供商的备份策略单独执行备份。

DANGER

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

术语

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

先决条件

  1. 部署 MinIO 对象存储:当前的备份和恢复解决方案依赖对象存储来保存备份数据,因此需要预先部署 MinIO 实例。ACP 提供 MinIO Object Storage,用于 快速创建 MinIO 实例。
  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 instance access address> # Example: http://192.168.1.100:32008
export MINIO_ACCESS_KEY=<MinIO instance access key> # Example: minioadmin
export MINIO_SECRET_KEY=<MinIO instance secret key> # Example: minioadminpassword
export MINIO_ALIAS_NAME=<MinIO instance alias name> # Example: myminio

export VELERO_BACKUP_BUCKET=<Velero backup bucket name> # Example: backup
export VELERO_BACKUP_REPO_NAME=<Velero backup repo name> # Example: gitlab-backup-repo

export GITLAB_NAMESPACE=<namespace of the GitLab instance to be backed up>
export GITLAB_NAME=<name of the GitLab instance to be backed up>

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

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

# Output:
#   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}

# Output:
# 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

# Output
# 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
    Please set the correct namespace and instance name:
    export GITLAB_NAME=<original GitLab instance name>
    export GITLAB_NAMESPACE=<original namespace name>
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 "Found 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})

praefect_db_secret_name=$(kubectl get gitlabofficial -n ${GITLAB_NAMESPACE} ${GITLAB_NAME} -o jsonpath='{.spec.helmValues.global.praefect.dbSecret.secret}')
if [ -n "${praefect_db_secret_name}" ]; then
    echo "Found praefect database secret: ${praefect_db_secret_name}"
    secrets_to_label+=(${praefect_db_secret_name})
fi

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 "Found 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 "Found 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 "Found 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 "Found object storage connect 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 "Add label to 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}

# Output:
# secret/sh.helm.release.v1.xxx-gitlab.v9 not labeled
WARNING

如果 Redis Secret 由 GitLab 实例外部进行管理,例如由 Alauda Cache Service for Redis OSS 管理,则外部控制器可能会移除此脚本添加的备份标签。在这种情况下,Velero 不会备份 Redis Secret。请将外部管理的 Redis Secret 视为外部依赖项:手动备份该 Secret,并在启动恢复后的 GitLab 实例之前将其恢复到目标命名空间。

如果需要备份其他资源,请参阅为其他资源添加备份标签部分。

停止 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

# Output:
# 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

# Output
# 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

# Output
# 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.

要解决此问题,需要将 --features=EnableCSI 参数添加到 Velero 部署中。

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

# Output
# 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

# Output
# 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}"

# Output
# 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. 准备 Redis Secret(可选)
  3. 选择要恢复的备份
  4. 确定恢复目标命名空间
  5. 卸载 Alauda Toolchain Code Repository E1 Operator

恢复 PostgreSQL 数据库(可选)

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

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

准备 Redis Secret(可选)

如果新实例仍使用相同的 Redis 实例和 Secret 名称,请确保目标命名空间中存在 Redis Secret。当 Redis Secret 在 GitLab 实例之外进行管理时(例如由 Alauda Cache Service for Redis OSS 管理),外部控制器可能会移除在 Label Resources for Backup 步骤中添加的备份标签。在这种情况下,Velero 不会备份 Redis Secret,因此你需要手动备份 Redis Secret,并将其恢复或复制到目标命名空间。

如果新实例使用的 Redis 实例或 Secret 名称与旧实例不同,请先在目标命名空间中为新实例创建 Redis Secret。在修改 GitLab CR 资源步骤中,你将更新 GitLab CR,以使用新的 Redis 连接信息。

选择要恢复的备份数据

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

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

# Output:
# gitlab-backup-xxx     Completed       2025-07-01T07:29:19Z

设置 BACKUP_NAME 环境变量:

export BACKUP_NAME=<selected backup record name>

确定目标命名空间

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

export NEW_GITLAB_NAMESPACE=<new namespace name>
kubectl create namespace ${NEW_GITLAB_NAMESPACE}

卸载 Alauda Toolchain Code Repository E1 Operator

执行以下命令卸载 Alauda Toolchain Code Repository E1 operator:

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

# Output:
# subscription.operators.coreos.com "gitlab-ce-operator" deleted
为什么要卸载 operator?

在恢复过程中,Velero 需要启动备份 Pod 以恢复 PVC 中的数据,这需要一定时间。如果在恢复过程中未卸载 operator,可能会出现以下问题:

  1. operator 可能会根据 GitLab CR 重新创建工作负载资源,导致已恢复的 Pod 重启或重新创建,最终导致恢复中断或失败。
  2. 某些已恢复的资源可能发生冲突,例如,operator 根据已恢复的 GitLab CR 创建的 ingress 资源可能会与旧实例的 ingress 资源冲突。
卸载 Alauda Toolchain Code Repository E1 Operator 的影响

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

卸载 operator 不会导致现有实例发生故障。

恢复操作

恢复操作包括以下五个步骤:

  1. 创建恢复配置文件
  2. 创建恢复任务
  3. 清理资源
  4. 修改 GitLab CR 资源
  5. 部署 Alauda Toolchain Code Repository E1 Operator

创建恢复配置文件

仔细阅读 YAML 中的注释,并根据实际情况进行修改(例如更改存储类),然后创建修改后的 YAML。

# If you need to change storage class, set the following two environment variables
OLD_STORAGECLASS_NAME='' # Original storage class name
NEW_STORAGECLASS_NAME='' # New storage class 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

# Output
# 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}"


# Output
# 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 部署
    • 修改原因:旧实例和新实例创建的服务资源使用相同 NodePort 会导致冲突,从而导致新实例的服务资源创建失败
    • 修改建议:
      • (推荐)将原实例的 NodePort 更改为临时端口,保持新实例不变
      • 保持原实例的 NodePort 不变,将新实例的 NodePort 更改为新端口
    • 修改方法:请参考配置实例网络访问修改实例访问配置。
  3. 存储类:
    • 适用场景:原实例使用存储类部署,并且新旧实例之间的存储类发生变化(例如,原实例使用 NFS,新实例使用 Ceph)
    • 修改原因:如果不进行修改,operator 仍会使用旧存储类创建 PVC,从而与已恢复的 PVC 发生冲突
    • 修改建议:将存储类更改为正确的值
    • 修改方法:请参考配置实例存储修改实例存储配置。
  4. PostgreSQL 连接信息:
    • 适用场景:新实例和旧实例计划使用不同的 PostgreSQL 实例或数据库
    • 修改原因:如果不进行修改,新实例仍会连接到旧的 PostgreSQL 实例或数据库
    • 修改建议:将 PostgreSQL 连接信息更改为正确的值
    • 修改方法:请参考配置 PostgreSQL 访问凭证修改 PostgreSQL 访问凭证配置。
  5. Redis 连接信息:
    • 适用场景:新实例和旧实例计划使用不同的 Redis 实例或 Secret 名称
    • 修改原因:如果不进行修改,新实例仍会连接到旧的 Redis 实例,或使用旧的 Redis Secret 名称
    • 修改建议:将 Redis 连接信息更改为正确的值
    • 修改方法:请参考配置 Redis 访问凭证修改 Redis 访问凭证配置。

部署 Alauda Toolchain Code Repository E1 Operator

进入 Administrator 视图,前往 Marketplace -> OperatorHub 页面,然后重新部署 Alauda Toolchain Code Repository E1 operator。

部署 Operator 后,operator 将根据 GitLab CR 部署新实例。你可以在实例详情页面查看实例部署进度。

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

  • 组
  • 仓库
  • 用户
  • 合并请求

问答

为备份标记其他资源

此步骤为可选步骤。如果同一命名空间中还有其他希望一同备份的资源,可以为相应资源添加 release 标签。标签值应为原 GitLab 实例名称,例如:

metadata:
  labels:
    release: gitlab-g6zd4

如何确定 PVC 是否支持快照?

要确定 PVC 是否支持快照,需要检查其底层 StorageClass 是否支持快照功能。如果集群中存在一个 driver 与 StorageClass 的 provisioner 匹配的 VolumeSnapshotClass,则 PVC 支持快照。如果存在这样的 VolumeSnapshotClass,则该 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 driver 必须支持快照操作

GitLab 是 GitLab Inc. 的商标。

Alauda 是独立厂商。本产品与 GitLab Inc. 没有任何关联,也未获得其认可或赞助。所有商标均归其各自所有者所有,此处仅用于标识。