用 curl 做三层部署验证:命令、配置与两个坑
为什么用 curl 做部署验证
部署完成后的验证,很多团队依赖浏览器点一遍或者等监控报警。这两种方式都有延迟。curl 是每个镜像里基本都有的工具,脚本化成本低,退出码可以直接用来判断成败,适合放进 CI/CD 或者发布后的 post-deploy 步骤。下面按三层组织:入口层(负载均衡或网关)、应用层(服务实例直连)、依赖层(数据库、缓存、下游 HTTP 接口)。
整体脚本约定:用 -s -o /dev/null -w "%{http_code}" 只取状态码,配合 set -e 让失败即时中断。
check() {
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$1")
[ "$code" = "$2" ] || { echo "FAIL $1 -> $code (want $2)"; exit 1; }
}第一层:入口层验证
入口层要验证三件事:域名解析到预期 IP、HTTPS 证书链完整、后端路由通。命令分别如下:
curl -sI https://api.example.com/healthz
curl -o /dev/null -s -w "%{http_code} %{time_total}\n" https://api.example.com/healthz
curl -s --resolve api.example.com:443:10.0.3.21 https://api.example.com/healthz第二条输出形如 200 0.183,可以把 time_total 超过 1 秒视为异常并打日志。第三条用 --resolve 绕过 DNS 直接打到指定后端,用来区分「DNS 问题」和「服务问题」。如果 --resolve 的请求通而普通请求不通,问题在 DNS 或 VIP,不在应用。
第二层:应用实例直连
入口正常不代表每个实例都健康。逐个实例直连,可以避免「负载均衡只把流量发到还活着的两个节点」导致的问题被掩盖。脚本里遍历实例 IP 列表:
for ip in 10.0.3.21 10.0.3.22 10.0.3.23; do
curl -s -o /dev/null -w "$ip %{http_code}\n" \
--max-time 3 -H "Host: api.example.com" \
http://$ip:8080/healthz
done注意用 -H "Host: ..." 保持虚拟主机一致,否则 Nginx 可能返回 404。这一层还要检查响应体里的关键字,例如构建版本号,确认新版本真的生效:
curl -s http://10.0.3.21:8080/healthz | grep -q '"build":"20240612-a1b2c3"'
第三层:依赖连通性
健康检查接口最好能透出依赖状态。如果没有,用 curl 直接探测下游 HTTP 依赖,例如内部用户服务:
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 \
http://user-svc.internal:9090/ping
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 \
-u "$CACHE_USER:$CACHE_PASS" http://cache-admin.internal:8090/ping数据库和 Redis 不适合用 curl,但可以把它们的连通性检查放进应用的健康检查接口里,curl 只负责取这个接口的结果。这样第三层的验证统一成一次 HTTP 请求,脚本简单。
踩坑一:连接被复用,验证了旧节点
现象:滚动更新后立刻执行 curl,所有实例状态码都正确,但访问到的还是旧版本代码。原因是在同一个 curl 进程外,HTTP/1.1 的 keep-alive 连接被复用,或者中间有代理缓存了响应。
解决办法:在验证脚本里显式关闭连接复用、禁用缓存,并加一个唯一的查询参数:
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Connection: close" \
-H "Cache-Control: no-cache" \
"https://api.example.com/healthz?v=$(date +%s)"另外把版本号比对放进断言,不要只看 200。只看状态码会在「旧版本也健康」时给出假阳性。
踩坑二:默认跟随重定向与超时缺失
现象有两个。第一,入口返回 301 跳转到登录页,不加 -L 时状态码是 301,脚本以为失败;加了 -L 后状态码变成 200,但其实是登录页,验证完全失去意义。第二,没有 --max-time 时,某个实例的网络半断开会让脚本卡住很久,发布流水线超时。
解决办法:验证脚本一律不加 -L,把 301/302 当成失败处理,因为健康检查端点和业务 API 都不应该重定向;同时对每个请求加 --max-time 3 和 --connect-timeout 2。如果确实需要跟随跳转,用 -w "%{url_effective}" 打印最终 URL 并在脚本里检查它是否等于预期值:
final=$(curl -sL -o /dev/null -w "%{url_effective}" "$URL")
[ "$final" = "$URL" ] || { echo "redirected to $final"; exit 1; }小结
三层验证分别回答三个问题:入口能不能通、每个实例是不是新版本、下游依赖有没有断。命令要点是 -s -o /dev/null -w 取状态码、--resolve 定位 DNS 与 VIP、--max-time 防止挂起、-H "Connection: close" 防止复用旧连接、不加 -L 且断言响应体里的版本号。这些检查跑完通常在 3 秒以内,可以直接放进发布流程的最后一步。
- 点赞
- 收藏
- 关注作者
评论(0)