明明报 503,配置却显示全部已生效
目标
在真实的 Istio 1.31 网格(VM 内的 k3s)中,对接手的配置所造成的四种症状——请求头规则被忽略、/api 的 503、金丝雀请求头的 503、没有 sidecar 的客户端连接被断开——从证据出发定位原因并修复。
最后把 istiod 暂时关掉,亲自看看没有控制平面时,什么仍在继续、什么会被挡住。
为什么重要
在网格中,503 的原因不止一个。可能是配置所指向的 Envoy cluster 根本不存在(NC),可能是 cluster 存在但没有可选的 Pod(UH),也可能是请求连被解析为 HTTP 都没做到(NR)。kubectl apply 成功只意味着 API 服务器收下了,Envoy 怎样使用那条规则,还要另行确认。所以故障排查分成三层。
- 配置:
istioctl analyze会找出引用中断的地方(IST0101)和选不到 Pod 的 subset(IST0173)。 - 数据平面:用
istioctl proxy-config listeners·endpoints查看 Envoy 实际收到的配置,通过访问日志中的响应标志(response flag),读出请求在哪里停住。 - 控制平面:用
istioctl proxy-status查看哪些代理连着 istiod,并确认没有 istiod 时,已收到的配置仍会继续使用,而写入新配置、注入新 Pod 则因为 Webhook 而被拒绝。
步骤
- 应用接手的配置,并记录四个 Pod 的 uid 以及是否有 sidecar。
- 在 listener 中找出请求头规则被忽略的原因(tcp_proxy),并通过 Service 端口名称来纠正协议。
- 用 Telemetry API 开启命名空间的访问日志。
- 用访问日志(NC)和 analyze 确认
/api的 503,并修复不存在的 subset 引用。 - 用访问日志(UH)和空的 endpoint 确认金丝雀请求头的 503,并修复 subset 标签。
- 用服务端日志(NR)确认没有 sidecar 的 legacy 被断开,并在保持 STRICT 的前提下把它加入网格。
- 关闭再开启 istiod,记录现有流量、配置写入、新 Pod 各自的情况。
- 编写从证据文件转写的报告。
参考
- 这个 VM 需要几分钟做准备。
kubectl、istioctl可以在登录 shell 中直接使用(istioctl 1.31.0)。 - 配方(recipe)创建的
ica-policy命名空间是为其他实验准备的,不要动它。工作文件放在/root/ica-debug/。 - 修改配置之后,传播到代理需要几秒。结果与预期不同时,请稍后再确认。
- 如果在第 7 步不把 istiod 恢复,Istio 资源的变更和新 Pod 的创建会一直被拒绝。
- 官方文档: Debugging Envoy and Istiod、 Protocol Selection、 Envoy Access Logs、 Configuration analysis messages、 Envoy access log response flags
记录接手的网格中的 Pod 与代理列表
原样应用接手的配置 /opt/fixtures/ica-debug/incident.yaml,并等待命名空间 ica-debug 的四个 Pod(web-v1、web-v2、client、legacy)变为 Ready。把此时的列表以 JSON 数组保存到 /root/ica-debug/inventory.json。每个元素有 name、uid、sidecar(Pod 中有 istio-proxy 时为 true)三个键。暂时不要修复配置。
必须先记下哪些东西已经加入网格,才能解读后面的症状。此 VM 中的 Istio 1.31 不是把 istio-proxy 放在 spec.containers,而是以 restartPolicy: Always 放在 spec.initContainers 中(Kubernetes 原生 sidecar)。所以要同时查看两个列表,才能正确判断 sidecar。另外,也请用 istioctl proxy-status 一并查看哪些 Pod 连着 istiod。legacy Pod 已通过标签拒绝了注入。uid 是集群给出的值,自己编造会失败。
写了请求头规则,流量却照样对半分
在 client 中带上 x-canary: yes 请求头多次调用 http://web/,v1 和 v2 会混在一起返回(按规则应该只有一边)。修复之前,用 istioctl proxy-config listeners client.ica-debug --port 80 -o json 把 client sidecar 80 端口的 listener 保存到 /root/ica-debug/listener-before.json。然后把 Service web 的端口名称改为 http-web,让 Istio 把这个端口当作 HTTP 处理(端口号和 targetPort 保持不变)。
VirtualService 的 http 规则,只有 Envoy 把该端口解析为 HTTP 时才会生效。Istio 通过 Service 端口名称的前缀 <프로토콜>-이름(占位符依次为协议与名称)或 appProtocol 来确定协议。请在保存的 listener JSON 中,查找 Service ClusterIP listener 的过滤器名称是 tcp_proxy 还是 http_connection_manager。端口名称可以用 kubectl patch svc 的 json patch 来修改。
让代理说出发生了什么
对 ica-debug 命名空间整体开启 Envoy 访问日志。把 Telemetry 资源写入 /root/ica-debug/telemetry.yaml 并应用:spec.accessLogging 的 provider 名称是 envoy。开启之后,client 发出的请求必须在 kubectl logs client -c istio-proxy 中各留下一行。
minimal profile 没有在 meshConfig 中指定访问日志文件,所以默认不会留下日志。可以不修改整个网格的配置,而是用 Telemetry API 在命名空间范围内开启(apiVersion telemetry.istio.io/v1)。在请求中直接加上 x-request-id 请求头,就能在日志中轻松找到那一行。
只有 /api 是 503,而服务端 Pod 明明正常
在 client 中调用 http://web/api/orders 会返回 503。把与该请求对应的 client sidecar 访问日志的一行原样保存到 /root/ica-debug/nc.log,然后修复原因:把 VirtualService web 的 api 规则所指向的 subset,改为 DestinationRule 中实际存在的 v1。修复之后,/api/orders 必须返回 200 和正文 v1,并且 istioctl analyze -n ica-debug 中不能出现 IST0101。
Envoy 访问日志中紧跟在响应码后面的一栏就是响应标志。请把标志及其后面的详细原因,与 Envoy 文档中的 response flags 表对照。服务端 sidecar 上根本没有收到这个请求,这也是线索。istioctl analyze 只看配置就能找出同一个原因。
带上金丝雀请求头就出现 no healthy upstream
在 client 中带上 x-canary: yes 请求头调用 http://web/,这次会收到 no healthy upstream 和 503。把该请求的 client sidecar 访问日志的一行原样保存到 /root/ica-debug/uh.log,并把同一时刻的 istioctl proxy-config endpoints client.ica-debug --cluster 'outbound|80|v2|web.ica-debug.svc.cluster.local' -o json 输出保存到 /root/ica-debug/endpoints-before.json。然后修改 DestinationRule web 的 subset v2,让它选中实际的 Pod 标签(version: v2)。
与 NC 不同,这次 Envoy cluster(outbound|80|v2|…)是存在的。问题在于放进该 cluster 的 endpoint。subset 是按 Pod 标签对 Service 的 endpoint 再过滤一次,所以标签值哪怕只差一个字符,也会得到空列表。请把 kubectl get pod --show-labels 与 DestinationRule 的 labels 并排对照。1.31 的 analyze 会用 IST0173 告知这种情况。
只有没有 sidecar 的旧客户端连接被断开
在 legacy Pod 中执行 curl http://web/,会没有响应直接断开连接(curl 退出码 56)。在服务端(web-v1 或 web-v2)sidecar 日志中,找到以 legacy Pod IP 为源地址的那一行,原样保存到 /root/ica-debug/nr.log。然后保持 STRICT 不变,把 legacy 加入网格来修复:把去掉了拒绝注入的标签的 Pod 定义写入 /root/ica-debug/legacy.yaml,并重新创建 Pod。修复之后,从 legacy 访问 http://web/ 必须返回 200。
PeerAuthentication 为 STRICT 的服务端 sidecar 只接受通过 mTLS 进来的连接。没有 sidecar 的客户端发送的是明文,服务端 sidecar 找不到与该连接匹配的 filter chain——这是在被解析为 HTTP 请求之前的阶段,所以日志中的方法、路径栏是空的。降为 PERMISSIVE 也能让连接通,但不是这一步的答案。Pod 的标签和注解在运行中修改也不会触发注入,所以要删除后重新创建。
istiod 停掉期间,什么会停下来
用 kubectl -n istio-system scale deploy istiod --replicas=0 关闭 istiod,确认 Pod 消失之后,做三件事,并把结果分三行写入 /root/ica-debug/istiod-down.txt:traffic= 之后写 client 调用 http://web/ 得到的正文,config-write= 之后写 kubectl -n ica-debug annotate virtualservice web lab.example/probe=1 --overwrite 的输出,new-pod= 之后写 kubectl -n ica-debug run probe --image=curlimages/curl:8.10.1 --restart=Never --command -- sleep 60 的输出。记录之后,把 istiod 的 replicas 恢复为 1,并等到四个代理(client、web-v1、web-v2、legacy)再次出现在 istioctl proxy-status 中。
控制平面负责分发配置、签发证书,并接收注入与验证 Webhook。已经收到配置的 Envoy,即使没有 istiod,也会按最后的配置继续运行。而写入 Istio 资源的请求和创建新 Pod,需要 API 服务器调用 Webhook,请用 kubectl get validatingwebhookconfiguration,mutatingwebhookconfiguration -o yaml 确认 Webhook 的 failurePolicy 是什么。如果忘了恢复,后面所有的配置变更都会被挡住。
用证据把四种 503、断连分类的报告
在 /root/ica-debug/report.json 中写入一个 JSON 对象。键与值的含义——header_rules_ignored_filter:修复之前在 listener 中看到的网络过滤器名称的最后一段(例如 xxx_proxy),api_503_flag、canary_503_flag、legacy_reset_flag:nc.log、uh.log、nr.log 中记录的响应标志,istiod_down_traffic:没有 istiod 时收到的正文,istiod_down_config_write:当时配置写入成功时为 accepted,被拒绝时为 rejected。这些值必须与你留下的证据文件吻合,并且四种症状现在都必须已经修复。
报告要从证据转写,而不是凭记忆。请读取日志行中响应码后面的一栏,并在 listener JSON 中找到 Service ClusterIP listener 的过滤器名称。评分器会读取同样的文件进行对照,并重新发送请求头、/api、legacy 的请求来确认。