就绪检查与异常检测看的是不同的东西
一句话总结
Kubernetes 依据 Pod 自己报告的状态(就绪检查)来摘除流量。网格的异常值检测则依据 sidecar 实际收到的响应来摘除。对于通过了就绪检查、却对每个请求都返回 500 的 Pod,前一种眼睛看它好好的,只有后一种眼睛才看出它坏了。
为什么需要它
就绪检查通常查看 /healthz 这样的轻量路径。但故障常常出在这条路径之外——数据库连接池耗尽,或者某个配置出错的一个 Pod 对特定请求返回 500。就绪检查依然是 200,所以 Kubernetes 会让该 Pod 一直留在端点中。如果有三个 Pod,三分之一的请求会失败,在有人查看日志之前一直如此。
客户端一侧的代理会直接收到响应,所以能发现这一点。同一个上游连续失败,只要把这个上游暂时摘除即可——Envoy 的异常值检测(outlier detection)做的就是这件事,Istio 用 DestinationRule 的 outlierDetection 来开启。
工作原理
异常值检测——逐个单独观察上游主机。
| DestinationRule | Envoy 集群 | 含义 |
|---|---|---|
consecutive5xxErrors: 2 |
consecutive5xx: 2 |
连续 5xx 达到这个数量就驱逐 |
interval: 2s |
interval: "2s" |
判定是否驱逐的周期 |
baseEjectionTime: 60s |
baseEjectionTime: "60s" |
一次驱逐的基本时长 |
maxEjectionPercent: 50 |
maxEjectionPercent: 50 |
一次最多可以摘除的主机的最大比例 |
驱逐只发生在该 sidecar 的负载均衡列表中。Kubernetes 端点保持不变,其他客户端的 sidecar 会根据自己收到的响应单独判断。被驱逐的主机在基本驱逐时间过后会回来,如果再次失败,则按被驱逐的次数成比例地被摘除更久。maxEjectionPercent 是一道安全装置,防止故障蔓延到所有主机时把它们全部摘除、导致哪里都去不了。
让它可见。Istio 默认会过滤 sidecar 输出的 Envoy 统计。要查看驱逐次数(outlier_detection.ejections_enforced_total),需要通过 Pod 注解 proxy.istio.io/config 的 proxyStatsMatcher 把这项统计放行,而代理是在启动时读取它的,所以要重新创建 Pod。被驱逐的主机在 istioctl proxy-config endpoints 中会显示 OUTLIER CHECK 为 FAILED。
流量镜像——VirtualService 的 mirror 会复制请求并发给另一个服务,并丢弃它的响应。副本带着与原请求相同的 x-request-id,所以可以在接收方日志中找到对应项。用生产流量测试新版本,同时只把原版本的响应返回给用户。
故障注入——VirtualService 的 fault 由客户端一侧的 sidecar 在发送请求之前注入延迟(delay)或中止(abort)。被中止的请求不会到达上游,访问日志的响应标志会记为 FI。加上请求头条件后,就可以只破坏正在测试的请求。
在现场相遇的样子
“三个 Pod 里有一个一直返回 500,却没有人发现”——这是就绪检查能通过的故障。配置了异常值检测后,用户看到的错误会在几次之后停止。作为代价,故障本身被掩盖了,所以要给驱逐统计设置告警。
“开启异常值检测后,主机全被摘除了”——上游整体变慢时,如果把 maxEjectionPercent 设为 100,就会这样。全部摘除后无处可去,反而使故障扩大。
“开启流量镜像后,订单在新版本一侧的数据库里写入了两次”——镜像丢弃的只是响应,请求是真的被处理了。有副作用的请求(写入)要另外把它从镜像目标中排除。
官方文档:DestinationRule — OutlierDetection、Mirroring、Fault Injection、Envoy outlier detection、ProxyConfig proxyStatsMatcher
下一项实验要做什么
把一个始终返回 500 的 Pod 与两个正常的 Pod 放在同一个服务后面,先测量失败率。放行统计之后,配置异常值检测,确认错误停止,以及 Envoy 已经驱逐了那个 Pod,然后加入流量镜像和带请求头条件的故障注入。最后取出这些配置变成了 Envoy 配置中的哪一栏。