跳到主要内容
版本:1.2.0

健康检查

AISIX 为进程、流量、配置和模型状态分别提供健康与状态端点。这些端点同时适用于开源 AISIX 网关和连接 AISIX Cloud 的 AISIX 网关。请使用与所监控条件对应的端点。要验证从调用方到模型服务提供方的完整路径,请通过网关发送测试请求。

问题端点监听器
这个实例是否应该被重启?GET /livez代理
此实例是否应接收代理流量?GET /readyz代理
此实例是否已应用任何有效配置?GET /status/ready指标/状态
此实例当前使用什么配置提供服务?GET /status/config指标/状态
模型是否可用于路由?GET /status/models指标/状态

这些端点不需要身份认证。启用 Prometheus 指标时会提供指标/状态端点;默认情况下已启用。请确保该监听器仅对监控和运维系统开放。

代理存活检查

使用 /livez 检查进程存活状态:

curl -i "http://127.0.0.1:3000/livez"

健康进程返回 200 OK,响应体为 ok

正在排空的实例同样返回 200。存活检查决定的是要不要重启实例,而排空是有意为之的工作:一个已被告知关闭的实例正在处理完它已经接收的请求,重启它恰好会杀掉这些请求。报告排空状态的是 /readyz,这样流量会被撤走,而实例不会在自己仍有进行中请求时被替换掉。

手动排查时可以追加 ?verbose=1。自动化探针不要依赖 verbose 响应体。

存活检查刻意保持较窄范围。它不能证明模型可用,也不能证明模型服务提供方请求能够成功。

它确实意味着网关已经加载了配置,但这只是承载该端点的监听器何时存在所带来的副作用。从 etcd 或 AISIX Cloud 读取资源的网关,在应用第一个配置之前不会绑定代理监听器,因此在那之前 /livez 不是返回 503,而是根本不会有响应。参见启动与第一个配置

流量就绪

使用 /readyz 判断实例是否应接收流量:

curl -i "http://127.0.0.1:3000/readyz"

实例正在排空时,该端点返回 503 Service Unavailable。有效配置可用后,只要网关仍能使用该配置提供服务,实例就会保持就绪。

在应用第一个配置之前,/readyz 的表现取决于资源来源;对于使用 etcd 或 AISIX Cloud 的网关,得到的并不是 503:承载该端点的代理监听器尚未绑定,探针只会得到被拒绝的连接。要观察这一阶段,请使用指标/状态监听器上的 GET /status/ready,参见启动与第一个配置

控制面或配置存储中断不会仅仅因为最近没有更新就让运行中的网关变为未就绪。来源停滞通常会影响所有实例,将它们全部移出流量会中断流量路径,而不是把流量转移到健康实例。请单独监控配置新鲜度。

排查实例未就绪的原因时可以追加 ?verbose=1。自动化探针不要依赖 verbose 响应体。

在 Kubernetes 中,请将存活和就绪探针分别指向代理监听器上的 /livez/readyz,并在同一个监听器上再配置一个 startupProbe,使网关仍在连接配置来源期间另外两个探针都不会动作。该探针需要多大的预算,参见启动与第一个配置。请为网关留出足够的终止时间,以排空进行中的请求和流式请求。

启动与第一个配置

资源来自 etcd 或 AISIX Cloud 的网关,只有在应用第一个配置之后才会绑定代理监听器。这是无条件的,也没有对应的开关:从未应用过配置的实例没有任何东西可以提供服务,而把「端口可以连上」当作「这个实例已就绪」的流量层会把调用方路由过来,然后看到每个请求都被拒绝。

有两类启动过程不受影响,会立即绑定监听器,因为在设置监听器时它们已经持有配置:使用资源文件的网关会在启动时加载该文件,加载不成功就退出;以及从磁盘缓存恢复了可用快照的 etcd 或 AISIX Cloud 网关。

等待期间,各监听器的表现并不相同:

监听器等待期间的表现
代理未绑定。/livez/readyz 以及所有面向调用方的端点都会拒绝连接。
指标/状态启用 Prometheus 指标时,进程启动即绑定。GET /status/ready 返回 503 Service Unavailable,响应体为 no configuration availableGET /status/config 报告状态 never_loaded
Admin开源网关启用该监听器时,进程启动即绑定。等待期间它自己的 /livez 返回 200/readyz 返回 503

有一种启动过程根本到不了上面这张表,在把拒绝连接的指标端口当成进程已死之前,值得先知道这一点。设置了 etcd.user 时,网关会在创建任何监听器之前先去连接 etcd,而 etcd.dial_timeout_ms 默认不设置——因此一个完成了 TCP 连接却不作任何应答的端点,会让进程无限期地停在那里,代理、指标/状态和 Admin 三个监听器都还没有打开。它此时会写出的,是下面表中那条 still connecting to etcd 告警,每 10 秒一条,并带上它正在等待的端点。设置 dial_timeout_ms 会把这次连接变成一次有界的、网关会重试的失败,此后它就会进入这里描述的等待。

因此,观察冷启动要看指标/状态监听器。连接 AISIX Cloud 的网关从不绑定 Admin 监听器,此时 GET /status/ready 是唯一会响应的健康端点,也是区分「网关仍在连接配置来源」与「网关根本没有运行」的唯一手段。绑定了 Admin 监听器的开源网关还能用该监听器上的 /livez/readyz——但只有指标/状态端点能说明这段等待的原因。

代理地址和 proxy.tls 证书材料仍会在等待开始之前于启动阶段校验,因此地址不可用或证书文件不可读仍然是启动失败,而不会被推迟到后面某个时刻。

网关不会放弃。在没有应用任何配置期间,它既不会退出,也不会绑定一个降级的监听器,并会在某次读取成功的那一刻绑定监听器。读取失败时,它按指数退避重试,退避从 1 秒增长到 60 秒上限。

有一类故障不在等待之列,区别在于 etcd 作出了怎样的响应。根本连不上的 etcd——连接被拒绝、DNS 失败、TLS 失败,或者建立连接超过了 etcd.dial_timeout_ms——正是这段等待所针对的情况,而且无论是否设置 etcd.user,行为都一样。etcd 在启动那次连接上作出响应并拒绝的凭据则相反。设置了 etcd.user 时,网关会在建立连接的过程中完成认证,因此密码不对——或者把凭据发给了一个根本没有开启认证的集群——会当场被拒绝。等待无法把它变成可用的连接,因此网关会报出这次拒绝并退出,而不是起来之后永远空着。etcd 不再接受的 Token 归入前一类而不是这一类,因为网关靠重新认证就能把它治好;参见 etcd 配置存储

比那次连接更晚到达的拒绝不会结束进程,而这恰恰是更值得认出来的一种情况。etcd 的权限是逐次调用校验的,而不是在认证时校验,因此一个密码正确、却没有 etcd.prefix 读权限的用户,会在启动时被接受、在配置读取时被拒绝;而一个在 etcd 不可达时启动的网关,即使密码是错的,也会改为在那里才收到拒绝。这两种都不会退出:网关保持运行、代理监听器不绑定,按同样的退避重试,并在每一次尝试时以 ERROR 写出 etcd refused this gateway's credentials — no configuration can be read until they are fixed; still retrying。这种情况在 /status/config 上表现为 source.connected: falsestatenever_loaded,与来源根本连不上时完全一样,因此把两者区分开的正是这条日志。

被接受却始终没有响应的读取既不算成功也不算失败。它默认不受任何超时限制,会一直处于在途状态,退避重试也就永远等不到一次可供重试的失败。设置 etcd.request_timeout_ms 可以给配置读取加上上限,把这种挂起变成退避会重试的失败。它同样会限制建立配置 watch 的握手。那是这段等待之后一步的故障,而不是这段等待的一部分——读取此时已经成功,第一个配置已经应用,监听器也已经绑定——但它是更危险的那一半:etcd 能正常响应读取、却始终不确认 watch 时,网关本会一直提供这第一份快照,对之后的每一次变更都视而不见,而 /status/config 仍然报告来源已连接。已经建立起来的 watch 流则有意不受它约束,因为任何这类上限都会在一段安静到没有产生事件的时间里到期,让网关不断重连而不是持续 watch。设置了 etcd.user 时在建立连接过程中进行的认证交互,也不再游离于所有超时设置之外:启动阶段由 etcd.dial_timeout_ms 覆盖,之后某次调用需要自行建立连接时由 request_timeout_ms 覆盖。不过这两个键默认都不设置,因此在默认部署上,在你设置其中之一以前它仍然不受任何约束。设置该项前请先阅读那里的说明,因为一个相对配置规模过短的上限只会把挂起换成一次永远无法完成的读取。无论哪种情况,网关都会把这段等待记录到自己的日志中:

日志内容级别写出时机
waiting for the first configuration before binding the proxy listenerINFO等待开始时写出一次。
proxy listener still not bound: no configuration has been applied yetWARN等待期间每 10 秒写出一次。
first configuration applied — binding the proxy listenerINFO监听器绑定时写出一次。
still connecting to etcd — nothing waiting on this connection can proceed until it answersWARN与 etcd 的连接尚未完成期间每 10 秒写出一条,带 endpointswaited_secs。只有设置了 etcd.user 时才会出现,因为没有 etcd 凭据的网关要到第一次读取时才会去连接。

设置 Kubernetes 启动探针的预算

代理监听器上的 startupProbe 会在整个启动过程(包含上述等待)中挡住存活和就绪探针。它的预算是 periodSeconds x failureThreshold,需要覆盖的是连接配置来源并应用其中内容的时间,而不仅仅是进程启动的时间。api7/aisix Helm Chart 为它预设了 300 秒的预算,即 periodSeconds: 2 配合 failureThreshold: 150

设置该预算时,两部分都要考虑到。连接来源指的是 DNS、TLS,以及控制面或 etcd 的可用性。应用其中的内容则是另一部分工作,其耗时会随环境所持有的配置规模增长,而这部分耗时并没有被测量过——因此预设的 300 秒是为大规模配置刻意留出的余量,而不是依据某次实测启动调出来的值。探测周期仍然很短,所以普通启动依然能在几秒内通过,这份余量不会拖慢滚动更新;只有异常情况才会真的等下去。

预算在网关自身重试节奏中的落点同样重要。配置读取失败后按退避重试,退避从 1 秒开始翻倍、以 60 秒封顶,因此各次尝试大致落在 t=0、1、3、7、15、31、63 秒,之后每分钟一次。这个阶梯没有尽头,所以没有任何预算能容纳它的全部;预算应当避免的是恰好在某次尝试之前结束,那会白白浪费已经等过的时间。原来的 60 秒正是如此,比 t≈63 秒那次尝试早了三秒——于是在比如 t=40 秒就已恢复的来源,不会等到那次即将执行的重试,实例先被重启了。

配置来源持续不可达、超过该预算的 Pod,其容器会被 kubelet 杀掉并重启,反复重启会表现为 CrashLoopBackOff。这是预期结果,而不是需要绕开的故障:该实例从来就没有可以提供服务的内容。重启后的容器会继续同样的等待,并在来源恢复后立即绑定监听器。请通过 GET /status/ready 和上面的日志来定位问题,而不是把探针指向其他监听器。

关闭与排空

负载均衡器是在下一次健康检查时才知道某个实例正在退出的,而不是实例作出决定的那一刻。在这两个时间点之间,它仍然会把新连接路由过来。如果网关一收到关闭信号就关闭监听器,这段间隔内被路由过来的连接都会被拒绝,调用方在一次普通的滚动更新或缩容中就会看到网关错误。

因此网关把这两件事分开。收到 SIGTERMSIGINT 时,它会:

  1. 立即让 /readyz 返回 503 Service Unavailable,使下一次健康检查将其移出流量。/livez 保持 200:进程是健康的,排空期间不应被重启。
  2. 继续接受新连接,至少持续 shutdown.min_drain_secs,默认为 30 秒。
  3. 为每个 HTTP/1.1 响应加上 Connection: close,使使用连接池的客户端在用完连接后主动退役,而不是把空闲连接一直留着。HTTP/2 禁止该头部,因此 HTTP/2 客户端改为在排空开始的那一刻收到 GOAWAY 帧。GOAWAY 要求对端处理完已经开启的流、不要再开新流;它本身不关闭连接,也不会打断任何进行中的请求。
  4. 最小窗口结束后,不设自身期限地等待进行中请求数降为零,然后停止接受新连接。
  5. 在保留快照缓存的 etcd 或 AISIX Cloud 网关上,再最多等待 5 秒,让仍在途中的快照缓存写入落盘,然后退出。

请把 min_drain_secs 设置为大于为该实例做负载均衡的组件的发现延迟。Kubernetes 就绪探针需要 periodSeconds x failureThreshold;外部负载均衡器则需要它自身的检查间隔乘以重试次数。设置过小会在流量仍在到达时关闭监听器;设置过大只会延迟退出。

config.yaml
shutdown:
min_drain_secs: 30

这个窗口是最小值,而不是期限。窗口结束后,网关仍会等待进行中的请求数降为零,因此比配置更慢的负载均衡器无法让它在流量仍在到达时关闭监听器。设为 0 会完全取消该窗口,仅当没有任何组件通过健康检查将流量路由到该实例时才适用。

由于等待进行中工作数降为零的过程没有上限,整个流程的实际上限由部署平台决定——Kubernetes 中是 terminationGracePeriodSeconds,systemd 下是 TimeoutStopSec。请把它设置为大于 shutdown.min_drain_secs、最长请求或流式传输时长,以及 5 秒快照缓存排空这三者之和,并留出运维余量。还应加上任何 preStop 钩子的持续时间,因为它会消耗同一个 Kubernetes 终止预算。

最后这一项虽小,却不是可有可无的余量,而且它是额外的时间,不会被在途请求的排空吸收:快照缓存排空要等到进行中请求数降为零之后才开始。网关在应用配置之后会在后台写快照缓存,因此一个刚应用完配置就被停止的实例,可能仍有一次写入没有完成。如果它在写入中途被杀掉,重启时用的就是缓存中此前保存的内容,而不是它刚刚应用的那份配置——如果那次写入本身就是第一次,则根本没有缓存可用。由于代理监听器要等到第一个配置被应用才绑定,没有缓存的网关在重新连上配置来源之前不会再打开端口。这 5 秒是对一次本地文件写入的固定兜底,而不是可调的旋钮,也没有对应的配置项。5 秒过后写入仍在途中时,网关会写出一条 WARN 并不再等待;这次写入是被放弃等待而不是被取消,而缓存文件的替换是原子的,因此落到磁盘上的一定是最近某一次应用的结果,绝不会是写了一半的内容。

备注

请把外部负载均衡器的健康检查指向 /readyz,而不是单纯的 TCP 连接探测。TCP 探测无法观察到就绪状态,它唯一能得到的信号就是监听器关闭——而这正是排空窗口要避免的那个事件。

在日志中观察排空过程

排空过程会记录自身的进展,因为一次请求通常留下的记录是在它结束时才写出的——而在平台的宽限期到期时仍在运行的请求根本走不到那一步。这些日志正是你把网关已经接手的工作,与信号之后仍被路由过来的流量区分开的依据:

日志内容字段写出时机
draining — /readyz now reports 503, still accepting new connectionsmin_drain_secsin_flightopen_connections收到信号时写出一次。
still draining in-flight requestsin_flightopen_connections排空期间周期性写出。
request arrived while drainingmethodpath,以及该请求自身的 request_idpeerdownstream_request_id信号之后每到达一个请求写一条。
accepted a new downstream connection while drainingpeeropen_connections信号之后每接受一条连接写一条。

这两个计数回答的是不同的问题,且无法互相推导。in_flight 统计正在处理的请求,流式响应只要还可能有字节流出就一直占着自己的名额;open_connections 统计代理监听器上处于打开状态的下游连接,包含连接池中没有承载请求、正处于空闲的那些。因此,in_flight 一直不降导致排空迟迟不结束,说明网关在处理自己已经接手的工作;in_flight 已降为零而 open_connections 仍不降,说明客户端握着用不到的连接不放;open_connections 还在上升,则说明仍有组件在往这里路由新连接。

那两条逐事件的日志合起来解释了滚动更新期间失败的请求。一条到达日志若能匹配到 peer 相同的接受日志,说明连接本身是在网关已经请求被摘除之后才被路由过来的——负载均衡器还没跟上,而这正是 min_drain_secs 要吸收的情况。一条到达日志若匹配不到接受日志,说明该连接早于信号存在,是客户端从自己的连接池中复用的,而这正是 Connection: close 响应头负责退役的对象。

不过接受日志本身无法区分你的调用方和你的平台。/livez/readyz 同样由代理监听器提供服务,探针在整个排空期间会持续以各自独立的连接到达,因此它们会抬高 open_connections,也会像其他连接一样产生接受日志。作出这项判断的是到达日志:它知道请求路径,并有意排除这两个探针端点,以免为数不多的真实请求被每隔几秒一次的探针淹没。接受日志则用来看到达日志看不到的情况——一条建立了却从未被使用的连接。

peerdownstream_request_id 就是指标与日志中描述的那组连接标识。把它们与网关前置组件自己的记录相互匹配,就能证明一条网关日志和一条负载均衡器日志说的是同一条连接。

配置状态

指标/状态监听器提供两个配置检查。

GET /status/ready 是仅检查配置的启动门禁:

  • 应用第一个有效配置之前返回 503 Service Unavailable
  • 有效配置可用后返回 200 OK;后续更新失败而 AISIX 使用最后已知的有效快照时也会返回 200 OK

对于使用 etcd 或 AISIX Cloud 的网关,应用第一个配置之前应当观察的就是这个端点,因为在那之前代理监听器尚未绑定。参见启动与第一个配置

GET /status/config 用于说明 AISIX 观察到和应用的内容:

curl -sS "http://127.0.0.1:9090/status/config"

当资源更新没有反映到代理行为中时,请使用该端点。比较来源状态与已应用状态,然后检查被拒绝的资源和最近一次加载失败。有关完整响应字段、状态含义、Prometheus 指标和告警示例,请参阅配置状态

配置状态不能替代调用方路径验证。预期快照应用后,请查询 GET /v1/models,并发送行为发生变更的请求。请参阅配置传播

按模型检查运行时健康状态

当网关已就绪,但路由避开某个模型或报告没有符合条件的目标时,请使用 GET /status/models

curl -sS "http://127.0.0.1:9090/status/models"

每个已配置模型会报告以下高层状态之一:

  • healthy:可用于路由;
  • cooldown:在最近的上游失败后暂时移出路由;只有开启了冷却的模型才会进入该状态;
  • unhealthy:后台模型检查失败后被排除;
  • not_applicable:可用性由其目标决定的虚拟模型。

状态视图有助于识别冷却和后台检查失败,但不会验证调用方访问权限或模型服务提供方凭证。即使模型报告为健康,调用方 API Key、模型服务提供方密钥或上游响应仍可能导致请求失败。

有关完整响应字段,请参阅配置状态

组合健康信号

请从最早失败的层级开始排查:

信号下一项检查
/livez/readyz 拒绝连接。代理监听器是否已经绑定。对于使用 etcd 或 AISIX Cloud 的网关,在应用第一个配置之前它不会绑定;请检查 /status/ready 和配置来源。
/livez 失败。进程状态、监听器绑定和监听器 TLS
/readyz 失败。排空状态
/status/ready 失败。初始配置来源和加载错误
/status/configdegradedout_of_sync被拒绝的资源、来源连接和 last_failure
/status/models 报告 cooldownunhealthy模型服务提供方凭证、提供方可用性、模型检查和出站网络
所有健康端点均成功,但请求失败。调用方访问权限、模型服务提供方路径、策略执行和上游响应

最后,请使用与应用相同的路径进行检查:

AISIX_API_KEY="YOUR_CALLER_API_KEY"

curl -sS "http://127.0.0.1:3000/v1/models" \
-H "Authorization: Bearer ${AISIX_API_KEY}"

然后通过所需的端点和模型发送真实请求。最后这项探测会验证运行时健康端点刻意不检查的条件。

下一步

使用故障排除,将失败的健康检查或请求路径检查进一步定位到配置、调用方策略、AISIX Cloud 投射或上游模型服务提供方。