HAProxy多节点配置同步脚本

HAProxy配置文件拆分

vi /usr/lib/systemd/system/haproxy.service

添加SUBCONFIG配置目录

Environment="CONFIG=/etc/haproxy/haproxy.cfg" "SUBCONFIG=/etc/haproxy/conf.d" "PIDFILE=/run/haproxy.pid" "EXTRAOPTS=-S /run/haproxy-master.sock"
ExecStartPre=/usr/sbin/haproxy -Ws -f $CONFIG -f $SUBCONFIG -c -q $EXTRAOPTS
ExecStart=/usr/sbin/haproxy -Ws -f $CONFIG -f $SUBCONFIG -p $PIDFILE $EXTRAOPTS
ExecReload=/usr/sbin/haproxy -Ws -f $CONFIG -f $SUBCONFIG -c -q $EXTRAOPTS

/etc/haproxy目录结构

ubuntu@k8s-lb1:/etc/haproxy$ tree
.
├── conf.d
│   ├── k8s-ingress.blacklist
│   ├── k8s-ingress.cfg
│   ├── kafka.cfg
│   ├── kafka.whitelist
│   ├── ks-console.cfg
│   ├── kube-apiserver.cfg
│   └── sync-cfg.sh
├── errors
│   ├── 400.http
│   ├── 403.http
│   ├── 408.http
│   ├── 500.http
│   ├── 502.http
│   ├── 503.http
│   └── 504.http
└── haproxy.cfg

2 directories, 15 files

同步脚本sync-cfg.sh

#!/bin/bash
#
# HAProxy 配置文件同步脚本
# 功能:将本地 HAProxy 配置同步到远程节点并重载服务
# 作者:Sulan Huang
# 版本:1.0
#

# 配置参数
REMOTE_HOST="k8s-lb2"        # 远程节点IP地址
REMOTE_USER="ubuntu"         # 远程节点用户名(Ubuntu用户)
HAPROXY_CONFIG="/etc/haproxy/haproxy.cfg"
HAPROXY_CONF_DIR="/etc/haproxy/conf.d"
BACKUP_DIR="/etc/haproxy/backup"
LOGFILE="$HOME/haproxy-sync.log"
SSH_KEY="$HOME/.ssh/id_rsa"  # SSH私钥路径(使用当前用户的HOME目录)

# Ubuntu 环境特定配置
CURRENT_USER=$(whoami)
NEED_SUDO=true
SYSTEMCTL_CMD="sudo systemctl"

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 日志函数 - 写入文件(不带颜色)
log_to_file() {
    local level=$1
    shift
    local message="$@"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    # 确保日志目录存在
    local log_dir=$(dirname "$LOGFILE")
    if [[ ! -d "$log_dir" ]]; then
        mkdir -p "$log_dir" 2>/dev/null || true
    fi
    
    # 写入日志文件(去除颜色代码)
    echo "${timestamp} [$level] $message" | sed 's/\x1b\[[0-9;]*m//g' >> "$LOGFILE" 2>/dev/null || true
}

# 颜色日志函数
log_info() {
    local message="$1"
    echo -e "${BLUE}[INFO]${NC} $message"
    log_to_file "INFO" "$message"
}

log_success() {
    local message="$1"
    echo -e "${GREEN}[SUCCESS]${NC} $message"
    log_to_file "SUCCESS" "$message"
}

log_warning() {
    local message="$1"
    echo -e "${YELLOW}[WARNING]${NC} $message"
    log_to_file "WARNING" "$message"
}

log_error() {
    local message="$1"
    echo -e "${RED}[ERROR]${NC} $message"
    log_to_file "ERROR" "$message"
}

# 通用日志函数(保持向后兼容)
log() {
    local level=$1
    shift
    local message="$@"
    log_to_file "$level" "$message"
}


# 显示使用帮助
show_help() {
    cat << EOF
HAProxy 配置同步脚本

用法: $0 [选项]

选项:
    -h, --host       远程主机IP地址 (默认: $REMOTE_HOST)
    -u, --user       远程主机用户名 (默认: $REMOTE_USER)
    -k, --key        SSH私钥路径 (默认: $SSH_KEY)
    -t, --test       仅测试配置文件语法,不执行同步
    -f, --force      强制同步,跳过确认提示
    --help           显示此帮助信息

示例:
    $0                                    # 使用默认配置同步
    $0 -h 192.168.1.101 -u ubuntu        # 指定远程主机和用户
    $0 -t                                 # 仅测试配置文件
    $0 -f                                 # 强制同步,不询问确认

注意事项:
    - 确保本地和远程主机都安装了 HAProxy
    - 确保当前用户在两个节点上都有 sudo 权限
    - 确保已配置 SSH 密钥认证:ssh-copy-id ubuntu@remote_host
    - 建议先使用 -t 参数测试配置文件语法

EOF
}

# 解析命令行参数
parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--host)
                REMOTE_HOST="$2"
                shift 2
                ;;
            -u|--user)
                REMOTE_USER="$2"
                shift 2
                ;;
            -k|--key)
                SSH_KEY="$2"
                shift 2
                ;;
            -t|--test)
                TEST_ONLY=true
                shift
                ;;
            -f|--force)
                FORCE_SYNC=true
                shift
                ;;
            --help)
                show_help
                exit 0
                ;;
            *)
                log_error "未知参数: $1"
                show_help
                exit 1
                ;;
        esac
    done
}

# 检查依赖
check_dependencies() {
    log_info "检查依赖项..."
    
    # 检查必要的命令
    local commands=("haproxy" "rsync" "ssh" "scp" "sudo")
    for cmd in "${commands[@]}"; do
        if ! command -v "$cmd" &> /dev/null; then
            log_error "缺少必要命令: $cmd"
            exit 1
        fi
    done
    
    # 检查当前用户
    if [[ "$CURRENT_USER" == "root" ]]; then
        NEED_SUDO=false
        SYSTEMCTL_CMD="systemctl"
        log_info "当前用户为 root,无需 sudo"
    else
        log_info "当前用户为 $CURRENT_USER,将使用 sudo 执行特权操作"
        
        # 检查sudo权限
        if ! sudo -n true 2>/dev/null; then
            log_warning "检测到需要 sudo 密码,请确保有 sudo 权限"
            # 测试sudo权限
            if ! sudo -v; then
                log_error "无法获取 sudo 权限"
                exit 1
            fi
        fi
    fi
    
    # 检查配置文件是否存在(使用sudo读取)
    if [[ "$NEED_SUDO" == true ]]; then
        if ! sudo test -f "$HAPROXY_CONFIG"; then
            log_error "HAProxy 主配置文件不存在: $HAPROXY_CONFIG"
            exit 1
        fi
        
        if ! sudo test -d "$HAPROXY_CONF_DIR"; then
            log_warning "HAProxy 配置目录不存在: $HAPROXY_CONF_DIR"
            log_info "将尝试创建配置目录"
            sudo mkdir -p "$HAPROXY_CONF_DIR"
        fi
    else
        if [[ ! -f "$HAPROXY_CONFIG" ]]; then
            log_error "HAProxy 主配置文件不存在: $HAPROXY_CONFIG"
            exit 1
        fi
        
        if [[ ! -d "$HAPROXY_CONF_DIR" ]]; then
            log_warning "HAProxy 配置目录不存在: $HAPROXY_CONF_DIR"
            log_info "将尝试创建配置目录"
            mkdir -p "$HAPROXY_CONF_DIR"
        fi
    fi
    
    # 检查SSH密钥
    if [[ ! -f "$SSH_KEY" ]]; then
        log_error "SSH私钥文件不存在: $SSH_KEY"
        log_info "提示:请运行 'ssh-keygen -t rsa -b 4096' 生成SSH密钥"
        exit 1
    fi
    
    # 检查日志目录权限
    local log_dir=$(dirname "$LOGFILE")
    if [[ "$NEED_SUDO" == true ]] && [[ ! -w "$log_dir" ]]; then
        log_warning "无法写入日志目录 $log_dir,尝试使用用户目录"
        LOGFILE="$HOME/haproxy-sync.log"
        log_info "日志文件已更改为: $LOGFILE"
    fi
    
    log_success "依赖检查通过"
}

# 测试配置文件语法
test_config() {
    log_info "检查 HAProxy 配置文件语法..."
    
    # 创建临时配置文件进行测试
    local temp_config="/tmp/haproxy_test_$.cfg"
    
    # 合并主配置文件和conf.d目录下的配置文件(使用sudo读取)
    {
        if [[ "$NEED_SUDO" == true ]]; then
            sudo cat "$HAPROXY_CONFIG"
            if sudo test -d "$HAPROXY_CONF_DIR" && [[ $(sudo ls -A "$HAPROXY_CONF_DIR"/*.cfg 2>/dev/null | wc -l) -gt 0 ]]; then
                for conf_file in $(sudo ls "$HAPROXY_CONF_DIR"/*.cfg 2>/dev/null); do
                    if sudo test -f "$conf_file"; then
                        echo ""
                        echo "# Include: $conf_file"
                        sudo cat "$conf_file"
                    fi
                done
            fi
        else
            cat "$HAPROXY_CONFIG"
            if [[ -d "$HAPROXY_CONF_DIR" ]] && [[ $(ls -A "$HAPROXY_CONF_DIR"/*.cfg 2>/dev/null | wc -l) -gt 0 ]]; then
                for conf_file in "$HAPROXY_CONF_DIR"/*.cfg; do
                    if [[ -f "$conf_file" ]]; then
                        echo ""
                        echo "# Include: $conf_file"
                        cat "$conf_file"
                    fi
                done
            fi
        fi
    } > "$temp_config"
    
    # 测试配置语法
    if haproxy -c -f "$temp_config" 2>/dev/null; then
        log_success "配置文件语法检查通过"
        rm -f "$temp_config"
        return 0
    else
        log_error "配置文件语法检查失败"
        log_error "详细错误信息:"
        haproxy -c -f "$temp_config" 2>&1 | while read line; do
            log_error "  $line"
        done
        rm -f "$temp_config"
        return 1
    fi
}

# 测试远程连接
test_remote_connection() {
    log_info "测试远程连接 $REMOTE_USER@$REMOTE_HOST..."
    
    if ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes "$REMOTE_USER@$REMOTE_HOST" "echo 'Connection test successful'" &>/dev/null; then
        log_success "远程连接测试成功"
        return 0
    else
        log_error "无法连接到远程主机 $REMOTE_USER@$REMOTE_HOST"
        log_error "请检查网络连接、SSH密钥或主机信息"
        return 1
    fi
}

# 创建远程备份
create_remote_backup() {
    log_info "在远程主机创建配置备份..."
    
    local backup_timestamp=$(date '+%Y%m%d_%H%M%S')
    local remote_backup_dir="/etc/haproxy/backup/$backup_timestamp"
    
    ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "
        sudo mkdir -p '$remote_backup_dir'
        if sudo test -f '$HAPROXY_CONFIG'; then
            sudo cp '$HAPROXY_CONFIG' '$remote_backup_dir/'
        fi
        if sudo test -d '$HAPROXY_CONF_DIR'; then
            sudo cp -r '$HAPROXY_CONF_DIR' '$remote_backup_dir/'
        fi
        echo 'Backup created at: $remote_backup_dir'
    " 2>/dev/null
    
    if [[ $? -eq 0 ]]; then
        log_success "远程备份创建成功: $remote_backup_dir"
        return 0
    else
        log_error "远程备份创建失败"
        return 1
    fi
}

# 同步配置文件
sync_config() {
    log_info "同步配置文件到远程主机..."
    
    # 创建临时目录用于rsync
    local temp_dir="/tmp/haproxy_sync_$"
    mkdir -p "$temp_dir/etc/haproxy"
    
    # 复制本地文件到临时目录(处理权限问题)
    if [[ "$NEED_SUDO" == true ]]; then
        sudo cp "$HAPROXY_CONFIG" "$temp_dir/etc/haproxy/"
        sudo chown -R "$CURRENT_USER:$CURRENT_USER" "$temp_dir"
        
        if sudo test -d "$HAPROXY_CONF_DIR" && [[ $(sudo ls -A "$HAPROXY_CONF_DIR" 2>/dev/null | wc -l) -gt 0 ]]; then
            sudo cp -r "$HAPROXY_CONF_DIR" "$temp_dir/etc/haproxy/"
            sudo chown -R "$CURRENT_USER:$CURRENT_USER" "$temp_dir"
        fi
    else
        cp "$HAPROXY_CONFIG" "$temp_dir/etc/haproxy/"
        if [[ -d "$HAPROXY_CONF_DIR" ]] && [[ $(ls -A "$HAPROXY_CONF_DIR" 2>/dev/null | wc -l) -gt 0 ]]; then
            cp -r "$HAPROXY_CONF_DIR" "$temp_dir/etc/haproxy/"
        fi
    fi
    
    # 同步主配置文件
    if rsync -avz --delete -e "ssh -i $SSH_KEY" "$temp_dir/etc/haproxy/haproxy.cfg" "$REMOTE_USER@$REMOTE_HOST:/tmp/haproxy.cfg.new" 2>/dev/null; then
        # 在远程主机上移动文件到正确位置
        if ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "sudo mv /tmp/haproxy.cfg.new '$HAPROXY_CONFIG'" 2>/dev/null; then
            log_success "主配置文件同步成功"
        else
            log_error "主配置文件移动到目标位置失败"
            rm -rf "$temp_dir"
            return 1
        fi
    else
        log_error "主配置文件同步失败"
        rm -rf "$temp_dir"
        return 1
    fi
    
    # 同步conf.d目录
    if [[ -d "$temp_dir/etc/haproxy/conf.d" ]]; then
        # 确保远程目录存在
        ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "sudo mkdir -p '$HAPROXY_CONF_DIR'" 2>/dev/null
        
        if rsync -avz --delete -e "ssh -i $SSH_KEY" "$temp_dir/etc/haproxy/conf.d/" "$REMOTE_USER@$REMOTE_HOST:/tmp/haproxy_conf_d/" 2>/dev/null; then
            # 在远程主机上移动文件到正确位置
            if ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "
                sudo rm -rf '$HAPROXY_CONF_DIR'/*
                sudo mv /tmp/haproxy_conf_d/* '$HAPROXY_CONF_DIR/' 2>/dev/null || true
                sudo rmdir /tmp/haproxy_conf_d 2>/dev/null || true
            " 2>/dev/null; then
                log_success "配置目录同步成功"
            else
                log_error "配置目录移动到目标位置失败"
                rm -rf "$temp_dir"
                return 1
            fi
        else
            log_error "配置目录同步失败"
            rm -rf "$temp_dir"
            return 1
        fi
    else
        log_warning "配置目录为空或不存在,跳过同步"
    fi
    
    # 清理临时目录
    rm -rf "$temp_dir"
    return 0
}

# 测试远程配置
test_remote_config() {
    log_info "测试远程主机配置文件语法..."
    
    if ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "haproxy -c -f '$HAPROXY_CONFIG'" 2>/dev/null; then
        log_success "远程配置文件语法检查通过"
        return 0
    else
        log_error "远程配置文件语法检查失败"
        ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "haproxy -c -f '$HAPROXY_CONFIG'" 2>&1 | while read line; do
            log_error "  $line"
        done
        return 1
    fi
}

# 重载远程HAProxy服务
reload_remote_haproxy() {
    log_info "重载远程 HAProxy 服务..."
    
    # 检查HAProxy服务状态
    if ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "sudo systemctl is-active haproxy" &>/dev/null; then
        # 使用systemctl reload
        if ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "sudo systemctl reload haproxy" 2>/dev/null; then
            log_success "HAProxy 服务重载成功"
            
            # 验证服务状态
            if ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "sudo systemctl is-active haproxy" &>/dev/null; then
                log_success "HAProxy 服务运行正常"
                return 0
            else
                log_error "HAProxy 服务重载后状态异常"
                return 1
            fi
        else
            log_error "HAProxy 服务重载失败,尝试重启服务"
            if ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "sudo systemctl restart haproxy" 2>/dev/null; then
                log_success "HAProxy 服务重启成功"
                return 0
            else
                log_error "HAProxy 服务重启失败"
                # 显示详细错误信息
                ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "sudo systemctl status haproxy --no-pager -l" 2>&1 | while read line; do
                    log_error "  $line"
                done
                return 1
            fi
        fi
    else
        log_warning "远程 HAProxy 服务未运行,尝试启动服务"
        if ssh -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" "sudo systemctl start haproxy" 2>/dev/null; then
            log_success "HAProxy 服务启动成功"
            return 0
        else
            log_error "HAProxy 服务启动失败"
            return 1
        fi
    fi
}

# 重载本地HAProxy服务
reload_local_haproxy() {
    log_info "重载本地 HAProxy 服务..."
    # 检查HAProxy服务状态
    if sudo systemctl is-active haproxy &>/dev/null; then
        # 使用systemctl reload
        if sudo systemctl reload haproxy 2>/dev/null; then
            log_success "HAProxy 服务重载成功"
            # 验证服务状态
            if sudo systemctl is-active haproxy &>/dev/null; then
                log_success "HAProxy 服务运行正常"
                return 0
            else
                log_error "HAProxy 服务重载后状态异常"
                return 1
            fi
        else
            log_error "HAProxy 服务重载失败,尝试重启服务"
            if sudo systemctl restart haproxy 2>/dev/null; then
                log_success "HAProxy 服务重启成功"
                return 0
            else
                log_error "HAProxy 服务重启失败"
                # 显示详细错误信息
                sudo systemctl status haproxy --no-pager -l 2>&1 | while read line; do
                    log_error "  $line"
                done
                return 1
            fi
        fi
    else
        log_warning "本地 HAProxy 服务未运行,尝试启动服务"
        if sudo systemctl start haproxy 2>/dev/null; then
            log_success "HAProxy 服务启动成功"
            return 0
        else
            log_error "HAProxy 服务启动失败"
            return 1
        fi
    fi
}

# 显示同步摘要
show_summary() {
    log_info "同步摘要:"
    echo "  源主机: $(hostname)"
    echo "  目标主机: $REMOTE_HOST"
    echo "  目标用户: $REMOTE_USER"
    echo "  主配置文件: $HAPROXY_CONFIG"
    echo "  配置目录: $HAPROXY_CONF_DIR"
    echo "  日志文件: $LOGFILE"
}

# 主函数
main() {
    echo "=========================================="
    echo "      HAProxy 配置同步脚本"
    echo "=========================================="
    
    # 解析命令行参数
    parse_args "$@"
    
    # 检查依赖
    check_dependencies
    
    # 测试本地配置
    if ! test_config; then
        log_error "本地配置文件语法错误,停止同步"
        exit 1
    fi
    
    # 如果只是测试模式,直接退出
    if [[ "$TEST_ONLY" == true ]]; then
        log_success "测试模式:本地配置文件语法正确"
        exit 0
    fi
    
    # 显示同步摘要
    show_summary
    
    # 确认同步操作
    if [[ "$FORCE_SYNC" != true ]]; then
        echo ""
        read -p "确认执行同步操作? (y/N): " -n 1 -r
        echo ""
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            log_info "同步操作已取消"
            exit 0
        fi
    fi
    
    # 测试远程连接
    if ! test_remote_connection; then
        exit 1
    fi
    
    # 创建远程备份
    if ! create_remote_backup; then
        log_error "无法创建远程备份,停止同步"
        exit 1
    fi
    
    # 同步配置文件
    if ! sync_config; then
        log_error "配置文件同步失败"
        exit 1
    fi
    
    # 测试远程配置
    if ! test_remote_config; then
        log_error "远程配置文件语法错误,请检查"
        exit 1
    fi
    
    # 重载远程HAProxy服务
    if ! reload_remote_haproxy; then
        log_error "远程 HAProxy 服务重载失败"
        exit 1
    fi

    # 重载本地HAProxy服务
    if ! reload_local_haproxy; then
	log_error "本地 HAProxy 服务重载失败"
        exit 1
    fi    
    
    log_success "=========================================="
    log_success "HAProxy 配置同步完成!"
    log_success "=========================================="
}

# 捕获退出信号,进行清理
trap 'log_info "脚本执行中断"; exit 1' INT TERM

# 执行主函数
main "$@"