事故起因
业务上新集群,本来以为"洒洒水",11 点切,12 点就能在家睡觉了。流量切过来后,在验证过程中,发现网页能够正常打开,在登录时返回了 502,当场懵逼。在相关的容器日志发现一个高频的报错条目“7000 端口无法连接”,向业务组了解到这是 redis 集群中的一个端口,前后端是通过 redis 交互的,该集群同时还有 7001-7003 其它三个端口。
用 nc 命令对 redis 集群进行连接测试:向服务端发送 keys * 命令时,7000 端口返回的是 HTTP/1.1 400 Bad Request,其他三个端口是 redis 返回的 -NOAUTH Authentication required。
$ nc 10.0.0.6 7000
keys *
HTTP/1.1 400 Bad Request
content-length: 0
connection: close
$ nc 10.0.0.6 7003
keys *
-NOAUTH Authentication required
判断 7000 端口连接到了其他应用上,至少不是 redis。在宿主机上抓包发现没有抓到访问 7000 端口的流量,然后查看容器的 nf_conntrackb 表,发现 7000 端口的数据只有到本地的会话信息;7003 的有两条会话信息,一条到本机的,一条到目标服务器的。
$ grep 7000 /proc/net/nf_conntrack
ipv4 2 tcp 6 110 TIME_WAIT src=10.64.192.14 dst=10.0.0.6 sport=50498 dport=7000 src=127.0.0.1 dst=10.64.192.14 sport=15001 dport=50498 [ASSURED] mark=0 zone=0 use=2
$ grep 7003 /proc/net/nf_conntrack
ipv4 2 tcp 6 104 TIME_WAIT src=10.64.192.14 dst=10.0.0.6 sport=38952 dport=7003 src=127.0.0.1 dst=10.64.192.14 sport=15001 dport=38952 [ASSURED] mark=0 zone=0 use=2
ipv4 2 tcp 6 104 TIME_WAIT src=10.64.192.14 dst=10.0.0.6 sport=38954 dport=7003 src=10.0.0.6 dst=10.64.192.14 sport=7003 dport=38954 [ASSURED] mark=0 zone=0 use=2
由此判断出 istio 没有代理转发出 7000 的流量,这突然就触及到了我的知识盲区,一大堆人看着,办公室 26 度的空调,一直在冒汗。没办法了,在与业务商量后,只能先关闭 istio 注入,优先恢复了业务。回去后恶补 istio 的相关资料。终于将问题解决。记录下相关信息,以供日后参考。
背景知识补充
istio 的 Sidecar 有两种模式:
-
ALLOW_ANY:istio 代理允许调用未知的服务,黑名单模式。
-
REGISTRY_ONLY:istio 代理会阻止任何没有在网格中定义的 HTTP 服务或 service entry 的主机,白名单模式。
istio-proxy(Envoy)的配置结构
istio-proxy(Envoy)的代理信息大体由以下几个部分组成:
-
Cluster:在 Envoy 中,Cluster 是一个服务集群,Cluster 中包含一个到多个 endpoint,每个 endpoint 都可以提供服务,Envoy 根据负载均衡算法将请求发送到这些 endpoint 中。Cluster 分为 inbound 和 outbound 两种,前者对应 Envoy 所在节点上的服务;后者占了绝大多数,对应 Envoy 所在节点的外部服务。可以使用如下方式分别查看 inbound 和 outbound 的 Cluster。
-
Listeners:Envoy 采用 listener 来接收并处理 downstream 发过来的请求,可以直接与 Cluster 关联,也可以通过 rds 配置路由规则(Routes),然后在路由规则中再根据不同的请求目的地对请求进行精细化的处理。
-
Routes:配置 Envoy 的路由规则。istio 下发的缺省路由规则中对每个端口(服务)设置了一个路由规则,根据 host 来对请求进行路由分发,routes 的目的为其他服务的 Cluster。
-
Endpoint:Cludter 对应的后端服务,可以通过 istio pc endpoint 查看 inbound 和 outbound 对应的 endpoint 信息。
服务发现类型
Cluster 的服务发现类型主要有:
-
ORIGINAL_DST:ORIGINAL_DST 类型的 Cluster,Envoy 在转发请求时会直接采用 downstream 请求中的原始目的地 IP 地址。
-
EDS:EDS 获取到该 Cluster 中所有可用的 Endpoint,并根据负载均衡算法(缺省为 Round Robin)将 Downstream 发来的请求发送到不同的 Endpoint。istio 会自动为集群中的 service 创建代理信息,listener 的信息从 service 获取,对应的 Cluster 被标记为 EDS 类型。
-
STATIC:缺省值,在集群中列出所有可代理的主机 Endpoints。当没有内容为空时,不进行转发。
-
LOGICAL_DNS:Envoy 使用 DNS 添加主机,但如果 DNS 不再返回时,也不会丢弃。
-
STRICT_DNS:Envoy 将监控 DNS,而每个匹配的 A 记录都将被认为是有效的。
两个特殊集群
BlackHoleCluster:黑洞集群,匹配此集群的流量将被不会被转发。
{
"name": "BlackHoleCluster",
"type": "STATIC",
"connectTimeout": "10s"
}
类型为 static,但是没有指定可代理的 Endpoint,所以流量不会被转发。
PassthroughCluster:透传集群,匹配此集群的流量数据包的目的 IP 不会改变。
{
"name": "PassthroughCluster",
"type": "ORIGINAL_DST",
"connectTimeout": "10s",
"lbPolicy": "CLUSTER_PROVIDED",
"circuitBreakers": {
"thresholds": [
{
"maxConnections": 4294967295,
"maxPendingRequests": 4294967295,
"maxRequests": 4294967295,
"maxRetries": 4294967295
}
]
}
类型为 original_dst,流量将原样转发。
一个特殊的 Listener
istio 中有一个特殊的 Listener 叫 virtualOutbound,定义如下:
virtualOutbound:每个 Sidecar 都有一个绑定到 0.0.0.0:15001 的 listener,该 listener 下关联了许多 virtual listener。iptables 会先将所有出站流量导入该 listener,该 listener 有一个字段 useOriginalDst 设置为 true,表示会使用最佳匹配原始目的地的方式将请求分发到 virtual listener,如果没有找到任何 virtual listener,将会直接发送到数据包原目的地的 PassthroughCluster。
useOriginalDst 字段的具体意义是,如果使用 iptables 重定向连接,则代理接收流量的目标地址可能与原始目标地址不同。当此标志设置为 true 时,侦听器会将重定向流量转交给与原始目标地址关联的侦听器。如果没有与原始目标地址关联的侦听器,则流量由接收它的侦听器处理。默认为 false。
virtualOutbound 的流量处理流程如图所示:
这是 virtualOutbound 的部分配置:
{
"name": "envoy.tcp_proxy",
"typedConfig": {
"@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
"statPrefix": "PassthroughCluster",
"cluster": "PassthroughCluster"
}
}
……………
"useOriginalDst": true
istio 的 outbond 流量处理
开启流量治理后,pod 访问外部资源的流量转发路径如图