1. 问题的现场:一切配置都正常,实例却一直红着
先说结论:大多数时候,Health Check 显示 Unhealthy 并不是应用真的挂了,而是探活路径和 App Service 的探活机制之间出现了认知偏差。
我在接手一个老项目时遇到的情况是这样的:应用是一个部署在 Azure App Service(Windows 计划)上的 .NET Core Web API,后端依赖 Azure SQL 和 Redis。功能一切正常,日志没有任何异常,从公网访问接口响应都在 200ms 以内。但在 Azure 门户的 App Service 概览页上,实例列表里每个实例的健康状态都显示为 "Unhealthy",而且是持续性的,不是偶发。
当时我第一反应是——是不是应用池回收了?是不是某个实例的内存爆了?于是打开诊断日志、Application Insights、指标面板翻了一圈,发现 CPU、内存、请求量全部正常,唯一扎眼的只有 Health Check 状态。
后来我干脆在浏览器里手动请求 Health Check 路径,发现接口本身返回 200 OK,响应体也是标准的 Healthy 字符串。这就很诡异了:路径能通、返回正确、服务正常,但平台探活却判定为不健康。
如果你也遇到类似现象,别急着改代码。这篇文章从根因到排查链路,把 Health Check 的判定机制和实际踩坑点完整拆开讲。整个过程基于 Windows App Service 计划,但很多原理在 Linux 容器方案下同样适用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Health Check 的探活机制:平台到底在检查什么
2.1 探活请求和普通请求的区别
Azure App Service 的 Health Check 功能,本质上是在服务前面加了一个探活器,周期性地向指定路径发送 HTTP 请求。它和我们手动用浏览器或 curl 访问的区别在于:
- 探活请求带有特定的 User-Agent 头(通常是
AlwaysOn或类似标记),在部分日志系统里能看到。 - 探活请求的期望响应码是 200-399,超出这个范围直接判定为 Unhealthy。
- 探活请求的频率不是固定的,平台会根据实例的健康状态动态调整,健康状态下约每 40 秒一次,异常时会加速。
- 探活请求对响应时间敏感。如果连续请求在指定时间窗口内没有返回,会被视为超时。
关键点在于第三和第四点。很多人只关注返回码,忽略了响应时间的窗口限制,这就容易产生"手动访问是好的,平台判定是坏的"这种认知差。
2.2 判定 Unhealthy 的阈值和下架流程
平台内部对实例的健康判定遵循这样的逻辑:
- 探活请求发出后,如果返回码不在 200-399 范围内,标记一次失败。
- 如果响应超时或连接被拒绝,同样标记一次失败。
- 连续失败达到一定次数(实际实现中通常是 2-3 次连续失败),实例被标记为 Unhealthy。
- 实例一旦被标记为 Unhealthy,平台会将其从负载均衡轮转中移除,也就是说新的请求不会再路由到这个实例上。
- 移除后,平台仍会继续发送探活请求,如果实例恢复响应,会重新加入轮转。
这个机制本身的容错性还可以,它不会因为单次探活失败就立刻下架实例,只有连续失败才会触发移除。但问题也在这里——如果某个实例处于"间歇性超时"状态,比如每 40 秒的探活中有 2 次超时、1 次成功,那么它就会在 Healthy 和 Unhealthy 之间反复横跳,但用户看到的往往是最终停留在 Unhealthy 状态。
2.3 探活路径推荐和重定向陷阱
Health Check 配置页面上可以填一个路径,比如 /health 或 /healthcheck。配置本身非常简单,但有个隐藏的坑:如果该路径返回的是一个重定向响应(301/302),探活会判定为失败,因为重定向不在 200-399 范围内。 这里的 200-399 是严格的有效范围吗?实际上官方文档说的是 2xx 和 3xx 都算成功。
但是,很多应用的中间件会做 HTTPS 重定向或路径重写。如果你的 Health Check 路径被某个中间件拦截,返回了一个 301 或 302 到另一个路径,平台会跟随重定向吗?实测结果是不跟随,而是直接判定失败。所以,Health Check 路径必须是最终响应,不能是重定向链。
另外,如果应用启用
