高可用环境下Nginx服务管理脚本优化实践

背景

在现代分布式系统中,高可用性(High Availability, HA)是一个至关重要的设计目标。为了实现高可用性,通常会采用双中心或多中心架构,并结合负载均衡技术(如Nginx)和高可用工具(如Keepalived)来确保服务的连续性。然而,在高可用环境中进行服务管理时,可能会遇到一些复杂的问题,尤其是在批量关闭服务时。

本文将以一个实际的案例为基础,详细探讨如何优化Nginx服务管理脚本,以解决在高可用环境下批量关闭Nginx服务时遇到的问题。


问题描述

环境说明

  1. 双中心架构:每个中心有4台Web服务器,每台服务器上运行Nginx和Keepalived。
  2. VIP(虚拟IP)管理:每个中心通过Keepalived管理一个VIP,用于实现高可用性。
  3. 高可用演练:在演练过程中,需要关闭一个中心的所有Web服务器。

问题现象

在使用初始脚本批量关闭Nginx服务时,发现部分服务器的Nginx服务无法一次性关闭,需要多次执行关闭操作才能成功。经过分析,发现问题可能与Keepalived的VIP漂移过程有关。

问题分析

  1. Keepalived VIP漂移的影响
    • 当批量关闭Nginx服务时,Keepalived会检测到服务不可用,并将VIP漂移到其他节点。
    • 在VIP漂移过程中,Nginx服务可能无法立即关闭,导致部分服务器需要多次执行关闭操作。
  2. 脚本的局限性
    • 初始脚本仅尝试关闭Nginx服务一次,未考虑VIP漂移过程中的延迟。
    • 缺乏重试机制,无法应对服务关闭失败的情况。

初始脚本分析

以下是初始的Nginx服务管理脚本:

plain 复制代码
#!/bin/bash

# 获取IP地址
IP_ADDRESS=$(hostname -I | awk '{print $1}')

# 服务相关变量
SERVICE_NAME="nginx"
SERVICE_DIR="/app/metabank/nginx/sbin"
START_SCRIPT="nginx"
STOP_SCRIPT="nginx -s quit"
FORCE_STOP_SCRIPT="pkill -9 nginx"

# 检查服务运行状态的函数
check_service_health() {
    if [ ! -d "$SERVICE_DIR" ]; then
        echo "指定的服务目录不存在: $SERVICE_DIR"
        echo "非0"
        return 2
    fi
    if systemctl is-active --quiet "$SERVICE_NAME"; then
        # 服务正在运行
        return 0
    else
        # 服务未运行
        return 1
    fi
}

# 输出服务的当前状态
print_status() {
    echo "--------------------------------------------------------------------------------"
    echo "| IP地址             | 服务名 | 状态    |"
    echo "--------------------------------------------------------------------------------"
    check_service_health
    local status_code=$?
    local status="未知"
    case $status_code in
        0) status="运行中" ;;
        1) status="关闭" ;;
        2) status="服务目录不存在" ;;
    esac
    printf "| %-18s | %-14s | %-8s |\n" "$IP_ADDRESS" "$SERVICE_NAME" "$status"
    echo "--------------------------------------------------------------------------------"
    echo "0"
}

# 启动服务的函数
start_service() {
    local force=false
    if [[ "$*" == *"FORCE"* ]]; then
        force=true
    fi

    if [ ! -d "$SERVICE_DIR" ]; then
        echo "服务目录不存在:$SERVICE_DIR"
        echo "非0"
        return 1
    fi

    echo "正在检查并停止任何已存在的服务实例"
    stop_service FORCE

    echo "正在启动服务..."
    if systemctl start "$SERVICE_NAME"; then
        sleep 5
        check_service_health
        if [ $? -eq 0 ]; then
            echo "服务启动成功。"
        else
            echo "服务启动失败,请检查日志。"
            echo "非0"
            return 1
        fi
    else
        echo "服务启动失败,请检查日志。"
        echo "非0"
        return 1
    fi
    echo "0"
}

# 停止服务的函数
stop_service() {
    local force=false
    if [[ "$*" == *"FORCE"* ]]; then
        force=true
    fi

    if [ ! -d "$SERVICE_DIR" ]; then
        echo "服务目录不存在:$SERVICE_DIR"
        echo "非0"
        return 1
    fi

    if systemctl is-active --quiet "$SERVICE_NAME"; then
        echo "正在尝试停止服务..."
        if systemctl stop "$SERVICE_NAME"; then
            sleep 3
            if ! systemctl is-active --quiet "$SERVICE_NAME"; then
                echo "服务已成功停止。"
            else
                if $force; then
                    echo "正在强制停止服务..."
                    eval "$FORCE_STOP_SCRIPT"
                    sleep 2
                    if ! systemctl is-active --quiet "$SERVICE_NAME"; then
                        echo "服务已被强制停止。"
                    else
                        echo "强制停止服务失败。"
                        echo "非0"
                        return 1
                    fi
                else
                    echo "尝试停止服务失败。"
                    echo "非0"
                    return 1
                fi
            fi
        else
            if $force; then
                echo "正在强制停止服务..."
                eval "$FORCE_STOP_SCRIPT"
                sleep 2
                if ! systemctl is-active --quiet "$SERVICE_NAME"; then
                    echo "服务已被强制停止。"
                else
                    echo "强制停止服务失败。"
                    echo "非0"
                    return 1
                fi
            else
                echo "服务停止命令执行失败。"
                echo "非0"
                return 1
            fi
        fi
    else
        echo "无需停止服务,服务已处于停止状态。"
    fi
    echo "0"
}

# 主执行逻辑
action=""
force=false

# 解析参数
for arg in "$@"; do
    case "${arg^^}" in
        START|STOP|STATUS|CHECK)
            if [ "${arg^^}" = "CHECK" ]; then
                action="STATUS"
            else
                action="${arg^^}"
            fi
            ;;
        FORCE)
            force=true
            ;;
        *)
            echo "未知参数: $arg"
            echo "用法: $0 {start|stop|status|check} [FORCE]"
            echo "非0"
            exit 1
            ;;
    esac
done

# 如果单独使用 FORCE 参数,默认执行 stop FORCE
if [[ "$force" == true && -z "$action" ]]; then
    action="STOP"
fi

# 执行操作
case "$action" in
    START)
        if $force; then
            start_service FORCE
        else
            start_service
        fi
        print_status
        ;;
    STOP)
        if $force; then
            stop_service FORCE
        else
            stop_service
        fi
        print_status
        ;;
    STATUS)
        print_status
        ;;
    *)
        echo "用法: $0 {start|stop|status|check} [FORCE]"
        echo "非0"
        exit 1
        ;;
esac

初始脚本的问题

  1. 缺乏重试机制:在关闭Nginx服务时,如果第一次关闭失败,脚本不会尝试再次关闭。
  2. 未考虑VIP漂移的延迟:在VIP漂移过程中,Nginx服务可能无法立即关闭,但脚本未对此进行处理。

优化方案

为了解决上述问题,我们对脚本进行了以下优化:

  1. 增加重试机制
    • 在关闭Nginx服务时,增加多次重试机制,定义间隔时间和重试次数变量。
    • 通过重试机制,确保在VIP漂移过程中能够成功关闭Nginx服务。
  2. 保持脚本独立性
    • 不修改Keepalived的配置或脚本,保持Nginx服务管理的独立性。

以下是优化后的脚本:

plain 复制代码
#!/bin/bash

# 获取IP地址
IP_ADDRESS=$(hostname -I | awk '{print $1}')

# 服务相关变量
SERVICE_NAME="nginx"
SERVICE_DIR="/app/metabank/nginx/sbin"
START_SCRIPT="nginx"
STOP_SCRIPT="nginx -s quit"
FORCE_STOP_SCRIPT="pkill -9 nginx"

# 定义间隔时间和重试次数
RETRY_INTERVAL=5  # 每次重试的间隔时间(秒)
MAX_RETRIES=5     # 最大重试次数

# 检查服务运行状态的函数
check_service_health() {
    if [ ! -d "$SERVICE_DIR" ]; then
        echo "指定的服务目录不存在: $SERVICE_DIR"
        echo "非0"
        return 2
    fi
    if systemctl is-active --quiet "$SERVICE_NAME"; then
        # 服务正在运行
        return 0
    else
        # 服务未运行
        return 1
    fi
}

# 输出服务的当前状态
print_status() {
    echo "--------------------------------------------------------------------------------"
    echo "| IP地址             | 服务名 | 状态    |"
    echo "--------------------------------------------------------------------------------"
    check_service_health
    local status_code=$?
    local status="未知"
    case $status_code in
        0) status="运行中" ;;
        1) status="关闭" ;;
        2) status="服务目录不存在" ;;
    esac
    printf "| %-18s | %-14s | %-8s |\n" "$IP_ADDRESS" "$SERVICE_NAME" "$status"
    echo "--------------------------------------------------------------------------------"
    echo "0"
}

# 启动服务的函数
start_service() {
    local force=false
    if [[ "$*" == *"FORCE"* ]]; then
        force=true
    fi

    if [ ! -d "$SERVICE_DIR" ]; then
        echo "服务目录不存在:$SERVICE_DIR"
        echo "非0"
        return 1
    fi

    echo "正在检查并停止任何已存在的服务实例"
    stop_service FORCE

    echo "正在启动服务..."
    if systemctl start "$SERVICE_NAME"; then
        sleep 5
        check_service_health
        if [ $? -eq 0 ]; then
            echo "服务启动成功。"
        else
            echo "服务启动失败,请检查日志。"
            echo "非0"
            return 1
        fi
    else
        echo "服务启动失败,请检查日志。"
        echo "非0"
        return 1
    fi
    echo "0"
}

# 停止服务的函数
stop_service() {
    local force=false
    if [[ "$*" == *"FORCE"* ]]; then
        force=true
    fi

    if [ ! -d "$SERVICE_DIR" ]; then
        echo "服务目录不存在:$SERVICE_DIR"
        echo "非0"
        return 1
    fi

    if systemctl is-active --quiet "$SERVICE_NAME"; then
        echo "正在尝试停止服务..."
        if systemctl stop "$SERVICE_NAME"; then
            sleep 3
            if ! systemctl is-active --quiet "$SERVICE_NAME"; then
                echo "服务已成功停止。"
            else
                if $force; then
                    echo "正在强制停止服务..."
                    eval "$FORCE_STOP_SCRIPT"
                    sleep 2
                    if ! systemctl is-active --quiet "$SERVICE_NAME"; then
                        echo "服务已被强制停止。"
                    else
                        echo "强制停止服务失败。"
                        echo "非0"
                        return 1
                    fi
                else
                    echo "尝试停止服务失败。"
                    echo "非0"
                    return 1
                fi
            fi
        else
            if $force; then
                echo "正在强制停止服务..."
                eval "$FORCE_STOP_SCRIPT"
                sleep 2
                if ! systemctl is-active --quiet "$SERVICE_NAME"; then
                    echo "服务已被强制停止。"
                else
                    echo "强制停止服务失败。"
                    echo "非0"
                    return 1
                fi
            else
                echo "服务停止命令执行失败。"
                echo "非0"
                return 1
            fi
        fi
    else
        echo "无需停止服务,服务已处于停止状态。"
    fi

    # 增加间隔时间后再次检查并关闭Nginx
    local retry_count=0
    while [ $retry_count -lt $MAX_RETRIES ]; do
        retry_count=$((retry_count + 1))
        echo "第${retry_count}次重试关闭Nginx服务..."
        echo "等待${RETRY_INTERVAL}秒后再次检查Nginx服务状态..."
        sleep $RETRY_INTERVAL
        if systemctl is-active --quiet "$SERVICE_NAME"; then
            echo "Nginx服务仍在运行,再次尝试停止..."
            if systemctl stop "$SERVICE_NAME"; then
                sleep 3
                if ! systemctl is-active --quiet "$SERVICE_NAME"; then
                    echo "服务已成功停止。"
                    break
                else
                    echo "再次尝试停止服务失败。"
                fi
            else
                echo "再次尝试停止服务失败。"
            fi
        else
            echo "Nginx服务已停止。"
            break
        fi
    done

    if [ $retry_count -eq $MAX_RETRIES ]; then
        echo "已达到最大重试次数(${MAX_RETRIES}),停止服务失败。"
        echo "非0"
        return 1
    fi

    echo "0"
}

# 主执行逻辑
action=""
force=false

# 解析参数
for arg in "$@"; do
    case "${arg^^}" in
        START|STOP|STATUS|CHECK)
            if [ "${arg^^}" = "CHECK" ]; then
                action="STATUS"
            else
                action="${arg^^}"
            fi
            ;;
        FORCE)
            force=true
            ;;
        *)
            echo "未知参数: $arg"
            echo "用法: $0 {start|stop|status|check} [FORCE]"
            echo "非0"
            exit 1
            ;;
    esac
done

# 如果单独使用 FORCE 参数,默认执行 stop FORCE
if [[ "$force" == true && -z "$action" ]]; then
    action="STOP"
fi

# 执行操作
case "$action" in
    START)
        if $force; then
            start_service FORCE
        else
            start_service
        fi
        print_status
        ;;
    STOP)
        if $force; then
            stop_service FORCE
        else
            stop_service
        fi
        print_status
        ;;
    STATUS)
        print_status
        ;;
    *)
        echo "用法: $0 {start|stop|status|check} [FORCE]"
        echo "非0"
        exit 1
        ;;
esac

总结

通过增加重试机制,我们成功解决了在高可用环境下批量关闭Nginx服务时部分服务器无法一次性关闭的问题。这种优化方式不仅简单有效,而且保持了脚本的独立性,避免了与其他服务管理的耦合。

在实际运维工作中,面对复杂的生产环境,我们需要灵活运用各种技术手段,确保系统的高可用性和稳定性。希望这篇博客对大家在类似场景下的工作有所帮助。

相关推荐
___波子 Pro Max.几秒前
Linux mount和SSD分区
linux·mount
浪裡遊8 分钟前
Nginx快速上手
运维·前端·后端·nginx
用户2587141932631 小时前
深入浅出--Linux基础命令知识(总结,配图文解释)
linux·ubuntu
风正豪1 小时前
如何向 Linux 中加入一个 IO 扩展芯片
linux·运维·单片机
如果是君2 小时前
Ubuntu20.04安装运行DynaSLAM
linux·python·深度学习·神经网络·ubuntu
熬夜苦读学习2 小时前
进程间通信--匿名管道
运维·服务器
前端白袍3 小时前
性能优化:服务器性能影响网站加载速度分析
运维·服务器·性能优化
无聊的烤苕皮3 小时前
RHCE(RHCSA复习:npm、dnf、源码安装实验)
linux·npm·云计算·dnf·rhcsa
xxxx1234454 小时前
Linux驱动开发-①pinctrl 和 gpio 子系统②并发和竞争③内核定时器
linux·驱动开发·单片机