印度机房用 curl 探活,DNS 缓存把故障藏了四十分钟
印度机房,Ubuntu 22.04,主机上跑一个后端服务,前面一层 Nginx 反代到内部两个上游节点。上游节点 IP 变化比较频繁,节点重启后由内部 DNS 重新分配地址。探活脚本每 30 秒跑一次,用 curl 请求 /healthz,连续三次失败就发告警。脚本跑了大半年没出过事。
上周出事了。下游调用方报 502,持续了大概四十分钟,而探活脚本的日志里一直是 200。手动执行同一条命令,返回依然正常。把 curl 换成 curl -v 才看到关键一行:Trying 10.x.x.x...,那是一个已经下线的旧上游 IP。也就是说,服务实际在连一个不存在的地址,探活却告诉我是健康的。
根因在 curl 的 DNS 缓存。这台主机上装了 nscd,之前为了减少 DNS 查询压力配的,positive-time-to-live 默认是 3600 秒。上游地址切换后,nscd 里还留着旧记录,curl 每次走缓存都拿到旧 IP。更麻烦的是,旧 IP 上还有一台机器在跑一个残存的旧进程,/healthz 恰好也返回 200——所以探活是真的“成功”了,只是成功得毫无意义。
做法上分两步。先让探活绕开一切缓存,直接问 DNS:
# 显式指定 DNS 解析结果,跳过 nscd 与解析器缓存
curl --resolve "api.internal:8080:$(dig +short api.internal | head -n1)" \
-fsS --max-time 3 http://api.internal:8080/healthz
第二步,让健康检查校验内容而不只是状态码:
resp=$(curl -fsS --max-time 3 \
--resolve "api.internal:8080:$(dig +short api.internal | head -n1)" \
http://api.internal:8080/healthz)
echo "$resp" | grep -q '"build":"2024.11.3"' || exit 1
build 字段是上游发布时写死的,旧进程不可能有新的 build 号,这样旧 IP 上的残存进程就骗不过检查了。
为什么这样做:--resolve 把 DNS 查询从 curl 内部挪到了脚本里,每次探活都重新解析,缓存时间等于脚本的执行间隔,代价是每 30 秒多一次 DNS 查询,内网 DNS 完全扛得住。校验 build 号则是把“进程活着”和“是我要的那个版本活着”区分开。代价是每次发布都要同步改脚本里的 build 字符串,所以我把这行写进了发布清单,不靠记忆。
会失效的地方:dig +short 返回多行时 head -n1 只取第一个,如果 DNS 轮询返回多个 A 记录,探活只会覆盖其中一个,需要改成逐个请求。--resolve 里的端口必须和 URL 里的端口一致,写成 80 会静默连不上。还有一次 nscd 被清掉后忘了重装,脚本没问题,但别的服务又开始重复解析,这说明修探活和修根因是两件事。
你们的探活是只看状态码,还是会校验返回内容里的版本或实例标识?如果上游地址会变,DNS 缓存这一层你们是怎么处理的?
0 条评论
还没有评论,写下第一条。