从 GitLab 14.0.12 迁移数据到 17.8.z

要解决的问题

3.16 和 3.18 版本支持的 GitLab 版本落后于官方版本。将 GitLab 升级到最新官方版本需要完成 10 多次升级。升级到 4.0 后,产品不会自动升级到 17.8.z。本方案介绍如何将数据从 14.0.12 迁移到 17.8.z。

考虑到版本差距较大,我们采用数据迁移方式进行升级。

术语

术语说明
all-in-one GitLab所有 GitLab 组件都打包并部署在单个容器或 Pod 中。用于快速升级版本。
platform-deployed GitLabGitLab 由平台使用 Operator 进行部署和管理,各组件相互分离。

流程概述

数据迁移路径

根据官方升级路径文档,数据迁移路径如下:

  • 14.0.12 -> 14.3.6
  • 14.3.6 -> 14.9.5
  • 14.9.5 -> 14.10.5
  • 14.10.5 -> 15.0.5
  • 15.0.5 -> 15.4.6
  • 15.4.6 -> 15.11.13
  • 15.11.13 -> 16.3.8
  • 16.3.8 -> 16.7.9
  • 16.7.9 -> 16.11.10
  • 16.11.10 -> 17.3.6
  • 17.3.6 → 17.5.5
  • 17.5.5 → 17.8.1

最后,备份 17.8.1 中的数据,并将其导入 platform-deployed 17.8.z 实例,以完成数据迁移。

数据迁移流程

备份 platform-deployed GitLab,将其恢复到使用 all-in-one 镜像部署的 GitLab,然后使用 all-in-one 镜像升级到 17.8.1,最后将数据备份恢复到 platform-deployed 17.8.z 实例。

  1. 备份 platform-deployed GitLab 14.0.12 实例。
  2. 将备份恢复到 all-in-one 部署的 GitLab 14.0.12。
  3. 对 all-in-one 部署执行滚动升级,将其升级到 GitLab 17.8.1。
  4. 备份升级后的 all-in-one GitLab 17.8.1 实例。
  5. 将 all-in-one 17.8.1 的备份恢复到 platform-deployed GitLab 17.8.z 实例。
TIP

有关最新的 GitLab 实例和 Operator 版本,请参阅发布说明。

迁移时长

迁移过程涉及数据库和代码仓库的备份与恢复操作:

  • 数据库越大,迁移所需时间越长。
  • 代码仓库数量越多,或单个代码仓库越大,迁移时长会显著增加。
  • 存储性能也会影响效率——建议使用 topolvm 以获得更好的性能。

测试环境:

  • 一个大型代码仓库(~600 MB),其他代码仓库为较小的初始代码仓库
  • GitLab 实例:6,700 个议题,3,600 个合并请求
迁移步骤关键因素1,000 个代码仓库10,000 个代码仓库
备份平台部署的 GitLab 14.0.12代码仓库大小和数量10 分钟30 分钟
恢复到一体化 GitLab 14.0.12代码仓库大小和数量20 分钟1 小时
滚动升级到 GitLab 17.8.1数据库大小1 小时 30 分钟1 小时 40 分钟
备份一体化 GitLab 17.8.1代码仓库大小和数量3 分钟10 分钟
恢复到平台部署的 GitLab代码仓库大小和数量30 分钟1 小时

前提条件

  1. 在执行主机上安装 kubectl(>=1.30.0)和 yq(>=4.45.0)。

    kubectl version --client
    yq --version
    
    # Output:
    # Client Version: v1.30.0
    # Kustomize Version: v5.4.2
    # yq (https://github.com/mikefarah/yq/) version v4.45.4
  2. 配置环境变量:

    # Replace with the name of the old GitLab CR
    export GITLAB_NAME=<old gitlab name>
    export GITLAB_NAMESPACE=<gitlab deploy namespace>
  3. 准备用于升级的 PVC。这些 PVC 必须创建在与旧 GitLab 实例相同的命名空间中:

    TIP

    估算原始实例所需存储大小的一种简单方法,是将数据库和 Gitaly 组件使用的 PVC 大小相加。

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: backup-pvc
      namespace: ${GITLAB_NAMESPACE}
    spec:
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 40Gi
      storageClassName: ceph
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: upgrade-pvc
      namespace: ${GITLAB_NAMESPACE}
    spec:
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 20Gi
      storageClassName: ceph
      volumeMode: Filesystem
    1. backup-pvc:用于存储备份 tar 包。建议容量为原始实例大小的两倍。
    2. 替换为集群中的存储类名称。
    3. upgrade-pvc:用于在滚动升级过程中存储 GitLab 数据。建议分配的存储空间等于原始实例的总存储大小加上数据库大小的三倍。
    4. 替换为集群中的存储类名称。
  4. 准备用于升级的镜像。从附件中下载每个版本的镜像,并将其推送到 GitLab 部署集群可以拉取的镜像仓库。镜像下载链接:

    中国地域
    其他地域
    # amd images
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-14-0-12-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-14-3-6-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-14-9-5-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-14-10-5-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-15-0-5-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-15-4-6-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-15-11-13-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-16-3-8-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-16-7-9-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-16-11-10-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-17-3-6-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-17-5-5-amd.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-17-8-1-amd.tgz
    
    # arm images
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-14-0-12-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-14-3-6-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-14-9-5-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-14-10-5-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-15-0-5-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-15-4-6-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-15-11-13-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-16-3-8-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-16-7-9-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-16-11-10-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-17-3-6-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-17-5-5-arm.tgz
    https://cloud.alauda.cn/attachments/knowledge/242090509/gitlab-ce-17-8-1-arm.tgz
  5. 准备用于升级的脚本。

    执行以下命令以生成四个脚本:

  • check_gitlab.sh:检查 GitLab pod 是否就绪。
  • finalize_migrations.sql:完成迁移。
  • monitor_gitlab.sh:监控 GitLab pod,以检查数据迁移是否完成。
  • rolling_upgrade.sh:逐个滚动升级 GitLab 实例。
命令
生成的脚本
cat << 'EOF' > check_gitlab.sh
#!/bin/bash

gitlab_pod=$1
port=${2:-30855}

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log "Starting monitoring script for GitLab pod: ${gitlab_pod} on port: ${port}"

while true; do
  curl_result=$(kubectl -n $GITLAB_NAMESPACE exec ${gitlab_pod} -- curl -s localhost:${port} | grep "You are being")
  if [[ -z "${curl_result}" ]]; then
      log "HTTP not ready. Retrying in 10 seconds..."
      sleep 10
      continue
  fi

  log "GitLab is ready"
  break
done
EOF

cat << 'EOF' > finalize_migrations.sql
select concat(
  'gitlab-rake gitlab:background_migrations:finalize[',
  job_class_name, ',',
  table_name, ',',
  column_name, $$,'$$,
  REPLACE(cast(job_arguments as text), ',', $$\,$$),
  $$']$$
)
from batched_background_migrations WHERE status NOT IN(3, 6);
EOF

cat << 'EOF' > monitor_gitlab.sh
#!/bin/bash

gitlab_pod=$1
port=${2:-30855}

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log "Starting monitoring script for GitLab pod: ${gitlab_pod} on port: ${port}"

while true; do
  curl_result=$(kubectl -n $GITLAB_NAMESPACE exec ${gitlab_pod} -- curl -s localhost:${port} | grep "You are being")
  if [[ -z "${curl_result}" ]]; then
      log "HTTP check not ready. Retrying in 10 seconds..."
      sleep 10
      continue
  fi

  migration_result=$(kubectl -n $GITLAB_NAMESPACE exec ${gitlab_pod} -- gitlab-psql -c "SELECT job_class_name, table_name,  column_name, job_arguments FROM batched_background_migrations WHERE status NOT IN(3, 6);" | grep "(0 rows)")

  if [[ -z "${migration_result}" ]]; then
      log "Database migration is running. Retrying in 10 seconds..."
      sleep 10
      continue
  fi

  log "GitLab is ready, all migrations are done"
  break
done
EOF

cat << 'EOF' > rolling_upgrade.sh
#!/bin/bash

image=$(kubectl -n $GITLAB_NAMESPACE get deployment gitlab -o jsonpath='{.spec.template.spec.containers[?(@.name=="gitlab")].image} ')

versions=(
  14.0.12-ce.0
  14.3.6-ce.0
  14.9.5-ce.0
  14.10.5-ce.0
  15.0.5-ce.0
  15.4.6-ce.0
  15.11.13-ce.0
  16.3.8-ce.0
  16.7.9-ce.0
  16.11.10-ce.0
  17.3.6-ce.0
  17.5.5-ce.0
  17.8.1-ce.0
)

STATE_FILE="upgrade_state.log"
touch "$STATE_FILE"

is_done() {
  grep -Fxq "$1" "$STATE_FILE"
}

mark_done() {
  echo "$1" >> "$STATE_FILE"
}

for version in "${versions[@]}"; do
  if is_done "$version"; then
    echo "Version ${version} already processed, skipping..."
    continue
  fi

  echo "Upgrading to ${version}..."
  new_image="${image%:*}:${version}"
  kubectl -n $GITLAB_NAMESPACE set image deployment/gitlab gitlab=$new_image

  echo "Waiting for the GitLab pod to start..."
  sleep 10
  kubectl -n $GITLAB_NAMESPACE wait deploy gitlab --for condition=available --timeout=3000s
  sleep 10
  kubectl -n $GITLAB_NAMESPACE wait pod -l deploy=gitlab --for=condition=Ready --timeout=3000s

  new_pod_name=$(kubectl -n $GITLAB_NAMESPACE get po -l deploy=gitlab --sort-by=.metadata.creationTimestamp  --field-selector=status.phase=Running -o jsonpath='{.items[-1].metadata.name}')
  echo "Waiting for GitLab to be ready..."
  bash check_gitlab.sh $new_pod_name

  echo "Waiting for migrations to finish..."
  kubectl -n $GITLAB_NAMESPACE cp finalize_migrations.sql $new_pod_name:/tmp/finalize_migrations.sql
  for i in {1..10}; do
    echo "Running migration tasks (attempt $i/10)..."
    if kubectl -n $GITLAB_NAMESPACE exec -ti $new_pod_name -- bash -c "gitlab-psql -t -A -f /tmp/finalize_migrations.sql > /tmp/run_migration_tasks.sh && xargs -d \"\n\" -P 3 -I {} bash -c \"{}\" < /tmp/run_migration_tasks.sh"; then
      echo "Migration tasks completed successfully"
      break
    fi
    sleep 30
  done
  bash monitor_gitlab.sh $new_pod_name
  echo "Upgraded to ${version} successfully"
  mark_done "$version"
done

echo "All upgrades completed successfully!"
EOF

备份 platform-deployed GitLab 14.0.12

  1. 在 GitLab 14 上启用 task-runner 组件以执行备份命令

    Platform 3.18.0 以下版本的命令
    Platform 3.18.0 及更高版本的命令
    输出
    # generate the image repository and tag for the task-runner based on the gitaly image
    image=$(kubectl get pod ${GITLAB_NAME}-gitaly-0 -n $GITLAB_NAMESPACE -o jsonpath='{.spec.containers[0].image}')
    tag=${image##*:}
    registry=${image%%/*}
    
    echo "registry: ${registry}"
    echo "tag: ${tag}"
    
    kubectl patch gitlabofficials.operator.devops.alauda.io "${GITLAB_NAME}" \
      -n "$GITLAB_NAMESPACE" \
      --type=merge \
      -p "$(cat <<EOF
    {
      "spec": {
        "helmValues": {
          "gitlab": {
            "task-runner": {
              "enabled": "true",
              "image": {
                "repository": "${registry}/devops/gitlab-org/build/cng/gitlab-task-runner-ce",
                "tag": "${tag}"
              },
              "resources": {
                "limits": {
                  "cpu": "2",
                  "memory": "4G"
                }
              }
            }
          }
        }
      }
    }
    EOF
    )"
    
    # Wait for the task-runner deployment to be available
    echo "Waiting for the task-runner deployment to be available..."
    sleep 60
    
    # Execute the command and confirm that the ${GITLAB_NAME}-task-runner deployment exists
    kubectl wait deploy ${GITLAB_NAME}-task-runner -n $GITLAB_NAMESPACE --for condition=available --timeout=3000s
    kubectl get deploy -n $GITLAB_NAMESPACE ${GITLAB_NAME}-task-runner
  2. 将 GitLab 14 设置为只读模式

    命令
    输出
    kubectl patch deploy ${GITLAB_NAME}-webservice-default -n $GITLAB_NAMESPACE --type='merge' -p='{"metadata":{"annotations":{"skip-sync":"true"}}}'
    kubectl patch deploy ${GITLAB_NAME}-sidekiq-all-in-1-v1 -n $GITLAB_NAMESPACE --type='merge' -p='{"metadata":{"annotations":{"skip-sync":"true"}}}'
    kubectl scale deploy ${GITLAB_NAME}-webservice-default -n $GITLAB_NAMESPACE --replicas 0
    kubectl scale deploy ${GITLAB_NAME}-sidekiq-all-in-1-v1 -n $GITLAB_NAMESPACE --replicas 0
  3. 为 GitLab 14 task-runner 修补备份 PVC。PVC 应命名为 backup-pvc。

    命令
    输出
    kubectl patch deploy ${GITLAB_NAME}-task-runner -n $GITLAB_NAMESPACE --type='json' -p='
    [
      {
        "op": "add",
        "path": "/metadata/annotations/skip-sync",
        "value": "true"
      },
      {
        "op": "replace",
        "path": "/spec/template/spec/volumes/1",
        "value": {"name": "task-runner-tmp", "persistentVolumeClaim": {"claimName": "backup-pvc"}}
      }
    ]'
    
    echo "Waiting for the task-runner deployment to be available..."
    sleep 60
    
    # Execute the command and confirm that the ${GITLAB_NAME}-task-runner deployment exists
    kubectl wait deploy ${GITLAB_NAME}-task-runner -n $GITLAB_NAMESPACE --for condition=available --timeout=3000s
    kubectl get deploy -n $GITLAB_NAMESPACE ${GITLAB_NAME}-task-runner
  4. 备份 GitLab 14 数据

    命令
    输出
    pod_name=$(kubectl get po -n $GITLAB_NAMESPACE -l app=task-runner,release=${GITLAB_NAME} -o jsonpath='{.items[0].metadata.name}')
    kubectl exec -ti -n "$GITLAB_NAMESPACE" $pod_name -- gitlab-rake gitlab:backup:create SKIP=repositories
    
    # Add permissions to the backup file.
    kubectl exec -ti -n "$GITLAB_NAMESPACE" $pod_name -- bash -c '
    chmod 777 /srv/gitlab/tmp/backups/*_gitlab_backup.tar
    ls -lh /srv/gitlab/tmp/backups
    '

    执行以下命令备份 rails-secret,该命令会将 rails-secret 保存到当前目录中名为 gitlab14-rails-secret.yaml 的文件。

    kubectl get secrets -n ${GITLAB_NAMESPACE} ${GITLAB_NAME}-rails-secret -o jsonpath="{.data['secrets\.yml']}" | base64 --decode | yq -o yaml > gitlab14-rails-secret.yaml
  5. 备份完成后移除 task-runner 组件。

    命令
    输出
    kubectl patch gitlabofficials.operator.devops.alauda.io ${GITLAB_NAME} -n $GITLAB_NAMESPACE --type='merge' -p='
    {
      "spec": {
        "helmValues": {
          "gitlab": {
            "task-runner": null
          }
        }
      }
    }'
    
    echo "Waiting for the task-runner deployment to be removed..."
    sleep 30
    
    kubectl get po -n "$GITLAB_NAMESPACE" -l app=task-runner,release=${GITLAB_NAME}

将备份数据恢复到 all-in-one 的 14.0.12 部署

  1. 使用 all-in-one 镜像部署 GitLab 14.0.12。

    首先,设置 NODE_IP 环境变量,以通过此 IP 和主机端口访问 all-in-one GitLab 实例。

    export NODE_IP=<node_ip>
    export GITLAB_IMAGE=<registry-host>/gitlab/gitlab-ce:14.0.12-ce.0

    创建 all-in-one GitLab 实例。

    命令
    输出
    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Service
    metadata:
      name: gitlab-http-nodeport
      namespace: ${GITLAB_NAMESPACE}
    spec:
      ports:
        - appProtocol: tcp
          name: web
          port: 30855
          protocol: TCP
          targetPort: 30855
          nodePort: 30855
      selector:
        deploy: gitlab
      type: NodePort
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: gitlab
      namespace: ${GITLAB_NAMESPACE}
    spec:
      progressDeadlineSeconds: 600
      replicas: 1
      revisionHistoryLimit: 10
      selector:
        matchLabels:
          deploy: gitlab
      strategy:
        rollingUpdate:
          maxSurge: 1
          maxUnavailable: 1
        type: RollingUpdate
      template:
        metadata:
          labels:
            deploy: gitlab
        spec:
          affinity: {}
          containers:
            - env:
                # External access address of the all-in-one gitlab, need to replace with the IP and nodeport port of a cluster node
                - name: GITLAB_OMNIBUS_CONFIG
                  value: external_url 'http://${NODE_IP}:30855'
              image: ${GITLAB_IMAGE} # Can be replaced with an image that can be pulled
              imagePullPolicy: IfNotPresent
              name: gitlab
              ports:
                - containerPort: 30855
                  name: http
                  protocol: TCP
                - containerPort: 2424
                  name: ssh
                  protocol: TCP
              resources:
                limits:
                  cpu: "8"
                  memory: 8Gi
                requests:
                  cpu: 4
                  memory: "4Gi"
              securityContext:
                privileged: true
              terminationMessagePath: /dev/termination-log
              terminationMessagePolicy: File
              volumeMounts:
                - mountPath: /dev/shm
                  name: dshm
                - mountPath: /var/opt/gitlab/backups
                  name: backup
                  subPath: backups
                - mountPath: /etc/gitlab
                  name: gitlab-upgrade-data
                  subPath: config
                - mountPath: /var/log/gitlab
                  name: gitlab-upgrade-data
                  subPath: logs
                - mountPath: /var/opt/gitlab
                  name: gitlab-upgrade-data
                  subPath: data
          dnsPolicy: ClusterFirst
          restartPolicy: Always
          schedulerName: default-scheduler
          securityContext: {}
          terminationGracePeriodSeconds: 30
          volumes:
            - name: dshm
              emptyDir:
                medium: Memory
                sizeLimit: 512Mi
            - name: backup
              persistentVolumeClaim:
                claimName: backup-pvc
            - name: gitlab-upgrade-data
              persistentVolumeClaim:
                claimName: upgrade-pvc
    EOF

    检查 all-in-one GitLab 实例是否已成功启动。

    命令
    输出
    pod_name=$(kubectl get po -l deploy=gitlab -n $GITLAB_NAMESPACE -o jsonpath='{.items[0].metadata.name}')
    bash check_gitlab.sh $pod_name
  2. 修改 all-in-one GitLab 的 rails secret 内容。将 secret_key_base、otp_key_base、db_key_base、openid_connect_signing_key、ci_jwt_signing_key 字段替换为备份的 GitLab 14 rails secret 中的对应字段。

    命令
    输出
    # Copy the gitlab-secrets.json to the local
    pod_name=$(kubectl get po -l deploy=gitlab -n $GITLAB_NAMESPACE -o jsonpath='{.items[0].metadata.name}')
    kubectl -n $GITLAB_NAMESPACE cp $pod_name:/etc/gitlab/gitlab-secrets.json all-in-one-gitlab-secrets.json
    
    # Replace with the data backup from the gitlab 14 rails-secret.
    KEYS=(secret_key_base otp_key_base db_key_base openid_connect_signing_key ci_jwt_signing_key)
    for key in "${KEYS[@]}"; do
      echo "Replace ${key} ..."
      export KEY_VALUE=$(yq eval -r --unwrapScalar=false ".production.${key}" gitlab14-rails-secret.yaml)
      yq eval ".gitlab_rails.${key} = env(KEY_VALUE)" all-in-one-gitlab-secrets.json -i
    done
    
    # Copy the gitlab-secrets.json to the pod
    kubectl -n $GITLAB_NAMESPACE cp all-in-one-gitlab-secrets.json $pod_name:/etc/gitlab/gitlab-secrets.json

    禁用不必要的组件。

    cat <<EOF >> gitlab.rb
    prometheus['enable'] = false
    gitlab_kas['enable'] = false
    redis_exporter['enable'] = false
    gitlab_exporter['enable'] = false
    postgres_exporter['enable'] = false
    sidekiq['enable'] = false
    EOF
    
    # Copy the gitlab.rb to the pod
    kubectl -n $GITLAB_NAMESPACE cp gitlab.rb $pod_name:/etc/gitlab/gitlab.rb
  3. 将 Gitaly 数据同步到 All-in-One GitLab

    Tip

    此步骤必须在运行 All-in-One GitLab pod 的节点上执行。

    首先,在节点上定位挂载路径:原始 GitLab Gitaly 组件的 PVC(名为 repo-data-${GITLAB_NAME}-gitaly-0)以及 All-in-One GitLab 使用的升级 PVC(名为 upgrade-pvc)的挂载路径。

    使用 rsync 命令,将 Gitaly 数据高效地从原始 PVC 复制到 All-in-One GitLab 实例的 upgrade-pvc。

    Tip

    使用 rsync 代替 scp ——对于此类数据传输,前者速度明显更快且效率高得多。

    获取原始实例的存储类型

    persistence_type=$(kubectl get GitlabOfficial.operator.devops.alauda.io -n $GITLAB_NAMESPACE $GITLAB_NAME -o jsonpath='{.spec.persistence.type}')
    echo "gitlab persistence type: $persistence_type"
    使用 LocalPath 部署
    使用 PVC 或 StorageClass 部署
    origin_gitaly_local_mount_path=$(kubectl get GitlabOfficial.operator.devops.alauda.io -n $GITLAB_NAMESPACE $GITLAB_NAME -o jsonpath='{.spec.persistence.location.path}')/gitlab
    origin_gitaly_node_ip=<origin_gitaly_node_ip>
    all_in_one_upgrade_pvc_mount_path=<all_in_one_gitaly_mount_path>
    
    # Run this command on the node where the all-in-one pod is located.
    rsync -avh --chown=998:998 ${origin_gitaly_node_ip}:${origin_gitaly_local_mount_path}/ ${all_in_one_upgrade_pvc_mount_path}/data/git-data/repositories/
    
    # check the ownership of the repository data.
    ls -alh ${all_in_one_upgrade_pvc_mount_path}/data/git-data/repositories

    重启 pod 并等待其启动。

    命令
    输出
    # Restart the pod
    kubectl delete po -l deploy=gitlab -n $GITLAB_NAMESPACE
    
    echo "Waiting for the pod to start..."
    sleep 10
    
    pod_name=$(kubectl get po -l deploy=gitlab -n $GITLAB_NAMESPACE -o jsonpath='{.items[0].metadata.name}')
    bash check_gitlab.sh $pod_name
  4. 恢复备份数据。

    TIP

    在恢复过程中,可能会出现与数据库相关的错误,例如:

    • ERROR: role "xxx" does not exist
    • ERROR: function xxx does not exist
    • ERROR: permission denied to create extension "pg_stat_statements"
    • ERROR: could not open extension control file

    这些错误可以忽略,详细信息请参阅(https://gitlab.com/gitlab-org/gitlab/-/issues/266988)。

    命令
    输出
    # Set gitlab to read-only
    pod_name=$(kubectl get po -l deploy=gitlab -n $GITLAB_NAMESPACE -o jsonpath='{.items[0].metadata.name}')
    kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- gitlab-ctl stop puma
    kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- gitlab-ctl stop sidekiq
    
    # Confirm that puma and sidekiq have been stopped
    kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- gitlab-ctl status
    
    # Execute restore
    backup_id=$(kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- bash -c 'ls /var/opt/gitlab/backups/*14.0.12_gitlab_backup.tar | head -n1 | sed "s|.*/\([0-9_]*_14\.0\.12\)_gitlab_backup\.tar|\1|"')
    echo "Backup ID: ${backup_id}"
    
    echo "This step will prompt you multiple times to confirm whether to continue. \nPlease select \"yes\" each time to proceed."
    kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- bash -c 'gitlab-backup restore BACKUP=${backup_id}'

    重启 pod 并等待其启动,然后执行 gitlab-rake gitlab:check SANITIZE=true 检查 GitLab 完整性。

    命令
    输出
    # Restart the pod
    kubectl delete po -l deploy=gitlab -n $GITLAB_NAMESPACE
    
    # Waiting for the pod to start
    sleep 10
    pod_name=$(kubectl get po -l deploy=gitlab -n $GITLAB_NAMESPACE -o jsonpath='{.items[0].metadata.name}')
    bash check_gitlab.sh $pod_name
    
    kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- gitlab-rake gitlab:check SANITIZE=true

    登录 GitLab 以检查实例是否成功恢复(root 用户密码与原始实例保持一致),然后执行以下命令获取 all-in-one 实例的访问地址。

    命令
    输出
    kubectl -n $GITLAB_NAMESPACE get pod -l deploy=gitlab -o yaml | grep external_url

将已部署的 all-in-one gitlab 滚动升级到 17.8.1

执行 rolling_upgrade.sh 脚本,该脚本将根据升级路径,逐步将 all-in-one 实例升级到 17.8.1 版本。

bash -x rolling_upgrade.sh
Tip

在滚动升级期间,数据库可能会自动重启。因此,脚本可能会显示如下错误。这是预期行为——脚本会自动重试并继续执行。

connections on Unix domain socket "/var/opt/gitlab/postgresql/.s.PGSQL.5432"

脚本执行完成后,进入 GitLab Web UI,检查实例版本是否为 17.8.1,并验证数据是否完整,包括代码仓库、用户、议题、合并请求等。

备份 17.8.1 all-in-one 实例中的数据

停止 all-in-one 实例服务并备份数据。备份文件将保存到 /var/opt/gitlab/backups/ 目录中。

命令
输出
pod_name=$(kubectl get po -l deploy=gitlab -n $GITLAB_NAMESPACE -o jsonpath='{.items[0].metadata.name}')
kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- bash -c '
gitlab-ctl stop puma
gitlab-ctl stop sidekiq
gitlab-ctl status
gitlab-rake gitlab:backup:create
'

停止 all-in-one 实例服务。

命令
输出
kubectl scale deploy gitlab -n $GITLAB_NAMESPACE --replicas 0

将 all-in-one 备份数据恢复到 platform-deployed 17.8.z

新 gitlab 实例的命名空间

新 gitlab 实例的命名空间必须与旧 gitlab 实例的命名空间相同。

  1. 参考 GitLab 部署指南,使用 operator 部署新的 GitLab 17.8.z 实例。

    Tip
    1. 新部署的实例必须与旧实例位于同一命名空间中
    2. 新部署实例的访问方式不得与旧实例冲突,例如域名、主机端口等。

    新实例部署完成后,为新实例名称设置环境变量:

    export NEW_GITLAB_NAME=<name of the new GitLab instance>
  2. 为新的 GitLab 实例启用 toolbox 组件。

    kubectl patch gitlabofficial.operator.alaudadevops.io ${NEW_GITLAB_NAME} -n $GITLAB_NAMESPACE --type='merge' -p='
    {
      "spec": {
        "helmValues": {
          "gitlab": {
            "toolbox": {
              "enabled": true,
              "resources": {
                "limits": {
                  "cpu": 2,
                  "memory": "4G"
                }
              }
            }
          }
        }
      }
    }'

    等待实例完成重新部署。

    命令
    输出
    kubectl get po -l app=toolbox,release=${NEW_GITLAB_NAME} -n $GITLAB_NAMESPACE
  3. 将备份数据恢复到由 operator 部署的 GitLab 17.8.z

    TIP

    在恢复过程中,可能会出现与数据库相关的错误,例如:

    • ERROR: role "xxx" does not exist
    • ERROR: function xxx does not exist
    • ERROR: must be owner of extension

    可以忽略这些错误,详情请参阅(https://gitlab.com/gitlab-org/gitlab/-/issues/266988)。

    关闭新的 17.8.z gitlab 实例的外部访问,并修补 toolbox 组件以挂载备份 pvc。

    命令
    输出
    kubectl patch deploy ${NEW_GITLAB_NAME}-webservice-default -n $GITLAB_NAMESPACE --type='merge' -p='{"metadata":{"annotations":{"skip-sync":"true"}}}'
    kubectl patch deploy ${NEW_GITLAB_NAME}-sidekiq-all-in-1-v2 -n $GITLAB_NAMESPACE --type='merge' -p='{"metadata":{"annotations":{"skip-sync":"true"}}}'
    kubectl scale deploy ${NEW_GITLAB_NAME}-webservice-default -n $GITLAB_NAMESPACE --replicas 0
    kubectl scale deploy ${NEW_GITLAB_NAME}-sidekiq-all-in-1-v2 -n $GITLAB_NAMESPACE --replicas 0
    
    # Modify the toolbox, mount the backup pvc to the toolbox
    kubectl patch deploy ${NEW_GITLAB_NAME}-toolbox -n $GITLAB_NAMESPACE -p='{"metadata":{"annotations":{"skip-sync":"true"}}}'
    kubectl patch deploy ${NEW_GITLAB_NAME}-toolbox -n $GITLAB_NAMESPACE --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/volumes/1", "value": {"name": "toolbox-tmp", "persistentVolumeClaim": {"claimName": "backup-pvc"}}}]'

    等待 toolbox pod 就绪,然后将备份数据恢复到新的 GitLab 实例。

    命令
    输出
    toolbox_pod_name=$(kubectl get po -n $GITLAB_NAMESPACE -l release=$NEW_GITLAB_NAME,app=toolbox -o jsonpath='{.items[0].metadata.name}')
    
    # backup the old backup file
    kubectl -n $GITLAB_NAMESPACE exec -ti $toolbox_pod_name -- bash -c '
    mkdir -p /srv/gitlab/tmp/backup_tarballs
    mv /srv/gitlab/tmp/backups/*gitlab_backup.tar /srv/gitlab/tmp/backup_tarballs || true
    '
    
    backup_file=$(kubectl -n $GITLAB_NAMESPACE exec -ti $toolbox_pod_name -- bash -c "ls /srv/gitlab/tmp/backup_tarballs/*17.8.1_gitlab_backup.tar | head -n1 | tr -d '\r\n'")
    echo "Backup file of 17.8.1: ${backup_file}"
    
    kubectl -n $GITLAB_NAMESPACE exec -ti $toolbox_pod_name -- backup-utility --restore --skip-restore-prompt -f "file://${backup_file}"
  4. 替换新 GitLab 实例的 rails-secret。

    命令
    输出
    kubectl get secrets -n ${GITLAB_NAMESPACE} ${NEW_GITLAB_NAME}-rails-secret -o jsonpath="{.data['secrets\.yml']}" | base64 --decode | yq -o yaml > gitlab17-rails-secret.yaml
    yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' gitlab17-rails-secret.yaml gitlab14-rails-secret.yaml > final-rails-secret.yml
    kubectl create secret generic rails-secret -n ${GITLAB_NAMESPACE} --from-file=secrets.yml=final-rails-secret.yml
    
    kubectl patch gitlabofficials.operator.alaudadevops.io ${NEW_GITLAB_NAME} -n $GITLAB_NAMESPACE --type='merge' -p='
    {
      "spec": {
        "helmValues": {
          "global": {
            "railsSecrets": {
              "secret": "rails-secret"
            }
          }
        }
      }
    }'
    
    kubectl patch deploy ${NEW_GITLAB_NAME}-webservice-default -n $GITLAB_NAMESPACE --type='json' -p='[{"op":"remove","path":"/metadata/annotations/skip-sync"}]'
    kubectl patch deploy ${NEW_GITLAB_NAME}-sidekiq-all-in-1-v2 -n $GITLAB_NAMESPACE --type='json' -p='[{"op":"remove","path":"/metadata/annotations/skip-sync"}]'
    kubectl patch deploy ${NEW_GITLAB_NAME}-toolbox -n $GITLAB_NAMESPACE --type='json' -p='[{"op":"remove","path":"/metadata/annotations/skip-sync"}]'
    
    kubectl delete pod -lrelease=${NEW_GITLAB_NAME} -n $GITLAB_NAMESPACE
    kubectl scale deploy ${NEW_GITLAB_NAME}-webservice-default -n $GITLAB_NAMESPACE --replicas 1
    kubectl scale deploy ${NEW_GITLAB_NAME}-sidekiq-all-in-1-v2 -n $GITLAB_NAMESPACE --replicas 1

    operator 将自动重新部署新的 GitLab 实例。等待实例完成重新部署。

  5. 验证升级后的数据是否正常。

    • 以管理员视图进入 GitLab UI,检查仓库和用户数据是否正常。
    • 选择一些仓库,验证代码、分支和合并请求是否正常运行。
  6. 清理未使用的资源:

    • 如有必要,删除旧的 GitLab 实例和旧的 operator。
    • 如有必要,删除 all-in-one GitLab 实例。

FAQ

恢复 Uploads 数据

迁移的数据不包括头像和附件,因此新的 GitLab 实例无法显示这些内容。要解决此问题,可以将 GitLab 17.8.z 的 uploads PVC 替换为 14.0.12 使用的 PVC。

设置 uploads PVC 名称的环境变量:

export UPLOADS_PVC_NAME=<name of the uploads pvc>

将新的 GitLab 实例修补为使用 uploads PVC:

kubectl patch gitlabofficials.operator.alaudadevops.io ${NEW_GITLAB_NAME} -n $GITLAB_NAMESPACE --type='json' -p='
{
  "spec": {
    "helmValues": {
      "global": {
        "uploads": {
          "persistence": {
            "enabled": true,
            "existingClaim": "${UPLOADS_PVC_NAME}"
          }
        }
      }
    }
  }
}'

如果旧的 GitLab 实例使用 HostPath 存储,则必须手动将旧实例 uploads 目录中的数据复制到新实例中 webservice 组件的 /srv/gitlab/public/uploads 路径。

如何查找节点上 PVC 的挂载路径

按照以下步骤查找 PersistentVolumeClaim (PVC) 在 Kubernetes 节点上的挂载路径:

  1. 获取 Pod 运行所在节点的 IP

    kubectl -n $GITLAB_NAMESPACE get pod <pod-name> -o jsonpath='{.status.hostIP}'
    
    # Output:
    # 192.168.xxx.xxx
  2. 获取与 PVC 绑定的卷名称

    kubectl get pvc -n $GITLAB_NAMESPACE <pvc-name> -o jsonpath='{.spec.volumeName}'
    
    # Output:
    # pvc-359bb8d2-841e-406c-a5e7-e48bc701f610
  3. 查找节点上的 PVC 挂载路径

    登录节点(使用步骤 1 中的节点 IP),然后运行:

    df -h | grep <pv-name>
    
    # Output:
    # 192.168.xx.xx:6789:/volumes/csi/csi-vol-4661bd66-a68f-42f6-b995-53bd5c3f79a8/ede3ffa1-0f77-4f44-b0e6-41ec7384484f   10G  216M  9.8G   3% /var/lib/kubelet/pods/7772096f-9b81-4698-8369-34689e36fc3a/   volumes/kubernetes.io~csi/pvc-359bb8d2-841e-406c-a5e7-e48bc701f610/mount

    最后一列显示的路径,例如:

    /var/lib/kubelet/pods/7772096f-9b81-4698-8369-34689e36fc3a/volumes/kubernetes.io~csi/pvc-359bb8d2-841e-406c-a5e7-e48bc701f610/mount

    即为 PVC 在节点上的挂载目录。


GitLab 是 GitLab Inc. 的商标。

Alauda 是一家独立供应商。本产品与 GitLab Inc. 无关联,也未获得其认可或赞助。所有商标均归其各自所有者所有,此处仅用于标识目的。