• Русский
  • Резервное копирование и восстановление GitLab с использованием Velero

    В этом руководстве показано, как реализовать операции резервного копирования и восстановления GitLab с помощью Velero, инструмента для аварийного восстановления в облаке с открытым исходным кодом.

    Содержание

    Область примененияТерминологияПредварительные требованияРезервное копированиеПодготовкаСоздание бакетаНастройка репозитория резервных копий VeleroВыполнение резервного копированияПометить ресурсы для резервного копированияОстановить сервисы GitLabСоздать pod для резервного копированияСоздать политику резервного копирования томовВыполнить резервное копированиеВосстановить сервисы GitLabВосстановлениеПодготовкаВосстановление базы данных PostgreSQL (опционально)Выбор данных резервной копии для восстановленияОпределение целевого namespaceУдаление Gitlab CE OperatorОперации восстановленияСоздание файла конфигурации восстановленияСоздание задачи восстановленияОчистка ресурсовИзменение ресурса GitLab CRРазвёртывание Gitlab CE OperatorВопросы и ответыПометка дополнительных ресурсов для резервного копированияКак определить, поддерживает ли PVC snapshot?

    Область применения

    Данное решение применимо к экземплярам GitLab версии 17.8 и выше.

    TIP

    Если ваш экземпляр GitLab уже настроен с объектным хранилищем, мы рекомендуем в первую очередь использовать официальное решение для резервного копирования и восстановления.

    Как определить, включено ли объектное хранилище?

    Данные GitLab состоят из трёх основных компонентов: база данных PostgreSQL, Git-репозитории и загруженные артефакты (включая аватары пользователей, вложения к комментариям и т.д.). Это решение поддерживает резервное копирование только Git-репозиториев и загруженных артефактов. Резервное копирование базы данных PostgreSQL требует отдельной процедуры в соответствии со стратегией резервного копирования вашего поставщика базы данных.

    DANGER

    Данное решение не поддерживает экземпляры GitLab, развернутые с использованием хранилища HostPath.

    Терминология

    ТерминОпределение
    Source InstanceЭкземпляр GitLab до резервного копирования
    Target InstanceЭкземпляр GitLab после восстановления
    Source NamespaceПространство имён, в котором расположен исходный экземпляр
    Target NamespaceПространство имён, в котором расположен целевой экземпляр
    GitLab CR ResourceОписывает конфигурацию развертывания экземпляра GitLab, используется Operator для развертывания экземпляров GitLab

    Предварительные требования

    1. Развернуть MinIO Object Storage: Текущее решение резервного копирования и восстановления использует объектное хранилище для сохранения данных резервных копий, требуется предварительно развернутый экземпляр 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> # Пример: 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. Создание бакета
    2. Настройка репозитория резервных копий Velero

    Создание бакета

    Выполните следующие команды для создания бакета для хранения данных резервных копий:

    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
        Пожалуйста, задайте правильные namespace и имя экземпляра:
        export GITLAB_NAME=<оригинальное имя экземпляра GitLab>
        export GITLAB_NAMESPACE=<оригинальное имя 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 аннотирован
    # deployment.apps/xxxx-gitlab-webservice-default аннотирован
    # deployment.apps/xxxx-gitlab-toolbox масштабирован
    # deployment.apps/xxxx-gitlab-webservice-default масштабирован

    Создать pod для резервного копирования

    Этот шаг создаёт pod, который монтирует PVC GitLab, чтобы 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 создан
    # pod/xxxx-gitlab-backup-pod готов

    Создать политику резервного копирования томов

    Политика томов используется для указания метода резервного копирования PVC. По умолчанию PVC резервируются с помощью метода fs-backup.

    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 создан

    Если PVC поддерживает snapshot, можно использовать snapshot для резервного копирования PVC, чтобы ускорить процесс. Для этого необходимо изменить ConfigMap политики томов, указав, что класс хранения использует snapshot для резервного копирования. Например, если класс хранения ceph поддерживает snapshot, нужно добавить следующую конфигурацию (добавленное условие должно быть в начале для более высокого приоритета):

    Как определить, поддерживает ли PVC snapshot?

    WARNING

    Если вы используете резервное копирование PVC через snapshot, при восстановлении нельзя менять класс хранения. Пожалуйста, корректируйте класс хранения в соответствии с реальной ситуацией.

    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 snapshot, при резервном копировании вы получите следующую ошибку:

    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
    
    # Вывод
    # schedule.velero.io/gitlab-backup создан
    # backup.velero.io/gitlab-backup-r6hht создан

    Просмотр логов резервного копирования:

    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. Определение целевого namespace для восстановления
    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=<имя выбранной резервной копии>

    Определение целевого namespace

    Рекомендуется восстанавливать экземпляр в новое пространство имён. Задайте следующие переменные окружения:

    export NEW_GITLAB_NAMESPACE=<имя нового 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" удалён
    Почему нужно удалить GitLab operator?

    Во время восстановления Velero запускает pod для восстановления данных в PVC, что занимает некоторое время. Если operator не удалить, могут возникнуть следующие проблемы:

    1. Operator может пересоздавать ресурсы workload на основе GitLab CR, что приведёт к перезапуску или пересозданию восстановленных pod, и, в итоге, к прерыванию или сбою восстановления.
    2. Некоторые восстановленные ресурсы могут конфликтовать, например, ingress ресурсы, созданные operator на основе восстановленного GitLab CR, могут конфликтовать с 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
      • Причина изменения: Идентичные NodePort в сервисах, созданных старым и новым экземплярами, вызовут конфликт и приведут к ошибке создания сервисов нового экземпляра
      • Рекомендации по изменению:
        • (Рекомендуется) Изменить NodePort исходного экземпляра на временный, оставить новый экземпляр без изменений
        • Оставить 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 развернёт новый экземпляр согласно GitLab CR. Вы можете отслеживать прогресс развертывания на странице деталей экземпляра.

    После того, как статус экземпляра вернётся в норму, войдите в GitLab и проверьте успешность восстановления данных. Проверяемые элементы включают, но не ограничиваются:

    • Группы
    • Репозитории
    • Пользователи
    • Merge Requests

    Вопросы и ответы

    Пометка дополнительных ресурсов для резервного копирования

    Этот шаг необязателен. Если у вас есть другие ресурсы в том же namespace, которые вы хотите резервировать вместе, вы можете добавить метку release к соответствующим ресурсам. Значение метки должно быть именем исходного экземпляра GitLab, например:

    metadata:
      labels:
        release: gitlab-g6zd4

    Как определить, поддерживает ли PVC snapshot?

    Чтобы определить, поддерживает ли PVC snapshot, необходимо проверить, поддерживает ли его базовый StorageClass функциональность snapshot. PVC поддерживает snapshot, если в кластере существует VolumeSnapshotClass, у которого поле driver совпадает с provisioner StorageClass. Если такой VolumeSnapshotClass есть, значит StorageClass (а значит и PVC) поддерживает snapshot.

    Пример конфигурации:

    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

    Ключевые моменты:

    • provisioner StorageClass должен точно совпадать с driver VolumeSnapshotClass
    • Оба ресурса должны существовать в кластере
    • CSI драйвер должен поддерживать операции snapshot