HAProxy的健康检查机制是保证后端服务高可用的核心手段,但默认的TCP四层检查只能确认端口处于监听状态,无法感知应用是否真正具备处理请求的能力。比如一个Java服务端口开着,但线程池已经耗尽、数据库连接全部挂掉,此时TCP检查依然会返回成功,流量照常被转发到一台"僵尸"节点上。要解决这个问题,就需要编写自定义的健康检查脚本,让HAProxy根据脚本的退出码来决定节点状态。本文将从检查方式的选择、脚本编写规范、配置落地以及常见问题排查几个方面,完整讲解HAProxy健康检查脚本的编写方法。

一、HAProxy支持的几种健康检查方式对比
在动手写脚本之前,必须先弄清楚HAProxy提供了哪些检查能力,以及它们各自的判断逻辑。很多人一上来就写脚本,结果选错了挂载点,导致脚本根本没被调用。HAProxy的检查能力主要分为内置检查和外部检查两大类,内置检查由HAProxy进程自身完成,外部检查则通过执行脚本或访问agent端口来完成。
1. 内置检查:httpchk与tcp-check
option httpchk是最常用的七层检查方式,HAProxy会主动向后端发起一个HTTP请求,根据响应状态码判断健康状态。可以通过http-check expect指令细化判断条件,例如要求状态码为200,或者要求响应体中包含特定字符串。这种方式无需编写脚本,性能开销小,是HTTP类服务的首选。而tcp-check则用于四层检查,可以按顺序执行connect、send、expect一系列动作,模拟简单的协议交互,适合Redis、MySQL这类有握手协议的服务。
2. 外部检查:external-check与agent-check
当内置检查无法满足需求时,就需要引入脚本。external-check通过external-check path和external-check command指令在服务器节点上执行命令,命令的退出码直接决定健康状态。需要注意它依赖nbthread 1或者在较新版本中的特殊支持,且默认出于安全考虑需要显式开启external-check模块允许。agent-check则是另一种思路:HAProxy主动连接后端某个端口(由agent-addr和agent-port指定),该端口上运行的任意程序(可以是你写的脚本,也可以是systemd服务)返回一个字符串来报告状态,支持up、down、ready、drain、maint等状态值,还能携带权重百分比。agent方式的优势在于检查逻辑与HAProxy配置解耦,脚本崩溃不会影响HAProxy本身的稳定性。
3. 三种方式的选择建议
简单总结选择原则:纯HTTP服务优先用httpchk加expect,这是成本最低的方案;需要检查数据库连接、磁盘空间、依赖服务可用性等复合条件时,用external-check执行脚本;希望在节点上常驻一个轻量状态上报程序、并且支持动态调整权重(比如灰度摘流)时,用agent-check。下面我们重点讲解脚本编写部分,这也是实践中最容易出错的地方。
二、健康检查脚本的编写规范与实例
无论是external-check还是agent模式,脚本的质量直接决定监控的可靠性。HAProxy对脚本有一个硬性约定:退出码0表示检查通过,非0表示失败。除此之外,超时控制、幂等性、资源消耗这三点是脚本必须保证的,否则一个写得不好的脚本可能把整个负载均衡集群拖垮。
1. 脚本退出码与超时控制
HAProxy执行外部检查时有inter参数控制检查间隔,但脚本自身的执行时间也必须受限。如果脚本内部有curl或数据库连接操作,一定要设置超时参数,否则脚本可能无限挂起,堆积大量僵尸进程。下面是一个综合检查HTTP接口和磁盘空间的脚本示例:
#!/bin/bash
# HAProxy外部健康检查脚本
# 退出码: 0 = 健康, 1 = 不健康
# 接收HAProxy传入的参数,这里假设是目标地址
TARGET=$1
LOG_TAG="haproxy-healthcheck"
# 检查HTTP接口,5秒超时,要求返回200且响应体包含ok
HTTP_RESULT=$(curl -s -m 5 -o /tmp/hc_body -w "%{http_code}" "http://${TARGET}/healthz")
if [ "$HTTP_RESULT" != "200" ]; then
logger -t "$LOG_TAG" "HTTP check failed, code=$HTTP_RESULT target=$TARGET"
exit 1
fi
if ! grep -q "ok" /tmp/hc_body 2>/dev/null; then
logger -t "$LOG_TAG" "healthz body invalid, target=$TARGET"
exit 1
fi
# 检查磁盘使用率,超过90%判定不健康
DISK_USAGE=$(df /data | awk 'NR==2 {gsub("%",""); print $5}')
if [ "$DISK_USAGE" -gt 90 ]; then
logger -t "$LOG_TAG" "disk usage too high: ${DISK}%"
exit 1
fi
exit 0
脚本中的curl -m 5将整个请求的超时限制在5秒,配合HAProxy侧的inter间隔(例如3秒)和timeout check设置,可以保证检查节奏可控。logger命令将失败原因写入系统日志,排查问题时通过grep haproxy-healthcheck /var/log/messages即可快速定位是哪一项检查失败,这比只看到一个"DOWN"状态有用得多。
2. 避免脚本层面的常见坑
第一个坑是脚本报错被静默吞掉。如果脚本第一行没有#!/bin/bash,或者文件没有可执行权限,external-check执行时会直接失败,HAProxy把节点标记为DOWN,但日志里信息很少。务必在部署后手动执行一次脚本验证退出码:echo $?。第二个坑是脚本中产生了大量临时文件但没有清理,长期运行后磁盘被打满,健康检查反而因为磁盘问题失效,形成恶性循环。建议用mktemp创建临时文件并在脚本末尾用trap清理。第三个坑是并发问题,多进程同时执行脚本时避免竞争同一个文件,参数化命名或使用管道代替临时文件是更稳妥的做法。
三、在HAProxy配置中接入脚本
脚本写好后,需要在HAProxy配置中正确挂载。下面演示external-check方式的完整配置片段:
global
# 开启外部检查,1.9之后的版本需要显式允许
external-check
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
listen web_cluster
bind *:80
balance roundrobin
# 检查间隔3秒,连续2次失败判定DOWN,连续2次成功恢复UP
default-server inter 3s fall 2 rise 2
server web1 192.168.1.11:8080 check external-check command "/opt/scripts/healthcheck.sh 192.168.1.11:8080"
server web2 192.168.1.12:8080 check external-check command "/opt/scripts/healthcheck.sh 192.168.1.12:8080"
这里有几个关键点值得展开。external-check command后面跟的是完整命令行,参数直接写在命令里,HAProxy不会自动替换目标地址,所以每个server都要写明自己的参数。fall 2 rise 2的含义是连续两次失败才摘除节点、连续两次成功才重新加入,这个防抖机制非常重要,如果设置为fall 1,一次网络抖动就会导致节点被踢出,流量来回切换造成服务不稳定。另外,脚本路径建议使用绝对路径,并且确保HAProxy运行用户(通常是haproxy)对脚本有执行权限、对相关命令有访问权限,必要时还需要调整SELinux策略或setsebool开关。
agent-check的配置示例
如果倾向于agent方式,配置和脚本结构会有所不同。后端节点上需要一个常驻进程监听指定端口并输出状态:
backend web_backend
mode http
server web1 192.168.1.11:8080 check agent-check agent-addr 192.168.1.11 agent-port 12345 agent-inter 5s
节点侧可以用一个极简的循环脚本配合nc或socat输出状态字符串,例如输出50%表示健康但权重减半,输出down表示需要摘除。这种方式的典型用途是维护窗口:运维人员在节点上手动执行命令把agent输出切换为maint,HAProxy会在下一次agent交互时将节点转入维护模式,无需修改配置或reload。
四、检查异常与抖动的排查思路
脚本上线后最常见的两类问题是"明明服务正常但被判定DOWN"和"节点状态频繁抖动"。对于前者,先手动以HAProxy运行用户身份执行脚本:sudo -u haproxy /opt/scripts/healthcheck.sh 192.168.1.11:8080; echo $?,环境变量差异、权限不足、PATH缺失是最常见的三类原因。对于后者,重点检查脚本内的超时设置是否接近甚至超过inter间隔,以及后端服务是否存在间歇性慢响应,可以适当加大inter、提高fall值来缓解。同时建议开启HAProxy的stats页面,观察每个节点的检查状态变化历史,结合系统日志中的脚本输出交叉定位。
最后需要提醒的是,健康检查本身也是一种对后端的请求压力,检查间隔设置得过短会在大集群场景下形成可观的额外负载。合理规划inter、fall、rise三个参数,让脚本保持轻量、快速、可观测,才能让这套自定义监控方案长期稳定地运行下去。