如何编写节点隔离处理脚本

wen 实用脚本 32

从零构建高可用集群容错机制

📚 目录导读

  1. 什么是节点隔离?为什么需要它?
  2. 节点隔离脚本的核心设计原则
  3. 准备工作:环境与依赖
  4. 脚本编写实战:分步实现
    • 1 基础结构定义
    • 2 心跳检测模块
    • 3 隔离触发条件
    • 4 隔离执行动作
    • 5 恢复与解除隔离
  5. 常见问题与深度Q&A
  6. 生产环境最佳实践

什么是节点隔离?为什么需要它?

节点隔离(Node Isolation) 是指在分布式系统或集群中,当一个节点(服务器、容器或服务实例)出现故障、响应超时或行为异常时,自动将其从集群中“断开”并阻止其继续接收流量或参与任务分配的过程。

如何编写节点隔离处理脚本

举个例子:假设你有一个由5台服务器组成的Web集群,突然某台服务器内存溢出,响应变得极其缓慢,如果不隔离它,客户端的请求会被“卡死”在它这里,导致整个集群的可用性下降,这就是节点隔离要解决的问题。

关键区别: 节点隔离 ≠ 直接杀死进程,隔离的目标是“逻辑上移除”而非“物理上销毁”,以便后续可以恢复或诊断。


节点隔离脚本的核心设计原则

当你要编写一个“真正可用”的节点隔离脚本时,必须遵守以下5条铁律:

原则 说明 错误做法
幂等性 多次执行隔离脚本,效果应与一次相同 第二次执行时报错“已隔离”
可恢复性 必须配套解除隔离功能 只写隔离不写恢复
低误判率 避免因网络抖动或短暂负载导致错误隔离 连续ping失败1次就隔离
安全回退 脚本本身崩溃时不能导致集群瘫痪 脚本依赖的数据库挂了,隔离逻辑崩溃
可观测性 每次隔离动作必须写日志、打事件 隔离后运维不知道发生了什么

黄金法则: 宁可漏过一次故障,也不误杀一个健康节点。


准备工作:环境与依赖

1 运行环境

  • 操作系统:Linux (CentOS 7+/Ubuntu 18.04+)
  • Shell:Bash 4.2+(建议使用 #!/bin/bash
  • 权限:脚本需要 rootsudo 权限执行,因为涉及iptables/ip route操作

2 必要工具

  • ping:基础网络探测
  • curl / wget:健康检查HTTP端点
  • iptablesnftables:实现网络层隔离
  • ip route:路由管理
  • systemctl(可选):停止服务

3 集群假设

本文以 Keepalived + Nginx 负载均衡集群 为例,目标节点IP假设为 168.1.100,健康检查端口 80


脚本编写实战:分步实现

1 基础结构定义

#!/bin/bash
# ========================================
# 节点隔离处理脚本 v2.3 - 生产就绪版
# 作者: 云原生运维团队
# 功能: 自动检测并隔离故障节点
# ========================================
set -euo pipefail  # 启用严格模式:任何非零退出直接中止
# ---------- 配置区 ----------
TARGET_IP="192.168.1.100"          # 待检测节点IP
HEALTH_CHECK_PORT=80               # 健康检查端口
PING_COUNT=5                       # ping次数
FAIL_THRESHOLD=3                   # 连续失败次数触发隔离
CHECK_INTERVAL=5                   # 检测间隔(秒)
ISOLATION_DURATION=300             # 隔离持续时间(秒),0表示手动恢复
LOG_FILE="/var/log/node_isolation.log"
STATE_FILE="/tmp/node_state_${TARGET_IP}.txt"  # 存储状态文件
# ----------------------------
# 日志函数
log() {
    local level=$1
    shift
    echo "$(date '+%Y-%m-%d %H:%M:%S') [${level}] $*" >> "$LOG_FILE"
}

为什么不直接用 set -e 就够?
u 可以捕获未定义变量错误(如拼写错误),加 o pipefail 能避免管道中中间命令失败被忽略,这是生产脚本的标配。


2 心跳检测模块

# 检测节点是否存活(使用多种方式)
check_node_alive() {
    local ip=$1
    local port=$2
    # 方式1: ICMP检测
    ping -c $PING_COUNT -W 2 "$ip" > /dev/null 2>&1
    local icmp_status=$?
    # 方式2: TCP端口检测
    timeout 3 bash -c "echo >/dev/tcp/$ip/$port" 2>/dev/null
    local tcp_status=$?
    # 方式3: HTTP GET检测(如果有API)
    local http_status=0
    curl -s -o /dev/null -w "%{http_code}" "http://${ip}:${port}/health" 2>/dev/null | grep -q "200" && http_status=0 || http_status=1
    # 综合评分:至少两种方式通过才算存活
    local alive_count=0
    [[ $icmp_status -eq 0 ]] && ((alive_count++))
    [[ $tcp_status -eq 0 ]] && ((alive_count++))
    [[ $http_status -eq 0 ]] && ((alive_count++))
    if [[ $alive_count -ge 2 ]]; then
        return 0  # 存活
    else
        return 1  # 死亡
    fi
}

细节说明:
为什么不用单一检测方法?因为网络环境复杂,ICMP可能被防火墙屏蔽,但业务端口正常;或者服务进程还在但HTTP返回5xx,多维度检测能大幅降低误判率。


3 隔离触发条件

# 判断是否需要隔离
should_isolate() {
    local ip=$1
    local fail_count=0
    for ((i=1; i<=$FAIL_THRESHOLD; i++)); do
        if check_node_alive "$ip" "$HEALTH_CHECK_PORT"; then
            fail_count=0
            return 1  # 节点恢复正常,取消隔离决策
        else
            ((fail_count++))
            log "WARN" "节点 $ip 第 $fail_count 次检测失败"
        fi
        sleep $CHECK_INTERVAL
    done
    if [[ $fail_count -ge $FAIL_THRESHOLD ]]; then
        log "ERROR" "节点 $ip 连续 $fail_count 次检测失败,触发隔离"
        return 0  # 需要隔离
    fi
}

关键设计:
两次检测之间必须有间隔($CHECK_INTERVAL),如果连续5次ping都失败,但每次间隔只有0.1秒,仍然可能是瞬时抖动,建议至少间隔5秒,总检测时间在15-30秒内完成。


4 隔离执行动作(核心)

# 执行隔离
isolate_node() {
    local ip=$1
    log "ISOLATE" "开始隔离节点 $ip"
    # 1. 从本地路由表中删除该节点(阻止出站到该节点)
    ip route del "$ip" 2>/dev/null || true
    # 2. 添加iptables规则丢弃入站到该节点的流量(针对负载均衡场景)
    iptables -A INPUT -s "$ip" -j DROP 2>/dev/null || true
    iptables -A OUTPUT -d "$ip" -j DROP 2>/dev/null || true
    # 3. 如果是容器环境,可以停止容器(示例)
    # docker stop my-app-container 2>/dev/null || true
    # 4. 如果是服务管理,可以停止服务(示例)
    # systemctl stop nginx 2>/dev/null || true
    # 5. 记录状态文件,用于后续恢复
    echo "isolated_at=$(date +%s)" > "$STATE_FILE"
    echo "duration=$ISOLATION_DURATION" >> "$STATE_FILE"
    # 6. 写入告警(可以集成钉钉/企业微信通知)
    log "ALERT" "节点 $ip 已被隔离,请检查原因!"
    # 非阻塞:启动后台进程监控恢复
    if [[ $ISOLATION_DURATION -gt 0 ]]; then
        (sleep $ISOLATION_DURATION && unisolate_node "$ip") &
        log "INFO" "后台调度恢复任务,将在 ${ISOLATION_DURATION}s 后尝试解除隔离"
    fi
}

隔离手段的权衡:

  • iptables DROP:网络层隔离,成本低、恢复快,缺点是如果节点本身在链路上,它依然能处理已建立的连接(TCP超时需等待)。
  • 停止服务:更彻底,但恢复时需要重启服务,耗时较长。
  • 删除路由:适用于IP层完全阻断,但需要根权限。

生产建议: 先执行iptables DROP(毫秒级生效),再根据业务需要决定是否停服务。


5 恢复与解除隔离

# 解除隔离
unisolate_node() {
    local ip=$1
    log "UNISOLATE" "尝试解除节点 $ip 的隔离"
    # 1. 先检查节点是否已恢复
    if check_node_alive "$ip" "$HEALTH_CHECK_PORT"; then
        # 2. 删除iptables规则
        iptables -D INPUT -s "$ip" -j DROP 2>/dev/null || true
        iptables -D OUTPUT -d "$ip" -j DROP 2>/dev/null || true
        # 3. 恢复路由(如果之前删除了)
        ip route add "$ip/32" dev eth0 2>/dev/null || true
        # 4. 恢复服务(如果之前停止了)
        # systemctl start nginx 2>/dev/null || true
        # 5. 清理状态文件
        rm -f "$STATE_FILE"
        log "INFO" "节点 $ip 已成功解除隔离,重新加入集群"
    else
        log "WARN" "节点 $ip 尚未恢复,跳过解除隔离,将在下次调度中重试"
        # 如果设置了自动重试,可以再次调度
        if [[ $ISOLATION_DURATION -gt 0 ]]; then
            (sleep $ISOLATION_DURATION && unisolate_node "$ip") &
        fi
    fi
}

解除隔离的陷阱:
很多人直接用 iptables -F 清空所有规则,这会导致其它防护规则也被删除,正确做法是删除指定规则(使用 -D),如果规则带注释(-m comment --comment "isolate-$ip"),可以更精确定位。


常见问题与深度Q&A

Q1: 如果脚本本身运行失败怎么办?会不会导致所有节点被误隔离?

A: 这正是设计原则中“安全回退”的体现,建议采用以下措施:

  • 脚本入口处增加自检机制:先检测自身运行环境(如iptables是否可用、日志目录可写),如果依赖不满足,直接退出而不是执行默认逻辑。
  • 增加熔断计数器:脚本运行失败超过3次,停止自动执行并发送告警。
  • 使用守护进程 + 心跳文件:如果脚本进程挂掉,另一个监控进程会接管。

Q2: 如何避免隔离时断开当前SSH连接?

A: 这个其实是很多运维翻车的场景,解决方案:

  • 限制iptables规则的作用域:使用 -m addrtype --dst-type LOCAL 排除本地回环流量。
  • 在脚本开头检查当前运行环境:如果脚本是用SSH远程执行的,增加 trap 捕获退出信号,确保清除规则。
  • 终极方案:永远不要在生产环境中直接SSH执行隔离脚本,而是通过API触发,且API必须带白名单校验。

Q3: 什么时候用“停止服务”而不是“网络隔离”?

A: 分场景:

  • 流量过高导致节点崩溃:网络隔离优先(因为万一停止服务后端口释放,健康检查可能误判为正常)。
  • 进程卡死但端口正常:必须停止服务(因为端口还在,负载均衡可能继续调度)。
  • 磁盘满导致写失败:停止服务 + 远程清理。
  • 最佳实践是两阶段隔离:先网络隔离(让流量停止),再进程停止(释放资源,避免孤儿进程)。

生产环境最佳实践

1 集成到现有监控系统

不要单独运行这个脚本,而是通过 Prometheus + AlertmanagerZabbix 触发,脚本作为“执行器”,监控系统作为“决策器”。

示例如下:

  1. Prometheus 检测到节点 up 指标为0。
  2. Alertmanager 调用Webhook。
  3. Webhook 触发你的隔离脚本。
  4. 脚本执行后,将结果写回Prometheus的PushGateway,形成闭环。

2 全文执行流程图(人工理解)

[开始] → 设置配置 → 创建日志文件
  ↓
[循环] → 检查节点存活(三次检测)
  ├─ 存活 → 检查是否处于隔离状态
  │        ├─ 已隔离 → 调用解除隔离函数(unisolate_node)
  │        └─ 未隔离 → 记录“健康”日志,继续监控
  └─ 死亡 → 检查连续失败次数是否≥阈值
           ├─ 是 → 检查是否已经隔离
           │       ├─ 已隔离 → 跳过(避免重复操作)
           │       └─ 未隔离 → 执行隔离(isolate_node)
           └─ 否 → 累加失败计数器,等待下一次检测
[结束] → 脚本退出(通常由crontab或systemd管理,持续运行)

3 错误处理与日志规范

  • 日志级别:INFO(正常状态)、WARN(检测失败但未触发)、ERROR(触发隔离)、ALERT(需要人工介入)
  • 日志格式:统一时间戳 + 级别 + 节点IP + 操作 + 原因

4 安全注意事项

  1. 脚本存储位置:放在 /opt/scripts/ 并设置 chmod 750,只有 rootops 组可读。
  2. 敏感信息硬编码:严禁将密码写在脚本里,健康检查建议使用Token或证书。
  3. 防止反复触发:在脚本开始处增加全局锁(flock -n /tmp/node_isolate.lock),避免多个进程同时执行。

编写一个“合格”的节点隔离处理脚本,其实是在做一道容错算法 + 自动化运维的综合题,它是集群自我修复的最后防线,写得好可以0人工干预保持系统稳定,写得不好反而成为“雪崩的导火索”。

希望这篇文章能帮你从“写一个脚本”进化到“设计一个可靠的容错系统”,如果你在实现过程中遇到其他坑,欢迎随时交流——毕竟,集群的每一行脚本背后,都站着真实的用户流量。

抱歉,评论功能暂时关闭!