GitLab 备份与 NFS 异地存储实施笔记
前言
- 当前环境:GitLab 部署在 Docker 容器中,宿主机磁盘空间紧张(
/dev/sda2仅剩 40GB)。 - 目标:将所有 GitLab 备份文件自动存储到 98TB 的 NFS 异地存储中,实现数据与系统分离。
- 存储位置:
192.168.111.1:/volume1/git14挂载至宿主机/gitlab_bak/。
存储基础设施配置
NFS 挂载
在宿主机执行挂载,并配置开机自动挂载:
# 创建本地挂载点
mkdir -p /gitlab_bak/
# 临时挂载
mount 192.168.111.1:/volume1/git14 /gitlab_bak/
# 配置持久化挂载 (编辑 /etc/fstab)
echo "192.168.111.1:/volume1/git14 /gitlab_bak/ nfs defaults,nfsvers=3 0 0" >> /etc/fstab
目录与权限初始化
注意:必须确保 NFS 目录对容器内的 git 用户(UID 998)有写入权限。
# 创建备份子目录
mkdir -p /gitlab_bak/backups
mkdir -p /gitlab_bak/config_bak
# 修正权限 (998 是 GitLab 容器内 git 用户的默认 UID)
chown -R 998:998 /gitlab_bak/backups
chmod 755 /gitlab_bak/backups
GitLab 容器部署与配置
容器启动命令
关键点:使用 -v 直接挂载 NFS 路径到容器内部,替代不可靠的宿主机软链接方案。
docker run -d -p 443:443 -p 9980:80 -p 9922:22 \
--name gitlab_new --restart always \
-e TZ=Asia/Shanghai \
-v /etc/localtime:/etc/localtime:ro \
-v /home/gzeport/gitlab/config:/etc/gitlab \
-v /home/gzeport/gitlab/logs:/var/log/gitlab \
-v /home/gzeport/gitlab/data:/var/opt/gitlab \
-v /gitlab_bak/backups:/var/opt/gitlab/backups \
docker.ajunyunwei.xyz/gitlab/gitlab-ce:18.2.1-ce.0
规避 NFS 权限报错 (Errno::EPERM)
若 GitLab 在 reconfigure 时尝试 chown NFS 目录会报错,需在 gitlab.rb 中禁用该行为:
# 编辑配置文件
vim /home/gzeport/gitlab/config/gitlab.rb
# 添加以下配置
gitlab_rails['manage_backup_path'] = false
gitlab_rails['backup_keep_time'] = 604800 # 自动清理 7 天前的数据备份
保存后,重启容器应用配置:docker restart gitlab_new。
自动化备份实施
核心备份脚本
在 /home/gzeport/scripts/gitlab_backup.sh 创建以下脚本。
#!/bin/bash
# GitLab 自动备份脚本
# Crontab: 0 2 * * * /bin/bash /home/gzeport/scripts/gitlab_backup.sh > /var/log/gitlab_bak.log 2>&1
set -e # 遇到错误立即退出
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
log "========== GitLab 备份开始 =========="
# 1. 检查 NFS 挂载状态
if ! mountpoint -q /gitlab_bak; then
log "错误: /gitlab_bak 未挂载,尝试挂载..."
mount 192.168.111.1:/volume1/git14 /gitlab_bak || {
log "错误: NFS 挂载失败,备份终止"
exit 1
}
log "NFS 挂载成功"
fi
# 2. 检查容器运行状态
if ! docker ps | grep -q gitlab_new; then
log "错误: gitlab_new 容器未运行"
exit 1
fi
# 3. 执行 GitLab 内部备份程序
log "开始执行 GitLab 数据备份..."
if docker exec gitlab_new gitlab-backup create; then
log "GitLab 数据备份成功"
else
log "错误: GitLab 数据备份失败"
exit 1
fi
# 4. 【非常重要】手动备份配置文件和密钥(这些不在 tar 包里,但恢复必须有它们)
BACKUP_DATE=$(date +%Y%m%d)
CONFIG_BACKUP_DIR="/gitlab_bak/config_bak/$BACKUP_DATE"
log "开始备份配置文件到 $CONFIG_BACKUP_DIR"
mkdir -p "$CONFIG_BACKUP_DIR"
# 备份配置文件
if [ -f /home/gzeport/gitlab/config/gitlab.rb ]; then
cp /home/gzeport/gitlab/config/gitlab.rb "$CONFIG_BACKUP_DIR/"
log "gitlab.rb 备份成功"
else
log "警告: gitlab.rb 文件不存在"
fi
if [ -f /home/gzeport/gitlab/config/gitlab-secrets.json ]; then
cp /home/gzeport/gitlab/config/gitlab-secrets.json "$CONFIG_BACKUP_DIR/"
log "gitlab-secrets.json 备份成功"
else
log "警告: gitlab-secrets.json 文件不存在"
fi
# 5. 删除 30 天前的旧配置备份(与 GitLab 数据备份保留策略一致)
log "清理 30 天前的旧配置备份..."
find /gitlab_bak/config_bak/ -maxdepth 1 -type d -mtime +30 ! -path /gitlab_bak/config_bak/ -exec rm -rf {} \;
log "旧配置备份清理完成"
# GitLab 数据备份(.tar)由 GitLab 自动清理(配置: backup_keep_time = 604800 即 7 天)
# 6. 显示备份统计信息
BACKUP_COUNT=$(ls -1 /gitlab_bak/backups/*.tar 2>/dev/null | wc -l)
CONFIG_COUNT=$(ls -1d /gitlab_bak/config_bak/*/ 2>/dev/null | wc -l)
log "当前保留备份: 数据备份 $BACKUP_COUNT 个, 配置备份 $CONFIG_COUNT 个"
log "========== GitLab 备份完成 =========="

设置定时任务
# 每天凌晨 2:00 执行
crontab -e
0 2 * * * /bin/bash /home/gzeport/scripts/gitlab_backup.sh > /var/log/gitlab_bak.log 2>&1
运维避坑指南
- 为什么不能用软链接?
Docker 挂载的是宿主机路径,无法识别指向宿主机其他位置的软链接。必须使用
-v直接挂载真实路径。 -
为什么一定要备份
gitlab-secrets.json?.tar数据包中不包含密钥。如果密钥丢失,备份中的数据库即便恢复,也无法解密用户密码、CI/CD 变量等,整个 GitLab 将处于损坏状态。 -
权限问题排查: 如果备份报错
Permission Denied,第一时间在宿主机检查chown -R 998:998 /gitlab_bak/backups。