GitLab 双物理机高可用新方案(基于 Rsyncd + Keepalived+PostgreSQL 流复制)

目录

环境准备

主机信息

[设置主机名与 hosts(两台均执行)](#设置主机名与 hosts(两台均执行))

主节点(192.168.252.102)

备节点(192.168.252.103)

[安装 GitLab 和相关依赖(两台都装)](#安装 GitLab 和相关依赖(两台都装))

[备节点禁止 GitLab 自动启动](#备节点禁止 GitLab 自动启动)

[配置 SSH 免密登录](#配置 SSH 免密登录)

主节点配置免密登录备节点

备节点配置免密登录主节点

[PostgreSQL 流复制](#PostgreSQL 流复制)

[修改主节点 gitlab.rb 配置](#修改主节点 gitlab.rb 配置)

应用配置并重启(主节点)

[备节点 GitLab 配置(设置为流复制从库)](#备节点 GitLab 配置(设置为流复制从库))

[修改备节点 gitlab.rb 配置](#修改备节点 gitlab.rb 配置)

[停止备节点 GitLab 服务](#停止备节点 GitLab 服务)

[在备节点启动 PostgreSQL 为从库](#在备节点启动 PostgreSQL 为从库)

文件同步

方式1:rsync(推荐)

创建同步脚本

添加定时任务(主节点)

[方式2:配置 lsyncd(与 rsync 二选一即可)](#方式2:配置 lsyncd(与 rsync 二选一即可))

创建配置文件

[启动 lsyncd](#启动 lsyncd)

[验证 lsyncd 运行状态](#验证 lsyncd 运行状态)

文件一致性抽验(推荐)

功能性验证(最可靠)

关键排除项确认

健康检查脚本

[配置 Keepalived](#配置 Keepalived)

主节点(192.168.252.102)

备节点(192.168.252.103)

[启动 Keepalived(两台)](#启动 Keepalived(两台))

[允许 VRRP 协议入站](#允许 VRRP 协议入站)

[验证 VIP(主节点应有)](#验证 VIP(主节点应有))

核心脚本

takeover.sh(备节点使用)

创建脚本文件

脚本内容

demote_to_standby.sh(备节点使用)

强制全量同步脚本(首次启动后对齐数据)

调试与验证

[GitLab 服务控制](#GitLab 服务控制)

查看初始密码(主节点)

[Keepalived 控制](#Keepalived 控制)

[验证 VIP 是否生效](#验证 VIP 是否生效)

手动触发故障切换测试

注意事项与限制

[GitLab 高可用(HA)运维手册](#GitLab 高可用(HA)运维手册)

日常检查

测试主机宕机(模拟故障切换)

操作步骤(原主机执行)

验证(从机执行)

主机宕机恢复(从从机备份还原)

第一步:在从机(当前主)执行备份

第二步:在原主机(待恢复)准备还原

第三步:在原主机执行数据还原(跳过配置文件)

第四步:主机验证并恢复高可用

[手动恢复从机身份(降级为 standby)](#手动恢复从机身份(降级为 standby))

最终验证(原主机执行)

关键注意事项


本方案通过在两台物理服务器部署 GitLab,结合 Rsyncd 实现实时文件同步、Keepalived 实现虚拟 IP(VIP)漂移和主备自动切换,搭建高可用(HA)GitLab 服务架构。

  • 主节点(gitlab-primary):正常运行 GitLab 服务,对外提供访问。
  • 备节点(gitlab-standby):实时同步主节点配置与数据,待命状态;主节点故障时,自动接管 VIP 并启动 GitLab 服务。
  • 共享 VIP:192.168.252.140/24,客户端始终通过该地址访问 GitLab。

注意:本方案适用于 GitLab Omnibus 安装方式(官方包安装),不适用于源码或 Docker 部署。

环境准备

主机信息

角色 主机名 IP 地址 操作系统
主节点 gitlab-primary 192.168.252.102 Ubuntu 24+
备节点 gitlab-standby 192.168.252.103 同主节点

文档中部分命令示例使用 192.168.252.150​ 作为主节点 IP,需根据实际环境替换为 192.168.252.102​。

设置主机名与 hosts(两台均执行)

主节点(192.168.252.102)
复制代码
sudo hostnamectl set-hostname gitlab-primary
echo "192.168.252.102 gitlab-primary" | sudo tee -a /etc/hosts
echo "192.168.252.103 gitlab-standby" | sudo tee -a /etc/hosts
备节点(192.168.252.103)
复制代码
sudo hostnamectl set-hostname gitlab-standby
echo "192.168.252.102 gitlab-primary" | sudo tee -a /etc/hosts
echo "192.168.252.103 gitlab-standby" | sudo tee -a /etc/hosts

使用 ip a​ 确认网卡名称(如 ens33、eth0、enp0s3 等),后续 Keepalived 配置需用到。

安装 GitLab 和相关依赖(两台都装)

复制代码
# 安装依赖
sudo apt update && sudo apt install -y lsyncd keepalived rsync
​
# 添加官方源(清华镜像)
rm -rf /usr/share/keyrings/gitlab_gitlab-ce-archive-keyring.gpg
curl -fsSL https://packages.gitlab.com/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/gitlab_gitlab-ce-archive-keyring.gpg
rm -rf /etc/apt/sources.list.d/gitlab_gitlab-ce.list
echo "deb [signed-by=/usr/share/keyrings/gitlab_gitlab-ce-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/noble noble main" | sudo tee /etc/apt/sources.list.d/gitlab-ce.list
​
# 安装 GitLab CE
sudo apt update && sudo apt install -y gitlab-ce

仅在主节点初始化(192.168.252.102 上运行):

复制代码
gitlab-ctl reconfigure

不要在 192.168.252.103 上运行 reconfigure​,先保持未初始化状态。

备节点禁止 GitLab 自动启动
复制代码
gitlab-ctl stop
systemctl disable gitlab-runsvdir

配置 SSH 免密登录

主节点配置免密登录备节点

复制代码
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
ssh-copy-id gitlab-standby

验证命令:

复制代码
ssh 'gitlab-standby'

备节点配置免密登录主节点

复制代码
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
ssh-copy-id gitlab-primary

PostgreSQL 流复制

修改主节点 gitlab.rb 配置

编辑 /etc/gitlab/gitlab.rb​,添加以下配置:

复制代码
# 基础配置
external_url 'http://192.168.252.140'
​
# 允许数据库监听物理网卡 IP 以供备机连接
postgresql['listen_address'] = '127.0.0.1,192.168.252.150'
​
# 配置流复制专用虚拟用户及密码
postgresql['sql_replication_user'] = "gitlab_replica"
postgresql['sql_replication_password'] = "SecureReplicaPass123!"
​
# 设置认证策略:仅允许备机 IP 通过 md5 方式进行 replication 连接
postgresql['pg_hba_replication'] = [
  "host replication gitlab_replica 192.168.252.103/32 md5"
]
​
# 允许内网网段访问
postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32', '192.168.252.0/24']
​
# 关键同步参数:设置日志级别为 replica 并预留发送槽位
postgresql['wal_level'] = "replica"
postgresql['max_wal_senders'] = 10
postgresql['max_replication_slots'] = 10

应用配置并重启(主节点)

复制代码
sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart postgresql

备节点 GitLab 配置(设置为流复制从库)

修改备节点 gitlab.rb 配置

编辑 /etc/gitlab/gitlab.rb​:

复制代码
# 基础配置(与主节点保持一致)
external_url 'http://192.168.252.140'
停止备节点 GitLab 服务
复制代码
sudo gitlab-ctl stop
在备节点启动 PostgreSQL 为从库
复制代码
/opt/gitlab-ha/demote_to_standby.sh
# 验证复制状态
sudo gitlab-psql -c "SELECT * FROM pg_stat_replication;"

成功标志:disabled,表示当前为只读从库。

文件同步

方式1:rsync(推荐)

创建同步脚本
复制代码
touch /opt/gitlab-ha/sync-gitlab.sh;chmod  +x /opt/gitlab-ha/sync-gitlab.sh

脚本内容:

复制代码
#!/bin/bash
# 关键部分注释:针对 GitLab 高性能热备数据同步脚本
# 行为:仅当 GitLab 存活时执行同步;否则跳过,不报错。
​
set -e
​
REMOTE_IP="192.168.252.103"
LOG_FILE="/var/log/gitlab_data_rsync.log"
​
log_info() {
    local msg="$1"
    printf "%s [INFO] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$msg" | tee -a "$LOG_FILE"
}
​
# 返回 0 表示 GitLab 存活,1 表示不存活
is_gitlab_alive() {
    # 检查 gitlab-ctl status 是否所有关键服务都在运行
    if ! output=$(gitlab-ctl status 2>&1); then
        log_info "GitLab 状态检查命令失败,判定为不存活。"
        return 1
    fi
​
    # 检查是否有任何服务处于 stopped 或 fail 状态
    if echo "$output" | grep -qE "(down|fail|stop)"; then
        log_info "检测到 GitLab 有服务未运行,判定为不存活。"
        return 1
    fi
​
    # 进一步确认关键进程存在(如 puma、sidekiq)
    if ! echo "$output" | grep -q "run: puma:" || ! echo "$output" | grep -q "run: sidekiq:"; then
        log_info "关键服务(puma 或 sidekiq)未运行,判定为不存活。"
        return 1
    fi
​
    return 0
}
# 加载钉钉发送函数
source /opt/gitlab-ha/send_dingtalk.sh
​
report_stats() {
    local stats_output="$1"
    local duration_sec="$2"  # 接收耗时(秒)
​
    local files_transferred=$(echo "$stats_output" | grep "Number of regular files transferred" | awk -F': ' '{print $2}' | xargs)
    local total_size=$(echo "$stats_output" | grep "total size is" | awk '{print $4}')
    local sent_size=$(echo "$stats_output" | grep -E "^sent " | awk '{print $2}')
​
    # 格式化耗时为"X分Y秒"
    local minutes=$((duration_sec / 60))
    local seconds=$((duration_sec % 60))
    local duration_str="${minutes}分${seconds}秒"
​
    log_info "指标汇总 | 传输文件: ${files_transferred:-0} | 数据总量: ${total_size:-0} | 实际发送: ${sent_size:-0} | 耗时: ${duration_str}"
​
    send_dingtalk_message "[$(date '+%Y-%m-%d %H:%M:%S')] 主机已完成Gitlab数据同步(不含postgresql数据库),指标汇总:
- 传输文件: ${files_transferred:-0}
- 数据总量: ${total_size:-0}
- 实际发送: ${sent_size:-0}
- 同步耗时: ${duration_str}"
}
​
sync_data_volumes() {
    log_info "开始同步 GitLab 静态数据..."
    local start_ts=$SECONDS
    local tmp_output=$(mktemp)
​
    rsync -aH -h --delete --numeric-ids --inplace --sparse --stats --outbuf=L \
        --exclude='postgresql/' \
        --exclude='redis/' \
        --exclude='*.sock' \
        --exclude='gitlab.rb' \
        /var/opt/gitlab/ "${REMOTE_IP}:/var/opt/gitlab/" 2>&1 | tee "$tmp_output"
​
    local result=$(cat "$tmp_output")
    rm -f "$tmp_output"
    
    local end_ts=$SECONDS
    local duration=$((end_ts - start_ts))
​
    report_stats "$result" "$duration"
    log_info "耗时统计: $(( duration / 60 ))分$(( duration % 60 ))秒"
}
​
# ===== 主流程 =====
log_info "=========================================="
log_info "启动 GitLab 热备数据同步任务..."
​
if is_gitlab_alive; then
    log_info "GitLab 存活,执行数据同步。"
    sync_data_volumes
    log_info "同步任务完成"
else
    log_info "GitLab 未存活,跳过本次同步。"
fi
​
log_info "=========================================="
添加定时任务(主节点)
复制代码
root@gitlab-primary:/opt/gitlab-ha# crontab -l
*/10 * * * * /bin/bash /opt/gitlab-ha/sync-gitlab.sh >> /var/log/gitlab_rsync.log 2>&1

方式2:配置 lsyncd(与 rsync 二选一即可)

创建配置文件
复制代码
mkdir -p /etc/lsyncd
cat > /etc/lsyncd/lsyncd.conf.lua << 'EOF'
-- /etc/lsyncd/lsyncd.conf.lua
-- 针对 32核/64GB 配置的性能调优版
settings {
    logfile      = "/var/log/lsyncd/lsyncd.log",
    statusFile   = "/var/log/lsyncd/lsyncd.status",
    inotifyMode  = "CloseWrite",
    -- 充分利用 32 核 CPU,并发进程数上调
    maxProcesses = 16,
    -- 聚合 2000 个事件或等待 3 秒后触发同步,减少 SSD 小碎文件频繁 I/O
    maxDelays    = 2000,
    delay        = 3,
}
​
-- 同步配置目录
sync {
    default.rsync,
    source = "/etc/gitlab/",
    target = "192.168.252.103:/etc/gitlab/",
    rsync = {
        archive  = true,
        compress = false -- SSD 环境下,局域网内可关闭压缩以节省 CPU
    }
}
​
-- 同步数据目录
sync {
    default.rsync,
    source = "/var/opt/gitlab/",
    target = "192.168.252.103:/var/opt/gitlab/",
    rsync = {
        archive  = true,
        compress = false,
        -- 核心优化:排除 pgsql 物理目录以防数据库损坏
        exclude  = {
            "postgresql/",
            "redis/",
            "**/*.sock",
            "log/",
            "tmp/",
            "gitlab-rails/working/",
            "nginx/proxy_cache/"
        },
        -- 针对 SSD 优化:跳过对未改变文件的校验,提升速度
        _extra = { "--inplace", "--numeric-ids" }
    }
}
EOF
启动 lsyncd
复制代码
systemctl enable --now lsyncd
验证 lsyncd 运行状态
  1. 查看 lsyncd 运行状态

    systemctl status lsyncd

  2. 监控实时同步日志

    tail -f /var/log/lsyncd/lsyncd.log

  3. 查看同步统计概览

    cat /var/log/lsyncd/lsyncd.status

文件一致性抽验(推荐)
  1. 比对配置文件(主备机分别执行)

    md5sum /etc/gitlab/gitlab.rb

  2. 比对 Git 仓库大小(主备机分别执行)

    du -sh /var/opt/gitlab/git-data/repositories

功能性验证(最可靠)
  1. 新建测试项目

    • 在主节点(192.168.252.150)网页端创建新项目 sync-test

    • 等待 3-5 秒(对应 lsyncd 配置的 delay = 3)

    • 在备节点(192.168.252.103)查看项目是否存在:

      ls -lh /var/opt/gitlab/git-data/repositories/@hashed/ | grep [项目哈希相关目录]

  2. 推送代码测试

    • 向主节点推送较大 Commit
    • 在备节点观察 /var/log/lsyncd/lsyncd.log,确认有 rsync 任务产生
关键排除项确认

检查备机数据库文件时间戳,确认 postgresql/ 目录未被同步:

复制代码
# 在备机执行
ls -ld /var/opt/gitlab/postgresql/data

若文件修改时间停留在最后一次手动执行 force-full-sync.sh​ 的时间,说明 exclude 配置生效。

健康检查脚本

复制代码
mkdir -p /opt/gitlab-ha
cat > /opt/gitlab-ha/check_gitlab_health.sh << 'EOF'
#!/bin/bash
# /opt/gitlab-ha/check_gitlab_health.sh
​
# 日志文件路径
LOG_FILE="/var/log/gitlab-health-check.log"
# 最大日志文件大小,单位:字节(此处为 5MB)
MAX_LOG_SIZE=5242880
# 用于 syslog 的标签
LOG_TAG="gitlab-health-check"
​
# 日志记录函数:同时输出到终端和日志文件,并自动限制日志大小
log() {
    local msg="[ $(date '+%Y-%m-%d %H:%M:%S') ] $1"
    echo "$msg"  # 输出到标准输出(便于手动运行时查看)
​
    # 检查日志文件是否存在且超过最大允许大小
    if [[ -f "$LOG_FILE" ]] && [[ $(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0) -gt $MAX_LOG_SIZE ]]; then
        # 清空日志文件,并记录轮转信息
        echo "[ $(date '+%Y-%m-%d %H:%M:%S') ] Log file exceeded ${MAX_LOG_SIZE} bytes, truncating old log." > "$LOG_FILE"
    fi
​
    # 追加当前日志到文件
    echo "$msg" >> "$LOG_FILE"
    # 可选:同时写入系统日志(如 /var/log/syslog),失败则忽略
    logger -t "$LOG_TAG" "$msg" 2>/dev/null || true
}
​
log "开始检查 GitLab 核心服务状态..."
​
# 检查 PostgreSQL 服务是否运行
if ! /usr/bin/gitlab-ctl status postgresql | grep -q "^run:"; then
    log "检查失败:postgresql 未运行"
    exit 1
else
    log "检查通过:postgresql 正常运行"
fi
​
# 检查 Puma(GitLab Web 服务)是否运行
if ! /usr/bin/gitlab-ctl status puma | grep -q "^run:"; then
    log "检查失败:puma 未运行"
    exit 1
else
    log "检查通过:puma 正常运行"
fi
​
# 检查 Nginx(前端反向代理)是否运行(可选但推荐)
if ! /usr/bin/gitlab-ctl status nginx | grep -q "^run:"; then
    log "检查失败:nginx 未运行"
    exit 1
else
    log "检查通过:nginx 正常运行"
fi
​
log "所有核心组件均正常,GitLab 服务健康"
exit 0
EOF
​
chmod +x /opt/gitlab-ha/check_gitlab_health.sh

配置 Keepalived

主节点(192.168.252.102)

编辑 /etc/keepalived/keepalived.conf​(将 ens5f3 替换为实际网卡名):

复制代码
vrrp_script chk_gitlab {
    script "/opt/gitlab-ha/check_gitlab_health.sh"
    interval 10
    weight -20         
    fall 2            
    rise 1           
}
​
vrrp_instance VI_1 {
    state MASTER
    interface ens5f3                   
    virtual_router_id 51
    priority 100
    advert_int 2
    nopreempt                    # 禁止自动抢占
    authentication {
        auth_type PASS
        auth_pass GitLabHA2026!
    }
    virtual_ipaddress {
        192.168.252.140/24
    }
    track_script {
        chk_gitlab
    }
}

备节点(192.168.252.103)

编辑 /etc/keepalived/keepalived.conf​(将 ens5f3 替换为实际网卡名):

复制代码
vrrp_instance VI_1 {
    state BACKUP
    interface ens5f3
    virtual_router_id 51
    priority 90          # 确保 90 大于主机降级后的 80 (100-20)
    advert_int 2
    nopreempt          # 建议开启非抢占模式,防止主机重启后立刻抢回
    
    # 备机不要 track_script,否则优先级会一直处于 90-20=70 的状态
    # track_script {
    #    chk_gitlab
    # }
    # 必须添加认证,否则无法接收主机的 VRRP 信号
    authentication {
        auth_type PASS
        auth_pass GitLabHA2026!
    }
​
    virtual_ipaddress {
        192.168.252.140/24
    }
​
    notify_master "/opt/gitlab-ha/takeover.sh"
    notify_backup "/opt/gitlab-ha/demote_to_standby.sh"
}

启动 Keepalived(两台)

复制代码
sudo systemctl enable --now keepalived

允许 VRRP 协议入站

复制代码
sudo iptables -A INPUT -p 112 -j ACCEPT
# 保存规则(Ubuntu 24.04)
sudo apt install -y iptables-persistent
sudo netfilter-persistent save

验证 VIP(主节点应有)

复制代码
ip addr show ens5f3 | grep 192.168.252.140

核心脚本

takeover.sh(备节点使用)

创建脚本文件
复制代码
cd /opt/gitlab-ha
touch takeover.sh
chmod +x takeover.sh
脚本内容
复制代码
#!/bin/bash
# 函数注释:当 Keepalived 切换至 MASTER 状态时,自动提升 PostgreSQL 并启动 GitLab 业务
# 修复说明:确保数据库在运行状态下接收 promote 信号,避免 PID 缺失错误
# 增强:增加 GitLab 服务健康检查,失败时自动重试 reconfigure + restart
​
LOG_FILE="/var/log/gitlab-ha.log"
DATA_DIR="/var/opt/gitlab/postgresql/data"
MAX_RETRIES=3
HEALTH_CHECK_URL="http://localhost/-/health"
​
# 加载钉钉发送函数
source /opt/gitlab-ha/send_dingtalk.sh
send_dingtalk_message "警告:从机正在自动提权为主机,请检查主机!!!"
​
echo "$(date): [MASTER] VIP detected, initiating promotion..." >> "$LOG_FILE"
​
# 1. 检查数据目录
if [ ! -d "$DATA_DIR" ]; then
    echo "$(date): ERROR - PostgreSQL data directory not found!" >> "$LOG_FILE"
    exit 1
fi
​
# 2. 确保数据库正在运行,以便接收提升信号
if ! sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_ctl -D "$DATA_DIR" status > /dev/null 2>&1; then
    echo "$(date): Postgres is NOT running. Cleaning up and starting..." >> "$LOG_FILE"
    rm -f "$DATA_DIR/postmaster.pid"
    gitlab-ctl start postgresql
    sleep 5  # 给予更充分的启动时间
fi
​
# 3. 执行提升操作
echo "$(date): Executing pg_ctl promote..." >> "$LOG_FILE"
sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_ctl -D "$DATA_DIR" promote >> "$LOG_FILE" 2>&1
​
# 等待几秒让 promote 生效
sleep 5
​
# 4. 启动/刷新 GitLab 全量服务
echo "$(date): Reconfiguring and starting GitLab services..." >> "$LOG_FILE"
gitlab-ctl reconfigure >> "$LOG_FILE" 2>&1
gitlab-ctl restart >> "$LOG_FILE" 2>&1
​
# 5. 健康检查 + 自动重试
attempt=0
while [ $attempt -lt $MAX_RETRIES ]; do
    echo "$(date): Performing health check (attempt $((attempt + 1))/$MAX_RETRIES)..." >> "$LOG_FILE"
    
    # 使用 curl 检查健康端点(GitLab 14+ 支持 /-/health)
    if curl -sf --max-time 30 "$HEALTH_CHECK_URL" > /dev/null 2>&1; then
        echo "$(date): [SUCCESS] GitLab is now active and writable." >> "$LOG_FILE"
        exit 0
    else
        echo "$(date): Health check FAILED. Retrying after reconfigure + restart..." >> "$LOG_FILE"
        ((attempt++))
        
        if [ $attempt -lt $MAX_RETRIES ]; then
            gitlab-ctl reconfigure >> "$LOG_FILE" 2>&1
            gitlab-ctl restart >> "$LOG_FILE" 2>&1
            sleep 30  # 等待服务稳定
        fi
    fi
done
​
# 如果所有重试都失败
echo "$(date): [CRITICAL] GitLab failed to become healthy after $MAX_RETRIES attempts." >> "$LOG_FILE"
​
exit 1

说明:虽然脚本名为 takeover.sh,但在当前配置中,主节点的 notify_master 会触发此脚本。实际上更常用于备机接管场景。若主节点重启后重新成为 MASTER,此脚本也会运行,但因服务已运行,影响有限。

demote_to_standby.sh(备节点使用)

复制代码
#!/bin/bash
# /opt/gitlab-ha/demote_to_standby.sh
#
# 功能:将本机 GitLab PostgreSQL 降级为从库
# 默认模式:仅当本机持有 VIP (192.168.252.140) 时执行
# 强制模式:添加 --force 参数可跳过 VIP 检查(用于手动维护)
​
set -euo pipefail  # 安全模式:出错即退出
​
VIP="192.168.252.140"
INTERFACE="ens5f3"
LOG_FILE="/var/log/gitlab/demote_to_standby.log"
​
# 加载钉钉通知函数
if [[ -f /opt/gitlab-ha/send_dingtalk.sh ]]; then
    source /opt/gitlab-ha/send_dingtalk.sh
else
    echo "警告: 未找到 send_dingtalk.sh,跳过钉钉通知" | tee -a "$LOG_FILE"
    send_dingtalk_message() { :; }  # 空函数占位
fi
​
# 日志函数
log() {
    local msg="$1"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $msg" | tee -a "$LOG_FILE"
}
​
# 检查是否持有 VIP
has_vip() {
    ip addr show "$INTERFACE" 2>/dev/null | grep -q "$VIP"
}
​
# 解析参数
FORCE_MODE=false
if [[ "${1:-}" == "--force" ]]; then
    FORCE_MODE=true
    log "强制模式启用:跳过 VIP 检查"
fi
​
# -------------------------------
# 主逻辑
# -------------------------------
​
if ! $FORCE_MODE && ! has_vip; then
    log "本机未持有 VIP ($VIP),且未启用强制模式  --force ,退出。"
    exit 0
fi
​
# 发送告警(仅在非强制模式下视为异常)
if ! $FORCE_MODE; then
    send_dingtalk_message "[$(date '+%Y-%m-%d %H:%M:%S')] 警告:检测到本机持有 VIP,正在自动降级为从库!请检查主节点状态。"
else
    send_dingtalk_message "[$(date '+%Y-%m-%d %H:%M:%S')] 通知:管理员手动触发 PostgreSQL 从库重建(强制模式)。"
fi
​
log "开始执行 PostgreSQL 从库重建流程..."
​
# 停止 GitLab 全部服务
log "停止 GitLab 服务..."
sudo gitlab-ctl stop || log "警告:gitlab-ctl stop 非零退出,继续执行"
​
# 重点:只操作 PostgreSQL
PG_DATA_DIR="/var/opt/gitlab/postgresql/data"
​
log "停止 PostgreSQL 服务..."
sudo gitlab-ctl stop postgresql || {
    log "警告:PostgreSQL 服务停止失败,尝试强制清理"
}
​
log "清理旧数据目录: $PG_DATA_DIR"
sudo rm -rf "$PG_DATA_DIR" || log "提示:数据目录可能不存在,忽略"
​
# 执行基础备份(从主库拉取)
MASTER_IP="192.168.252.150"
log "从主库 $MASTER_IP 同步全量数据..."
if ! sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_basebackup \
    -h "$MASTER_IP" \
    -U gitlab_replica \
    -D "$PG_DATA_DIR" \
    -P -v -R --wal-method=stream; then
    log "错误:pg_basebackup 失败,退出"
    exit 1
fi
​
# 修复权限
log "修复数据目录权限..."
sudo chown -R gitlab-psql:gitlab-psql "$PG_DATA_DIR"
sudo chmod 700 "$PG_DATA_DIR"
​
# 启动 PostgreSQL
log "启动 PostgreSQL 服务..."
sudo gitlab-ctl start postgresql || {
    log "错误:PostgreSQL 启动失败"
    exit 1
}
​
# 等待并验证流复制
log "等待 walreceiver 进程启动(最长 30 秒)..."
for i in {1..30}; do
    if pgrep -f "walreceiver" > /dev/null; then
        log "成功:walreceiver 已运行,流复制已建立"
        break
    fi
    sleep 1
done
​
# 最终启动其他服务(可选)
log "启动 GitLab 其他服务(保持只读)..."
sudo gitlab-ctl start nginx puma sidekiq redis
​
log "PostgreSQL 从库重建完成"
​
# ⚠️ 注意:不要在这里 restart keepalived!
# Keepalived 应由自身状态管理,手动重启可能导致 VIP 抖动

强制全量同步脚本(首次启动后对齐数据)

复制代码
#!/bin/bash
# ---------------------------------------------------------
# GitLab 镜像同步脚本:配置与数据全量同步(可选 PostgreSQL)
# 日志增强版:清晰标识是否同步 PostgreSQL
# ---------------------------------------------------------

SOURCE_DIR="/var/opt/gitlab/"
CONF_DIR="/etc/gitlab/"
TARGET_HOST="gitlab-standby"
LOG_FILE="/var/log/gitlab_rsync.log"

SYNC_POSTGRESQL=false

# 统一日志函数:带级别和时间戳
log() {
    local level="$1"
    local msg="$2"
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $msg" | tee -a "$LOG_FILE"
}

confirm_action() {
    log "WARN" "此操作将使用本地数据覆盖 $TARGET_HOST 上的 GitLab 配置和数据。"
    log "WARN" "备机上的现有数据可能丢失,请确保已做好预案。"

    echo
    echo "是否同步 PostgreSQL 数据目录?"
    echo "  [推荐选择 'N']:若备机使用流复制(如 pg_basebackup),请勿同步 PG 数据。"
    echo "  [仅选 'Y']:用于完全重建备机(如初次搭建或主库故障后恢复)。"
    read -p "同步 PostgreSQL?(y/N): " -r SYNC_PG_INPUT

    case "$SYNC_PG_INPUT" in
        [yY]|[yY][eE][sS])
            SYNC_POSTGRESQL=true
            log "INFO" "用户选择:✅ 同步 PostgreSQL 数据目录"
            ;;
        *)
            SYNC_POSTGRESQL=false
            log "INFO" "用户选择:❌ 不同步 PostgreSQL 数据目录(仅同步其他数据)"
            ;;
    esac

    echo
    read -p "确认继续执行同步?(输入 'yes' 确认): " -r CONFIRM
    if [[ "$CONFIRM" != "yes" ]]; then
        log "INFO" "操作已取消。"
        exit 0
    fi
}

sync_gitlab_data() {
    local target=$1
    local rsync_opts=(
        -avz
        --delete
        --exclude='log/'
        --exclude='tmp/'
        --exclude='**/*.sock'
        --exclude='redis/redis.socket'
    )

    if [[ "$SYNC_POSTGRESQL" == false ]]; then
        rsync_opts+=(
            --exclude='postgresql/data/postmaster.pid'
            --exclude='postgresql/data/postmaster.opts'
            --exclude='postgresql/data/'
        )
        log "INFO" "rsync 排除规则:已排除 postgresql/data/ 目录"
    else
        rsync_opts+=(
            --exclude='postgresql/data/postmaster.pid'
            --exclude='postgresql/data/postmaster.opts'
        )
        log "INFO" "rsync 排除规则:保留 postgresql/data/(仅排除运行时文件)"
    fi

    log "INFO" "开始同步配置文件到 $target..."
    if ! rsync -avz "$CONF_DIR" "$target:$CONF_DIR" >> "$LOG_FILE" 2>&1; then
        log "ERROR" "配置文件同步失败"
        return 1
    fi

    log "INFO" "开始同步数据目录到 $target..."
    if ! rsync "${rsync_opts[@]}" "$SOURCE_DIR" "$target:$SOURCE_DIR" >> "$LOG_FILE" 2>&1; then
        log "ERROR" "数据目录同步失败"
        return 1
    fi
}

main() {
    if [[ $EUID -ne 0 ]]; then
        echo "错误:必须以 root 权限运行此脚本。" >&2
        exit 1
    fi

    # 初始化日志分隔
    echo "==================================================" >> "$LOG_FILE"
    log "INFO" "GitLab 镜像同步任务启动"

    confirm_action
    sync_gitlab_data "$TARGET_HOST"
    local sync_status=$?

    if [[ $sync_status -eq 0 ]]; then
        log "SUCCESS" "同步任务成功完成"
        log "INFO" "PostgreSQL 数据同步状态: $(if $SYNC_POSTGRESQL; then echo '已包含'; else echo '已跳过'; fi)"
    else
        log "ERROR" "同步任务失败,请检查日志:$LOG_FILE"
        exit 1
    fi

    log "INFO" "GitLab 镜像同步任务结束"
    echo "==================================================" >> "$LOG_FILE"
}

main

重要:首次部署必须在主节点执行此脚本,确保备机拥有完整初始数据。

调试与验证

GitLab 服务控制

复制代码
sudo gitlab-ctl start   # 启动所有 GitLab 服务
sudo gitlab-ctl stop    # 停止所有 GitLab 服务
sudo gitlab-ctl status  # 查看各子服务运行状态

备节点正常情况下应保持 stopped 状态,直到故障切换。

查看初始密码(主节点)

复制代码
sudo cat /etc/gitlab/initial_root_password

访问 http://192.168.252.140/ 使用 root 用户登录(示例密码:kunW3M6wRwwPGSkks4ZIr2opBW8PW1/P71NlE7jPih8=)。

Keepalived 控制

复制代码
sudo systemctl restart keepalived
sudo systemctl status keepalived
# 查看 Keepalived 日志
journalctl -u keepalived -f

验证 VIP 是否生效

在任意节点执行:

复制代码
ip a

观察输出是否包含:

复制代码
inet 192.168.252.140/24 scope global ...

存在则表示当前节点持有 VIP。

手动触发故障切换测试

  1. 主节点停止 GitLab 服务:

    gitlab-ctl stop

  2. 观察主节点是否释放 VIP(ip a 不再显示 192.168.252.140)。

  3. 备节点检查是否获得 VIP 并启动 GitLab:

    ip a
    gitlab-ctl status
    curl -I http://192.168.252.140

恢复主节点后,若启用 nopreempt,VIP 不会自动切回,需手动干预或重启备机 Keepalived。当前配置未启用 nopreempt,主节点恢复健康后会重新抢占 VIP。

注意事项与限制

  • 数据库一致性风险:文件级同步无法保证 PostgreSQL 的 ACID 特性。主节点异常宕机可能导致备机数据损坏,建议配合定期备份(如 gitlab-backup create)使用。
  • 首次部署必须全量同步:启动 Keepalived 前,务必在主节点执行 force-full-sync.sh,确保备机有完整初始数据。
  • 版本一致性:主备节点 GitLab 版本(含补丁号)必须完全一致。
  • 网络要求:主备间网络延迟 < 10ms,带宽充足,避免同步滞后。
  • 监控建议:部署外部监控系统,检测 VIP 可达性、HTTP 响应、GitLab 服务状态等。

GitLab 高可用(HA)运维手册

日常检查

复制代码
# 检查 Keepalived 状态
systemctl status keepalived

# 查看主机同步 GitLab 文件的日志
cat /var/log/gitlab_data_rsync.log

# 查看从机是否已提权为主机
cat /var/log/gitlab-ha.log

# 查看 GitLab 配置
cat /etc/gitlab/gitlab.rb

# 一般性修复(重新应用配置)
sudo gitlab-ctl reconfigure;sudo gitlab-ctl restart

# 官方健康检查
sudo gitlab-rake gitlab:check SANITIZE=true

# 修复可能的权限问题
sudo gitlab-rake gitlab:storage:check

# 查看主机是否持有vip
ip a | grep ens5f3

# 疑难杂症 重启解
reboot

测试主机宕机(模拟故障切换)

操作步骤(原主机执行)
复制代码
# 停止 gitlab 模拟故障
sudo gitlab-ctl stop
验证(从机执行)
复制代码
# 查看日志确认是否提权成功
tail -n 50 /var/log/gitlab-ha.log

# 检查 VIP(192.168.252.140)是否已漂移到本机 ens5f3 接口
ip a | grep ens5f3

成功标志:VIP 出现在从机上,且 gitlab-ha.log 有提权成功记录。

主机宕机恢复(从从机备份还原)

第一步:在从机(当前主)执行备份
复制代码
# 验证 PostgreSQL 状态正常
gitlab-ctl status postgresql

# 验证数据库可读
sudo gitlab-psql -c "SELECT email, username FROM users ORDER BY created_at DESC LIMIT 5;"

# 执行备份脚本
cd /opt/gitlab-ha && ./gitlab-backup-pro.sh

# 将备份传输到原主机
scp -r /backup/gitlab root@192.168.252.150:/backup
第二步:在原主机(待恢复)准备还原
复制代码
# 停止 Keepalived(防止 VIP 漂移)
systemctl stop keepalived

# 停止 GitLab 服务(确保无写入)
sudo gitlab-ctl stop

此时原主机处于"离线维护"状态。

第三步:在原主机执行数据还原(跳过配置文件)
复制代码
# 列出备份文件(示例名:1770787575_2026_02_11_18.8.3_gitlab_backup.tar)
ls -l /backup/gitlab

# 执行还原(关键:--skip-config 不覆盖 /etc/gitlab/gitlab.rb)
cd /opt/gitlab-ha
./gitlab-restore-pro.sh --skip-config 1770803062_2026_02_11_18.8.3_gitlab_backup.tar

为什么加 --skip-config?避免覆盖原主机的 HA 配置(如角色、VIP 设置),防止主从角色混乱。

第四步:主机验证并恢复高可用
复制代码
# 启动 Keepalived(恢复 HA 控制)
systemctl restart keepalived

# 检查 VIP 是否回到原主机(192.168.252.140 应出现在 ens5f3)
ip a | grep ens5f3

# 验证 PostgreSQL 主从复制状态(应有 0 行记录,因为从机还没回复到从模式)
sudo gitlab-psql -c "SELECT * FROM pg_stat_replication;"

成功标志:pg_stat_replication 返回一行,说明从机正在同步。

手动恢复从机身份(降级为 standby)

在当前从机(曾临时为主)执行:

复制代码
# 执行降级脚本
/opt/gitlab-ha/demote_to_standby.sh

# 重启 Keepalived(重置监控状态 若主机健康则从机重启服务后放弃vip)
systemctl restart keepalived

# 可选主机强制同步gitlab数据到从机,按需选择,较为耗时
/opt/gitlab-ha/force-full-sync.sh
最终验证(原主机执行)
复制代码
# 再次确认复制状态(应有 1 行)
sudo gitlab-psql -c "SELECT * FROM pg_stat_replication;"

此时系统恢复默认拓扑:

  • 192.168.252.150:主(含 VIP 140)
  • 192.168.252.140:从(standby)

关键注意事项

项目 说明
​--skip-config​ 还原时必须使用,保留原主机 HA 配置
Keepalived 控制 任何手动干预 GitLab 前,先停 Keepalived 防漂移
PostgreSQL 角色 主库可写,从库只读;切换后需验证pg_is_in_recovery()​
日志位置 ​/var/log/gitlab-ha.log​是 HA 状态核心日志

核心脚本说明:

  • check_gitlab_health.sh:检查 gitlab 运行状态,失败则触发高可用自动切换从机
  • force-full-sync.sh:强制同步主机的 gitlab 数据和数据库到从机(高危)
  • gitlab-backup-pro.sh:备份 gitlab 数据和数据库脚本
  • gitlab-restore-pro.sh:还原 gitlab 数据和数据库脚本
  • sync-gitlab.sh:增量同步主机 gitlab 数据和数据库到从机
  • takeover.sh:从机提权为主机脚本

上述脚本请勿删除。

相关推荐
NineData1 天前
NineData智能数据管理平台新功能发布|2026年1-2月
数据库·sql·数据分析
IvorySQL1 天前
双星闪耀温哥华:IvorySQL 社区两项议题入选 PGConf.dev 2026
数据库·postgresql·开源
ma_king1 天前
入门 java 和 数据库
java·数据库·后端
jiayou641 天前
KingbaseES 实战:审计追踪配置与运维实践
数据库
NineData2 天前
NineData 迁移评估功能正式上线
数据库·dba
NineData2 天前
数据库迁移总踩坑?用 NineData 迁移评估,提前识别所有兼容性风险
数据库·程序员·云计算
赵渝强老师2 天前
【赵渝强老师】PostgreSQL中表的碎片
数据库·postgresql
全栈老石2 天前
拆解低代码引擎核心:元数据驱动的"万能表"架构
数据库·低代码
倔强的石头_3 天前
kingbase备份与恢复实战(二)—— sys_dump库级逻辑备份与恢复(Windows详细步骤)
数据库
jiayou644 天前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库