TT Lab
开始
学习 学习路径 课程

Istio 实测实验室

通过就绪检查却返回 500 的 Pod

在 TT Lab 中继续学习

目标

在混入了始终返回 500 的 Pod 的服务中,通过响应数量和 Envoy 的端点状态,确认异常值检测会实际驱逐那个 Pod,再加入流量镜像和故障注入,查看这些配置在 Envoy 中变成了什么。

为什么重要

Kubernetes 的就绪检查看的是 Pod 自己报告的状态。对每个请求都返回 500、却通过了就绪检查的 Pod 会一直留在端点中。异常值检测依据实际收到的响应来判断,所以能把这个 Pod 摘出去,但同时也会掩盖故障。只有学会亲眼确认驱逐、并用数字统计它,才能放心地使用这项功能。

步骤

  1. 用 kubectl apply -f /opt/fixtures/istlab/resilience-app.yaml 部署材料,等待所有 Pod 就绪。然后在 client 中访问 http://api/ 30 次,并在 /root/istlab-resilience/01-baseline.txt 中写入 ok=(200 的数量)和 err=(500 的数量)。
  2. 在 /root/istlab-resilience/client.yaml 中重新写出 client Pod,并在注解 proxy.istio.io/config 中加入 proxyStatsMatcher.inclusionRegexps: [".*outlier_detection.*"],删除原有的 client 后,用这个文件重新创建。
  3. 在 /root/istlab-resilience/dr.yaml 中写入并应用 DestinationRule api(主机 api.pay.svc.cluster.local)——用 outlierDetection 设置连续 5xx 2 次(consecutive5xxErrors: 2)、检测周期 2 秒、基本驱逐时间 60 秒、最大驱逐比例 50%。然后分两轮、每轮访问 30 次,并在 /root/istlab-resilience/03-outlier.txt 中写入 err_first=(前 30 次的 500 数量)和 err_second=(后 30 次的 500 数量)。
  4. 在驱逐生效期间(第 3 步之后 60 秒内),把 istioctl proxy-config endpoints client.pay --cluster 'outbound|80||api.pay.svc.cluster.local' -o json 的输出保存到 /root/istlab-resilience/04-endpoints.json。如果已经过了 60 秒,就像第 3 步那样访问 30 次再次触发驱逐后再保存。
  5. 在 /root/istlab-resilience/vs.yaml 中写入并应用 VirtualService api(主机 api.pay.svc.cluster.local)——目的地仍为 api.pay.svc.cluster.local,mirror 为 api-v2.pay.svc.cluster.local,mirrorPercentage 为 100。应用后,把 x-request-id 分别设为 mirror-1、mirror-2、mirror-3 访问三次,并在 /root/istlab-resilience/05-mirror.txt 中写入 mirrored=(api-v2 sidecar 日志中留下的这三个 ID 的数量)和 client_saw_v2=(client 收到的正文中出现过 v2 则为 yes)两行。
  6. 在 /root/istlab-resilience/vs.yaml 的 VirtualService 前面再加两条规则——请求头 x-chaos: abort 时用 fault.abort 注入 100% 的 503,x-chaos: delay 时用 fault.delay 注入 100% 的 2 秒延迟,目的地不变(带镜像的默认规则放在最后)。应用后,在 /root/istlab-resilience/06-fault.txt 中写入 abort_code=(abort 请求的状态码)、abort_flag=(该请求在 client sidecar 访问日志中的响应标志)、delay_seconds=(delay 请求耗时,舍去小数的秒数)三行。
  7. 从 client sidecar 收到的配置中取出两项,以 JSON 对象保存到 /root/istlab-resilience/07-envoy.json——outlier:istioctl proxy-config cluster client.pay --fqdn api.pay.svc.cluster.local -o json 的第一个集群的 outlierDetection 对象,原样保存;mirror_cluster:istioctl proxy-config routes client.pay --name 80 -o json 中 api.pay.svc.cluster.local 虚拟主机的最后一条路由所带的 requestMirrorPolicies[0].cluster 的值。
  8. 在 /root/istlab-resilience/08-report.md 中写入五行——errors_before=(第 1 步的 err)、errors_after_ejection=(第 3 步的 err_second)、ejected_ip=(第 4 步文件中 failedOutlierCheck 为 true 的地址)、mirror_reached_client=(第 5 步的 client_saw_v2)、abort_flag=(第 6 步)——并在其下写出学到的内容,至少四行。

参考

混入了一个故障 Pod 的服务

用 kubectl apply -f /opt/fixtures/istlab/resilience-app.yaml 部署材料,等待所有 Pod 就绪。然后在 client 中访问 http://api/ 30 次,并在 /root/istlab-resilience/01-baseline.txt 中写入 ok=(200 的数量)和 err=(500 的数量)。

api 服务后面有三个 Pod,其中 api-bad 始终返回 500。由于没有就绪检查,Kubernetes 把三个都视为 Ready 并放入端点——在 Kubernetes 看来这是正常的 Pod。因此大约三分之一的请求会失败。要在 Pod 内的 shell 中循环执行,请使用 sh -c 'for i in $(seq 1 30); do …; done'。

打开看不到的统计

在 /root/istlab-resilience/client.yaml 中重新写出 client Pod,并在注解 proxy.istio.io/config 中加入 proxyStatsMatcher.inclusionRegexps: [".*outlier_detection.*"],删除原有的 client 后,用这个文件重新创建。

Istio 默认会过滤掉 sidecar 输出的大部分 Envoy 统计——因为几千个 Pod 全都输出的话,Prometheus 承受不了。所以即使开启了异常值检测,也看不到驱逐次数之类的数字。proxyStatsMatcher 是通过 Pod 注解只放行所需统计的机制,代理是在启动时读取它的,所以要重新创建 Pod。是否成功写入,可以在代理引导配置的 stats_config 中查看。

用异常值检测摘除故障 Pod

在 /root/istlab-resilience/dr.yaml 中写入并应用 DestinationRule api(主机 api.pay.svc.cluster.local)——用 outlierDetection 设置连续 5xx 2 次(consecutive5xxErrors: 2)、检测周期 2 秒、基本驱逐时间 60 秒、最大驱逐比例 50%。然后分两轮、每轮访问 30 次,并在 /root/istlab-resilience/03-outlier.txt 中写入 err_first=(前 30 次的 500 数量)和 err_second=(后 30 次的 500 数量)。

异常值检测由 sidecar 依据实际收到的响应逐个判断上游。同一个 Pod 连续返回 5xx 时,就会把它暂时从负载均衡对象中摘除(驱逐)。第一轮会在被驱逐之前看到几次 500,第二轮应为 0。驱逐的次数用第 2 步放行的统计 …outlier_detection.ejections_enforced_total 来统计——kubectl -n pay exec client -c istio-proxy -- pilot-agent request GET stats | grep outlier。

Envoy 是怎样记录那个 Pod 的

在驱逐生效期间(第 3 步之后 60 秒内),把 istioctl proxy-config endpoints client.pay --cluster 'outbound|80||api.pay.svc.cluster.local' -o json 的输出保存到 /root/istlab-resilience/04-endpoints.json。如果已经过了 60 秒,就像第 3 步那样访问 30 次再次触发驱逐后再保存。

被驱逐的 Pod 不会从 Kubernetes 端点中消失。它只会从这个 sidecar 的负载均衡列表中消失。所以 kubectl get endpoints 中三个仍然都在,而在 Envoy 的端点列表中,该 Pod 的 healthStatus 上会带有 failedOutlierCheck: true。以表格形式查看,则是 OUTLIER CHECK 一栏为 FAILED。驱逐不是永久的,经过基本驱逐时间后它会重新回来,如果再次失败,则会被摘除更久。

用生产流量映照新版本——流量镜像

在 /root/istlab-resilience/vs.yaml 中写入并应用 VirtualService api(主机 api.pay.svc.cluster.local)——目的地仍为 api.pay.svc.cluster.local,mirror 为 api-v2.pay.svc.cluster.local,mirrorPercentage 为 100。应用后,把 x-request-id 分别设为 mirror-1、mirror-2、mirror-3 访问三次,并在 /root/istlab-resilience/05-mirror.txt 中写入 mirrored=(api-v2 sidecar 日志中留下的这三个 ID 的数量)和 client_saw_v2=(client 收到的正文中出现过 v2 则为 yes)两行。

流量镜像会复制请求发往另一方,并丢弃它的响应。所以可以用生产流量测试新版本,而不影响用户。副本带着与原请求相同的 x-request-id,所以可以在镜像目标的 sidecar 日志中按 ID 找到它——kubectl -n pay logs deploy/api-v2 -c istio-proxy。副本是异步发送的,日志中可能稍晚才会出现。

有意注入故障——只在带请求头时

在 /root/istlab-resilience/vs.yaml 的 VirtualService 前面再加两条规则——请求头 x-chaos: abort 时用 fault.abort 注入 100% 的 503,x-chaos: delay 时用 fault.delay 注入 100% 的 2 秒延迟,目的地不变(带镜像的默认规则放在最后)。应用后,在 /root/istlab-resilience/06-fault.txt 中写入 abort_code=(abort 请求的状态码)、abort_flag=(该请求在 client sidecar 访问日志中的响应标志)、delay_seconds=(delay 请求耗时,舍去小数的秒数)三行。

故障注入由客户端一侧的 sidecar 在把请求发往上游之前进行。所以 abort 不会到达上游,响应标志记为 FI(fault injected)。设置了请求头条件后,可以保持日常流量不变,只破坏正在测试的请求。VirtualService 的 http 规则从上往下取第一个匹配的,所以要把兜底规则放在最后。耗时用 curl -w '%{time_total}' 测量。

DestinationRule 和 VirtualService 变成了 Envoy 中的什么

从 client sidecar 收到的配置中取出两项,以 JSON 对象保存到 /root/istlab-resilience/07-envoy.json——outlier:istioctl proxy-config cluster client.pay --fqdn api.pay.svc.cluster.local -o json 的第一个集群的 outlierDetection 对象,原样保存;mirror_cluster:istioctl proxy-config routes client.pay --name 80 -o json 中 api.pay.svc.cluster.local 虚拟主机的最后一条路由所带的 requestMirrorPolicies[0].cluster 的值。

istiod 会把 DestinationRule 的 consecutive5xxErrors、interval、baseEjectionTime、maxEjectionPercent 转换为 Envoy 集群的 outlierDetection,把 VirtualService 的 mirror 转换为路由的 requestMirrorPolicies。名称略有不同(consecutive5xx),时间会变成 "2s" 这样的字符串。修改了配置但行为异常时,最终就要查看这个转换结果。

写下网格代替我们做了什么

在 /root/istlab-resilience/08-report.md 中写入五行——errors_before=(第 1 步的 err)、errors_after_ejection=(第 3 步的 err_second)、ejected_ip=(第 4 步文件中 failedOutlierCheck 为 true 的地址)、mirror_reached_client=(第 5 步的 client_saw_v2)、abort_flag=(第 6 步)——并在其下写出学到的内容,至少四行。

值从前面步骤的文件中抄过来。说明行中请写下“Kubernetes 的就绪检查和网格的异常值检测各自根据什么来判断”——一个看 Pod 自己报告的状态,一个看实际收到的响应。