文章目录
背景
我们要把一个服务迁移到新的负载均衡上。域名保持不变,但它的后端负载均衡 IP 会换成新的。迁移前需要先确认:从我们当前的服务器到新 IP 之间,网络是否连通。
听起来很简单:拿新 IP 发一个请求,看能不能通。但实际测下来,过程却不是那么顺利。
现象
同一个健康检查请求,在不同环境下结果不一样:
- 在没有 sidecar 的 Pod 里,直接访问新 IP,TCP 连接超时。
- 在有 Istio sidecar 的 Pod 里,访问新 IP,但不带业务域名的
Host请求头,同样不通。 - 在有 Istio sidecar 的 Pod 里,访问新 IP,并带上业务域名的
Host请求头,返回200。 - 老的 IP 则在两类 Pod 里都能访问。
一开始很容易把问题归到节点路由、网段互通或防火墙差异上。但真正要回答的问题是:带 sidecar 时测到的结果,为什么会依赖 Host 请求头?
排查过程
先在没有 sidecar 的 Pod 里用 TCP mtr 和 curl 验证。Pod 能到达部分外部地址,但对新 IP 无法完成连接。这说明不是整个 Pod 网络都不能出站,问题集中在新的目标地址上。
接着在带 sidecar 的 Pod 里分别测试两种请求:
sh
# 不指定业务 Host:连接建立,但没有 HTTP 响应
curl -v http://<new-lb-ip>/send/management/health
# 指定业务 Host:返回 200
curl -v -H 'Host: api.example.internal' \
http://<new-lb-ip>/send/management/health
这时候需要对比两者的 istio-proxy sidecar 的日志,关键证据显示,不指定 Host 时:
text
upstream_cluster: PassthroughCluster
route_name: allow_any
这说明 Envoy 只是把请求透明地转发到原始目标地址,也就是我们填的新 IP,没有命中任何 Istio 已知服务。
指定业务 Host 后,日志变成:
text
authority: api.example.internal
upstream_cluster: outbound|80||api.example.internal
upstream_host: <old-lb-ip>:80
response_code: 200
问题就在这里。请求虽然发给了新 IP,但 Envoy 根据 Host 里的域名,解析出了这个域名当前的后端 IP------还是老 IP。所以这次成功的请求,实际连接的是老 IP,不是我们想测的新 IP。
到这里自然会有一个疑问:为什么 Envoy 会主动去解析这个 Host,而不是老老实实把请求转发到原始地址?
为什么 Envoy 会解析这个 Host
关键在于 Istio 的默认出站行为。sidecar 接管出站流量后,会先看请求的 Host 是否匹配某个已注册的服务。匹配上了,就按这个服务的配置走;匹配不上,才退回透明转发。
这个"已注册的服务"来自 ServiceEntry。我们的业务域名在 ServiceEntry 里声明过,并且 resolution: DNS。也就是说,Istio 明确告诉 Envoy:这个域名是一个外部服务,地址要通过 DNS 解析得到。
所以 Envoy 不是"主动"去解析,而是被配置驱动。它收到 Host: api.example.internal,发现这个域名在 ServiceEntry 里,于是按 resolution: DNS 去解析,拿到域名当前的后端 IP。域名还没切到新 IP,解析结果自然还是老 IP。
不带 Host 时,请求没有可匹配的域名,Envoy 只能走 PassthroughCluster,把请求原样转发到原始地址,也就是新 IP。这就是为什么两种请求结果完全不同。
结论
带 sidecar 时,指定 Host 并不能测到新 IP 的连通性。恰恰相反,它让 Istio 按域名解析出当前后端,而当前后端还是老 IP,于是测到的是老 IP 的连通性。
真正的差异在 HTTP 路由:
text
应用请求新 IP
-> sidecar 截获流量
-> Envoy 根据 HTTP Host 匹配已注册服务
-> DNS 解析业务域名
-> 连接到域名当前的后端 IP(还是老 IP)
不指定 Host 时,Envoy 走 PassthroughCluster,直接连接原始地址,也就是新 IP,所以才会超时。指定 Host 后,请求被 Envoy 按服务配置重新选择上游,绕过了新 IP。
换句话说,带 sidecar 的测试结果不能用来判断新 IP 是否可达。要测新 IP,必须让请求真正落到新 IP 上,而不是被 Istio 按域名重新路由。
Istio 与 ServiceEntry 如何配合
Istio sidecar 通过 Pod 网络规则接管出站流量。对于 HTTP,它可以读取请求中的 Host,把请求路由到与该主机名对应的 Envoy cluster。
ServiceEntry 用于把集群外的服务声明给 Istio。例如:
yaml
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
spec:
hosts:
- api.example.internal
location: MESH_EXTERNAL
resolution: DNS
ports:
- number: 80
protocol: HTTP
这段配置告诉 Istio:api.example.internal 是一个 mesh 外部的 HTTP 服务,地址通过 DNS 解析。sidecar 收到 Host: api.example.internal 后,会选中该服务的 cluster,再连接 DNS 解析出的 endpoint。
这里有两个关键点,都来自 Istio 官方文档:
ServiceEntry的hosts字段会与 HTTP 的Host/Authority头匹配。也就是说,sidecar 是靠请求头里的域名来识别这个服务的。resolution: DNS时,若未指定 endpoints,proxy 会解析hosts中的域名作为路由目标。官方文档还特别指出,使用 DNS 解析时,sidecar 会忽略原始目的 IP,改为按域名做 DNS 查询后路由。
这两点正好解释了我们的现象:请求头里的域名决定了 Envoy 去解析谁,而解析结果由 DNS 决定。域名没切到新 IP 之前,解析出来的始终是老 IP。
另外,ALLOW_ANY 是 Istio 的默认出站模式。官方文档说明,在这种模式下,对 mesh 内未注册的未知服务,Istio proxy 会透传(passthrough)。这就是不带 Host 时走 PassthroughCluster、直连原始地址的原因。
所以 ServiceEntry 不等于网络放通。它提供的是服务身份、协议和解析方式;实际网络仍需允许 sidecar 到解析结果的出站连接。
排查要点
遇到"带 sidecar 成功、没有 sidecar 失败"时,先不要假定是网络策略差异。优先检查:
- 成功请求的
Host是否与 ServiceEntry、VirtualService 中的域名一致。 - Envoy access log 中的
upstream_cluster和upstream_host。 - 成功请求最终连接的地址,是否与 URL 中的原始地址不同。
这三项通常比继续跑 traceroute 更快找到真实的路由分叉点。尤其在做迁移前的连通性测试时,一定要确认请求最终连的是不是目标 IP,否则很容易被 Istio 的域名路由误导。
因为这个 k8s 集群环境是前人搭建的,而且缺少代码管理和文档描述,依据对 istio 缺乏理解,所以在排查过程中绕了好几道弯,才定位到这个特殊的 SE 配置。