本文目录导读:

- 核心逻辑
- 场景一:使用 Nginx + OpenResty(或 Lua)动态摘除
- 场景二:使用 HAProxy(通过 Socket 动态摘除)
- 场景三:云服务商负载均衡(如阿里云SLB、腾讯云CLB)
- 场景四:Kubernetes(Service + Endpoint)
- 场景五:自研网关 / 配置中心(如 Consul、Etcd)
- 重要注意事项(防坑指南)
要用脚本自动摘除故障后端,通常需要结合健康检查和负载均衡器/网关的API,具体实现方式取决于你的技术栈(如 Nginx、HAProxy、Kubernetes、云服务商CLB、自研网关等)。
以下是针对不同场景的通用思路和脚本示例(以 Shell 和 Python 为主):
核心逻辑
- 健康检查:对后端服务进行 HTTP/TCP 探测。
- 判断故障:连续失败N次(例如3次)则判为故障。
- 自动摘除:调用负载均衡的 API 或修改配置文件,将该后端从上游列表移除。
- 通知/记录:发送告警或写入日志。
使用 Nginx + OpenResty(或 Lua)动态摘除
Nginx 原生不支持运行时 API 修改 upstream,但可以通过 OpenResty 或 nginx-upsync 模块配合 Consul 实现动态摘除。
方案:写一个 Shell 脚本定时探测,并调用 Consul API 注销节点
#!/bin/bash
# 脚本:health_check_remove.sh
# 作用:检测后端服务健康,不健康则从 Consul(或直接修改 upstream)摘除
UPSTREAM_NAME="backend_servers"
CHECK_URL="http://192.168.1.10:8080/health"
SERVICE_ID="backend-1"
# 检测函数
check_health() {
local url=$1
# curl 探测,只获取 HTTP 状态码(-o /dev/null -s -w "%{http_code}")
status=$(curl -o /dev/null -s -w "%{http_code}" --connect-timeout 3 --max-time 5 "$url")
if [ "$status" -eq 200 ]; then
return 0
else
return 1
fi
}
# 摘除函数(以 Consul 为例)
deregister_consul() {
local service_id=$1
# 调用 Consul 的注销 API
curl -X PUT "http://localhost:8500/v1/agent/service/deregister/$service_id"
echo "[$(date)] 摘除 $service_id 从 Consul"
}
# 主逻辑
if ! check_health "$CHECK_URL"; then
deregister_consul "$SERVICE_ID"
# 可在此发送告警(如钉钉、邮件)
else
echo "[$(date)] 服务正常"
fi
配合定时任务 (crontab):
*/1 * * * * /path/to/health_check_remove.sh >> /var/log/remove_backend.log 2>&1
使用 HAProxy(通过 Socket 动态摘除)
HAProxy 支持通过 Unix Socket 或 Stats Socket 在运行时动态启用/禁用服务器。
#!/bin/bash
# 脚本:haproxy_remove.sh
# 依赖命令:socat (需要安装)
HAPROXY_SOCK="/var/run/haproxy/admin.sock"
BACKEND_NAME="mybackend"
SERVER_NAME="web1"
CHECK_URL="http://192.168.1.11:80/health"
check_health() {
local url=$1
# 探测 3 次,连续失败才判为故障(避免误判)
for i in {1..3}; do
status=$(curl -o /dev/null -s -w "%{http_code}" --connect-timeout 2 "$url")
if [ "$status" == "200" ]; then
return 0 # 立即返回健康
fi
sleep 1
done
return 1 # 3次都失败,判为故障
}
if ! check_health "$CHECK_URL"; then
# 通过 socat 发送 HAProxy 命令,禁用该服务器
echo "disable server $BACKEND_NAME/$SERVER_NAME" | socat stdio "$HAPROXY_SOCK"
echo "[$(date)] HAProxy 摘除 $BACKEND_NAME/$SERVER_NAME"
# 可选:发送告警
else
# 如果之前被禁用了,可以自动恢复(也可以另写脚本恢复)
# echo "enable server $BACKEND_NAME/$SERVER_NAME" | socat stdio "$HAPROXY_SOCK"
echo "[$(date)] 服务正常"
fi
云服务商负载均衡(如阿里云SLB、腾讯云CLB)
云厂商一般提供 SDK 或 CLI 来操作后端服务器,示例使用阿里云 CLI(需配置 AccessKey)。
前提: 安装 aliyun CLI 并配置凭证。
#!/bin/bash
# 脚本:alicloud_remove_backend.sh
REGION="cn-hangzhou"
SLB_ID="lb-xxxxx"
BACKEND_SERVER_IP="192.168.1.20"
BACKEND_PORT=80
CHECK_URL="http://192.168.1.20:80/health"
check_health() {
local url=$1
status=$(curl -o /dev/null -s -w "%{http_code}" --connect-timeout 3 "$url")
if [ "$status" == "200" ]; then
return 0
fi
return 1
}
if ! check_health "$CHECK_URL"; then
# 调用阿里云 CLI 摘除后端服务器
aliyun slb RemoveBackendServers \
--RegionId $REGION \
--LoadBalancerId $SLB_ID \
--BackendServers "[{\"ServerId\":\"$BACKEND_SERVER_IP\",\"Port\":$BACKEND_PORT}]"
# 也可以使用 Python SDK 更灵活
echo "[$(date)] 从 SLB 摘除 $BACKEND_SERVER_IP:$BACKEND_PORT"
else
echo "[$(date)] 健康检查通过"
fi
Kubernetes(Service + Endpoint)
如果你使用 K8s,故障后端通常由 Pod 的 livenessProbe 和 readinessProbe 自动处理,Pod 不健康,K8s 会自动将其从 Service 的 Endpoint 摘除。
但如果需要主动摘除一个节点(例如机器故障),可以用脚本:
#!/bin/bash
# 脚本:k8s_remove_pod.sh
# 通过 patch 方式将 Pod 标记为不可调度或者删除
NAMESPACE="default"
POD_NAME="my-app-xxxxx"
# 摘除方式1:直接删除 Pod(K8s 会重新创建,但新 Pod 可能仍在故障节点)
# kubectl delete pod $POD_NAME -n $NAMESPACE
# 摘除方式2:给节点打污点,驱逐所有 Pod(更彻底)
# kubectl taint nodes node1 key=value:NoSchedule
# 摘除方式3:通过修改 Service 的 Endpoint(不建议手动操作,应依靠 readiness probe)
# kubectl patch svc my-service -p '{"spec":{"externalTrafficPolicy":"Local"}}'
echo "[$(date)] 执行 K8s 摘除逻辑"
最佳实践: 在 K8s 中,不要用外部脚本来摘除 Pod(除非是临时维护),应该依赖于 Kubelet 的探活,如果探活失败,Pod 会被自动踢出 Service。
自研网关 / 配置中心(如 Consul、Etcd)
如果你的网关从 Consul 或 Etcd 读取服务列表,可以写一个 Watch 脚本。
Python 脚本示例(使用 Consul 库):
import requests
import consul
def check_and_remove():
c = consul.Consul()
service_name = "my-backend"
check_url = "http://192.168.1.10:8080/health"
try:
r = requests.get(check_url, timeout=3)
if r.status_code != 200:
# 从 Consul 注销
c.agent.service.deregister(f"{service_name}-10")
print(f"[{datetime.now()}] 摘除 {service_name}-10")
except:
# 网络异常也视为故障
c.agent.service.deregister(f"{service_name}-10")
print(f"[{datetime.now()}] 异常,摘除 {service_name}-10")
if __name__ == "__main__":
import time
while True:
check_and_remove()
time.sleep(5)
重要注意事项(防坑指南)
- 避免误判:不要一次失败就摘除,建议连续失败N次(如3次,间隔2秒)再执行摘除。
- 防止脑裂:如果脚本本身所在节点网络异常,可能会误判所有后端为故障,考虑多节点探测或只摘除探测失败的特定IP。
- 幂等性:摘除操作应能重复执行不影响结果(例如再次摘除已摘除的节点不报错)。
- 恢复机制:故障恢复后,需要自动重新加入,通常可以另写一个脚本,间隔一段时间对已摘除节点进行恢复探测,或手动运维。
- 日志和告警:每次摘除动作一定要记录日志,并发送到监控系统(如Prometheus、告警群)。
| 环境 | 推荐摘除方式 | 关键工具/API |
|---|---|---|
| Nginx (静态配置) | 手动或通过 Consul 动态更新 | nginx -s reload + Consul |
| Nginx + OpenResty | 直接调用 Lua API | balancer.set_peer_down(true) |
| HAProxy | 通过 Socket 命令 | disable server (socat) |
| Kubernetes | 依赖 readiness probe | 不推荐外部脚本直接操作 Pod |
| 云 SLB/CLB | 云厂商 CLI/SDK | aliyun slb RemoveBackendServers |
| Consul / Etcd | 调用 API 注销服务 | consul agent.service.deregister |
根据你的实际负载均衡组件(Nginx、HAProxy、K8s、云LB)选择对应方案,如果需要具体某一种的详细实现(如带重试、告警、统计),可以继续追问。