从 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。
鉴于版本跨度较大,升级采用数据迁移的方式。#目录
术语流程概述数据迁移路径数据迁移流程前置条件备份platform-deployed GitLab 14.0.12恢复备份数据到 all-in-one 部署的 14.0.12滚动升级 all-in-one 部署的 gitlab 到 17.8.1备份 17.8.1 all-in-one 实例数据将 all-in-one 备份数据恢复到 platform-deployed 17.8.zFAQ恢复上传数据如何查找节点上 PVC 的挂载路径#术语
| 术语 | 说明 |
|---|---|
all-in-one GitLab | 所有 GitLab 组件打包在一个容器或 Pod 中一起部署。用于快速版本升级。 |
platform-deployed GitLab | 由平台通过 Operator 部署和管理的 GitLab,组件分离部署。 |
#流程概述
#数据迁移路径
根据官方升级路径文档,数据迁移路径如下:
- 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 实例。
- 备份
platform-deployedGitLab 14.0.12 实例。 - 将备份恢复到
all-in-one部署的 GitLab 14.0.12。 - 对
all-in-one部署进行滚动升级至 GitLab 17.8.1。 - 备份升级后的
all-in-oneGitLab 17.8.1 实例。 - 将
all-in-one17.8.1 的备份恢复到platform-deployedGitLab 17.8.z 实例。
最新的 GitLab 实例和 operator 版本,请参考Release Note。
迁移过程涉及数据库和仓库的备份/恢复操作:
- 数据库越大,迁移时间越长。
- 仓库数量越多或单个仓库越大,耗时显著增加。
- 存储性能也影响效率,建议使用 topolvm 以获得更好性能。
测试环境:
- 一个大仓库(约 600 MB),其他为小型初始仓库
- GitLab 实例:6,700 个 issue,3,600 个 merge request
| 迁移步骤 | 关键因素 | 1,000 个仓库 | 10,000 个仓库 |
|---|---|---|---|
| 备份 platform-deployed GitLab 14.0.12 | 仓库大小和数量 | 10 分钟 | 30 分钟 |
| 恢复到 all-in-one GitLab 14.0.12 | 仓库大小和数量 | 20 分钟 | 1 小时 |
| 滚动升级到 GitLab 17.8.1 | 数据库大小 | 1 小时 30 分 | 1 小时 40 分 |
| 备份 all-in-one GitLab 17.8.1 | 仓库大小和数量 | 3 分钟 | 10 分钟 |
| 恢复到 platform-deployed GitLab | 仓库大小和数量 | 30 分钟 | 1 小时 |
#前置条件
-
在执行主机安装 kubectl (>=1.30.0) 和 yq (>=4.45.0)。
kubectl version --client yq --version # 输出示例: # Client Version: v1.30.0 # Kustomize Version: v5.4.2 # yq (https://github.com/mikefarah/yq/) version v4.45.4 -
配置环境变量:
# 替换为旧 GitLab CR 的名称 export GITLAB_NAME=<old gitlab name> export GITLAB_NAMESPACE=<gitlab deploy namespace> -
准备升级所需的 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- backup-pvc:用于存储备份包,建议容量为原实例大小的两倍。
- 替换为集群中的存储类名称。
- upgrade-pvc:用于滚动升级过程中存储 GitLab 数据,建议容量为原实例总存储大小加上数据库大小的三倍。
- 替换为集群中的存储类名称。
-
准备升级所需镜像。下载附件中的各版本镜像,并推送到 GitLab 部署集群可拉取的镜像仓库。镜像下载链接:
中国区其他区域# amd 镜像 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 镜像 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# amd 镜像 https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-14-0-12-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-14-3-6-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-14-9-5-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-14-10-5-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-15-0-5-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-15-4-6-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-15-11-13-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-16-3-8-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-16-7-9-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-16-11-10-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-17-3-6-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-17-5-5-amd.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-17-8-1-amd.tgz # arm 镜像 https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-14-0-12-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-14-3-6-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-14-9-5-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-14-10-5-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-15-0-5-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-15-4-6-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-15-11-13-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-16-3-8-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-16-7-9-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-16-11-10-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-17-3-6-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-17-5-5-arm.tgz https://cloud.alauda.io/attachments/knowledge/242090509/gitlab-ce-17-8-1-arm.tgz -
准备升级所需脚本。
执行以下命令生成四个脚本:
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├── check_gitlab.sh
├── finalize_migrations.sql
├── monitor_gitlab.sh
└── rolling_upgrade.sh#备份 platform-deployed GitLab 14.0.12
-
在 GitLab 14 上启用 task-runner 组件以执行备份命令
Platform < 3.18.0 的命令Platform 3.18.0+ 的命令输出# 根据 gitaly 镜像生成 task-runner 的镜像仓库和标签 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 )" # 等待 task-runner 部署可用 echo "Waiting for the task-runner deployment to be available..." sleep 60 # 执行命令并确认 ${GITLAB_NAME}-task-runner 部署存在 kubectl wait deploy ${GITLAB_NAME}-task-runner -n $GITLAB_NAMESPACE --for condition=available --timeout=3000s kubectl get deploy -n $GITLAB_NAMESPACE ${GITLAB_NAME}-task-runnerkubectl patch gitlabofficials.operator.devops.alauda.io ${GITLAB_NAME} -n $GITLAB_NAMESPACE --type='merge' -p=' { "spec": { "helmValues": { "gitlab": { "task-runner": { "enabled": "true", "resources": { "limits": { "cpu": "2", "memory": "4G" } } } } } } }' # 等待 task-runner 部署可用 echo "Waiting for the task-runner deployment to be available..." sleep 60 # 执行命令并确认 ${GITLAB_NAME}-task-runner 部署存在 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# gitlabofficial.operator.devops.alauda.io/xxx patched # Waiting for the task-runner deployment to be available... # deployment.apps/xxx-task-runner condition met # NAME READY UP-TO-DATE AVAILABLE AGE # high-sc-sso-ingress-https-task-runner 1/1 1 1 178m -
设置 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# deployment.apps/gitlab-old-webservice-default patched # deployment.apps/gitlab-old-sidekiq-all-in-1-v1 patched # deployment.apps/gitlab-old-webservice-default scaled # deployment.apps/gitlab-old-sidekiq-all-in-1-v1 scaled -
为 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 # 执行命令并确认 ${GITLAB_NAME}-task-runner 部署存在 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# deployment.apps/xxx-task-runner patched # Waiting for the task-runner deployment to be available... # deployment.apps/gitlab-14-task-runner condition met # NAME READY UP-TO-DATE AVAILABLE AGE # gitlab-14-task-runner 1/1 1 1 4m6s -
备份 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 # 给备份文件添加权限 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 '# 2024-11-01 06:16:06 +0000 -- Dumping database ... # Dumping PostgreSQL database gitlabhq_production ... [DONE] # 2024-11-01 06:16:18 +0000 -- done # .... # done # Deleting old backups ... skipping # Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data # and are not included in this backup. You will need these files to restore a backup. # Please back them up manually. # Backup task is done. # total 300K # -rwxrwxrwx 1 git git 300K Sep 15 09:15 1757927758_2025_09_15_14.0.12_gitlab_backup.tar通过以下命令备份 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 -
备份完成后移除 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}# Waiting for the task-runner deployment to be removed... # No resources found in xxxx namespace.
#恢复备份数据到 all-in-one 部署的 14.0.12
-
使用
all-in-one镜像部署 GitLab 14.0.12。首先设置
NODE_IP环境变量,通过该 IP 和 NodePort 端口访问all-in-oneGitLab 实例。export NODE_IP=<node_ip> export GITLAB_IMAGE=<registry-host>/gitlab/gitlab-ce:14.0.12-ce.0创建
all-in-oneGitLab 实例。命令输出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: # all-in-one gitlab 的外部访问地址,需替换为集群节点的 IP 和 nodeport 端口 - name: GITLAB_OMNIBUS_CONFIG value: external_url 'http://${NODE_IP}:30855' image: ${GITLAB_IMAGE} # 可替换为可拉取的镜像 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# service/gitlab-http-nodeport created # deployment.apps/gitlab created检查
all-in-oneGitLab 实例是否启动成功。命令输出pod_name=$(kubectl get po -l deploy=gitlab -n $GITLAB_NAMESPACE -o jsonpath='{.items[0].metadata.name}') bash check_gitlab.sh $pod_name# 2024-11-01 16:02:47 - Starting monitoring script for GitLab pod: gitlab-7ff8d674bd-m65nz on port: 30855 # 2024-11-01 16:12:59 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:09 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:19 - HTTP not ready. Retrying in 10 seconds... # ... # 2024-11-01 16:13:29 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:40 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:50 - GitLab is ready -
修改
all-in-oneGitLab 的 rails secret 内容。将secret_key_base、otp_key_base、db_key_base、openid_connect_signing_key、ci_jwt_signing_key字段替换为备份的 GitLab 14 rails secret 中对应字段。命令输出# 复制 gitlab-secrets.json 到本地 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 # 替换为 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 # 复制 gitlab-secrets.json 回 pod kubectl -n $GITLAB_NAMESPACE cp all-in-one-gitlab-secrets.json $pod_name:/etc/gitlab/gitlab-secrets.json# tar: Removing leading `/' from member names # Replace secret_key_base ... # Replace otp_key_base ... # Replace db_key_base ... # Replace openid_connect_signing_key ... # Replace ci_jwt_signing_key ...禁用不必要的组件。
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 # 复制 gitlab.rb 到 pod kubectl -n $GITLAB_NAMESPACE cp gitlab.rb $pod_name:/etc/gitlab/gitlab.rb -
同步 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> # 在 all-in-one pod 所在节点执行 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/ # 检查仓库数据所有权 ls -alh ${all_in_one_upgrade_pvc_mount_path}/data/git-data/repositoriesorigin_gitaly_pvc_mount_path=<origin_gitaly_mount_path> origin_gitaly_node_ip=<origin_gitaly_node_ip> all_in_one_upgrade_pvc_mount_path=<all_in_one_gitaly_mount_path> # 在 all-in-one pod 所在节点执行 rsync -avh --chown=998:998 ${origin_gitaly_node_ip}:${origin_gitaly_pvc_mount_path}/ ${all_in_one_upgrade_pvc_mount_path}/data/git-data/repositories/ # 检查仓库数据所有权 ls -alh ${all_in_one_upgrade_pvc_mount_path}/data/git-data/repositories重启 pod 并等待启动。
命令输出# 重启 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# pod "gitlab-xxxx" deleted # Waiting for the pod to start... # 2024-11-01 16:02:47 - Starting monitoring script for GitLab pod: gitlab-7ff8d674bd-m65nz on port: 30855 # 2024-11-01 16:12:59 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:09 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:19 - HTTP not ready. Retrying in 10 seconds... # ... # 2024-11-01 16:13:29 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:40 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:50 - GitLab is ready -
恢复备份数据。
TIP恢复过程中可能出现数据库相关错误,例如:
ERROR: role "xxx" does not existERROR: function xxx does not existERROR: permission denied to create extension "pg_stat_statements"ERROR: could not open extension control file
这些错误可忽略,详情见 (https://gitlab.com/gitlab-org/gitlab/-/issues/266988)。
命令输出# 设置 gitlab 为只读 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 # 确认 puma 和 sidekiq 已停止 kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- gitlab-ctl status # 执行恢复 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 "此步骤会多次提示是否继续,请每次选择“yes”以继续。" kubectl -n $GITLAB_NAMESPACE exec -ti $pod_name -- bash -c 'gitlab-backup restore BACKUP=${backup_id}'# ok: down: puma: 666s, normally up # ok: down: sidekiq: 660s, normally up # ... # run: prometheus: (pid 1142) 1043s; run: log: (pid 680) 1092s # down: puma: 695s, normally up; run: log: (pid 536) 1128s # down: sidekiq: 687s, normally up; run: log: (pid 551) 1124s # run: sshd: (pid 42) 1171s; run: log: (pid 41) 1171s # command terminated with exit code 6 # ... # This task will now rebuild the authorized_keys file. # You will lose any data stored in the authorized_keys file. # Do you want to continue (yes/no)? yes # # Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data # and are not included in this backup. You will need to restore these files manually. # Restore task is done.重启 pod 并等待启动,然后执行
gitlab-rake gitlab:check SANITIZE=true检查 GitLab 完整性。命令输出# 重启 pod kubectl delete po -l deploy=gitlab -n $GITLAB_NAMESPACE # 等待 pod 启动 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# pod "gitlab-xxxx" deleted # 2024-11-01 16:02:47 - Starting monitoring script for GitLab pod: gitlab-7ff8d674bd-m65nz on port: 30855 # 2024-11-01 16:12:59 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:09 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:19 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:29 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:40 - HTTP not ready. Retrying in 10 seconds... # 2024-11-01 16:13:50 - GitLab is ready # ... # GitLab configured to store new projects in hashed storage? ... yes # All projects are in hashed storage? ... yes # Checking GitLab App ... Finished # Checking GitLab subtasks ... Finished登录 GitLab 检查实例恢复是否成功(root 用户密码与原实例相同),并执行以下命令获取
all-in-one实例的访问地址。命令输出kubectl -n $GITLAB_NAMESPACE get pod -l deploy=gitlab -o yaml | grep external_url# value: external_url 'http://192.168.xxx.xxx:30855'
#滚动升级 all-in-one 部署的 gitlab 到 17.8.1
执行 rolling_upgrade.sh 脚本,按照升级路径逐步升级 all-in-one 实例到 17.8.1 版本。
bash -x rolling_upgrade.sh滚动升级过程中数据库可能会自动重启,脚本可能出现如下错误,这是正常现象,脚本会自动重试并继续执行。
connections on Unix domain socket "/var/opt/gitlab/postgresql/.s.PGSQL.5432"
脚本执行完成后,进入 GitLab Web UI 检查实例版本是否为 17.8.1,并验证数据完整性,包括代码仓库、用户、issue、merge request 等。
#备份 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
'# ok: down: puma: 0s, normally up
# ok: down: sidekiq: 0s, normally up
# ...
# 2025-09-15 03:24:37 UTC -- Deleting backups/tmp ...
# 2025-09-15 03:24:37 UTC -- Deleting backups/tmp ... done
# 2025-09-15 03:24:37 UTC -- Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data
# and are not included in this backup. You will need these files to restore a backup.
# Please back them up manually.
# 2025-09-15 03:24:37 UTC -- Backup 1757906670_2025_09_15_17.8.1 is done.
# 2025-09-15 03:24:37 UTC -- Deleting backup and restore PID file at [/opt/gitlab/embedded/service/gitlab-rails/tmp/backup_restore.pid] ... done停止 all-in-one 实例服务。
kubectl scale deploy gitlab -n $GITLAB_NAMESPACE --replicas 0# deployment.apps/gitlab scaled#将 all-in-one 备份数据恢复到 platform-deployed 17.8.z
新 gitlab 实例的命名空间必须与旧实例命名空间相同。
-
参考GitLab 部署指南使用 operator 部署新的 GitLab 17.8.z 实例。
Tip- 新部署的实例必须与旧实例在同一命名空间
- 新实例的访问方式不得与旧实例冲突,如域名、nodeport 等
新实例部署完成后,设置新实例名称的环境变量:
export NEW_GITLAB_NAME=<name of the new GitLab instance> -
为新 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# NAME READY STATUS RESTARTS AGE # xx-gitlab-toolbox-xxxx-xxxx-xxxx-xxxx 1/1 Running 0 178m -
将备份数据恢复到 operator 部署的 GitLab 17.8.z
TIP恢复过程中可能出现数据库相关错误,例如:
ERROR: role "xxx" does not existERROR: function xxx does not existERROR: 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 # 修改 toolbox,挂载 backup pvc 到 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"}}}]'# deployment.apps/xxxx-xxxx-toolbox patched等待 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}') # 备份旧备份文件 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}"# Backup file of 17.8.1: 1757906670_2025_09_15_17.8.1_gitlab_backup.tar # 2025-09-15 06:59:21 UTC -- Restoring repositories ... done # 2025-09-15 06:59:21 UTC -- Deleting backup and restore PID file at [/srv/gitlab/tmp/backup_restore.pid] ... done # 2025-09-15 06:59:41 UTC -- Restoring builds ... # 2025-09-15 06:59:41 UTC -- Restoring builds ... done # 2025-09-15 06:59:41 UTC -- Deleting backup and restore PID file at [/srv/gitlab/tmp/backup_restore.pid] ... done # Backup tarball not from a Helm chart based installation. Not processing files in object storage. -
替换新 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# secret/rails-secret created # gitlabofficial.operator.alaudadevops.io/xxxx-xxxx patchedoperator 会自动重新部署新 GitLab 实例,等待实例完成重新部署。
-
验证升级后数据是否正常。
- 进入 GitLab UI 的管理员视图,检查仓库和用户数据是否正常。
- 选择部分仓库,验证代码、分支和 merge request 是否正常。
-
清理未使用资源:
- 如有需要,删除旧 GitLab 实例和旧 operator。
- 如有需要,删除
all-in-oneGitLab 实例。
#FAQ
#恢复上传数据
迁移的数据不包含头像和附件,导致新 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.devops.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 的挂载路径
按照以下步骤查找 Kubernetes 节点上 PersistentVolumeClaim (PVC) 的挂载路径:
-
获取 Pod 所在节点 IP
kubectl -n $GITLAB_NAMESPACE get pod <pod-name> -o jsonpath='{.status.hostIP}' # 输出示例: # 192.168.xxx.xxx -
获取绑定到 PVC 的卷名称
kubectl get pvc -n $GITLAB_NAMESPACE <pvc-name> -o jsonpath='{.spec.volumeName}' # 输出示例: # pvc-359bb8d2-841e-406c-a5e7-e48bc701f610 -
查找节点上 PVC 的挂载路径
登录节点(使用步骤 1 的节点 IP),执行:
df -h | grep <pv-name> # 输出示例: # 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 在节点上的挂载目录。