316 lines
14 KiB
Markdown
316 lines
14 KiB
Markdown
|
# Istio 中的流量路由过程详解
|
|||
|
|
|||
|
本文以 Istio 官方的 bookinfo 示例来讲解在进入 Pod 的流量被 iptables 转交给 Envoy sidecar 后,Envoy 是如何做路由转发的,详述了 Inbound 和 Outbound 处理过程。
|
|||
|
|
|||
|
下面是 Istio 官方提供的 bookinfo 的请求流程图,假设 bookinfo 应用的所有服务中没有配置 DestinationRule。
|
|||
|
|
|||
|
![Bookinfo 示例](../images/bookinfo-sample-arch.png)
|
|||
|
|
|||
|
我们将要解析的是 `reviews-v1` 这个 Pod 中的 Inbound 和 Outbound 流量。
|
|||
|
|
|||
|
### 理解 Inbound Handler
|
|||
|
|
|||
|
Inbound Handler 的作用是将 iptables 拦截到的 downstream 的流量转发给 Pod 内的应用程序容器。在我们的实例中,假设其中一个 Pod 的名字是 `reviews-v1-545db77b95-jkgv2`,运行 `istioctl proxy-config listener reviews-v1-545db77b95-jkgv2 --port 15006` 查看该 Pod 中 15006 端口上的监听器情况 ,你将看到下面的输出。
|
|||
|
|
|||
|
```ini
|
|||
|
ADDRESS PORT MATCH DESTINATION
|
|||
|
0.0.0.0 15006 Addr: *:15006 Non-HTTP/Non-TCP
|
|||
|
0.0.0.0 15006 Trans: tls; App: istio-http/1.0,istio-http/1.1,istio-h2; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
|
|||
|
0.0.0.0 15006 Trans: raw_buffer; App: http/1.1,h2c; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
|
|||
|
0.0.0.0 15006 Trans: tls; App: TCP TLS; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
|
|||
|
0.0.0.0 15006 Trans: raw_buffer; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
|
|||
|
0.0.0.0 15006 Trans: tls; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
|
|||
|
0.0.0.0 15006 Trans: tls; App: istio,istio-peer-exchange,istio-http/1.0,istio-http/1.1,istio-h2; Addr: *:9080 Cluster: inbound|9080||
|
|||
|
0.0.0.0 15006 Trans: raw_buffer; Addr: *:9080 Cluster: inbound|9080||
|
|||
|
```
|
|||
|
|
|||
|
下面列出了以上输出中各字段的含义:
|
|||
|
|
|||
|
- ADDRESS:下游地址
|
|||
|
- PORT:Envoy 监听器监听的端口
|
|||
|
- MATCH:请求使用的传输协议或匹配的下游地址
|
|||
|
- DESTINATION:路由目的地
|
|||
|
|
|||
|
`reviews` Pod 中的 Iptables 将入站流量劫持到 15006 端口上,从上面的输出我们可以看到 Envoy 的 Inbound Handler 在 15006 端口上监听,对目的地为任何 IP 的 9080 端口的请求将路由到 `inbound|9080||` Cluster 上。
|
|||
|
|
|||
|
从该 Pod 的 Listener 列表的最后两行中可以看到,`0.0.0.0:15006/TCP` 的 Listener(其实际名字是 `virtualInbound`)监听所有的 Inbound 流量,其中包含了匹配规则,来自任意 IP 的对 `9080` 端口的访问流量,将会路由到 `inbound|9080||` Cluster,如果你想以 Json 格式查看该 Listener 的详细配置,可以执行 `istioctl proxy-config listeners reviews-v1-545db77b95-jkgv2 --port 15006 -o json` 命令,你将获得类似下面的输出。
|
|||
|
|
|||
|
```json
|
|||
|
[
|
|||
|
/*省略部分内容*/
|
|||
|
{
|
|||
|
"name": "virtualInbound",
|
|||
|
"address": {
|
|||
|
"socketAddress": {
|
|||
|
"address": "0.0.0.0",
|
|||
|
"portValue": 15006
|
|||
|
}
|
|||
|
},
|
|||
|
"filterChains": [
|
|||
|
/*省略部分内容*/
|
|||
|
{
|
|||
|
"filterChainMatch": {
|
|||
|
"destinationPort": 9080,
|
|||
|
"transportProtocol": "tls",
|
|||
|
"applicationProtocols": [
|
|||
|
"istio",
|
|||
|
"istio-peer-exchange",
|
|||
|
"istio-http/1.0",
|
|||
|
"istio-http/1.1",
|
|||
|
"istio-h2"
|
|||
|
]
|
|||
|
},
|
|||
|
"filters": [
|
|||
|
/*省略部分内容*/
|
|||
|
{
|
|||
|
"name": "envoy.filters.network.http_connection_manager",
|
|||
|
"typedConfig": {
|
|||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
|||
|
"statPrefix": "inbound_0.0.0.0_9080",
|
|||
|
"routeConfig": {
|
|||
|
"name": "inbound|9080||",
|
|||
|
"virtualHosts": [
|
|||
|
{
|
|||
|
"name": "inbound|http|9080",
|
|||
|
"domains": [
|
|||
|
"*"
|
|||
|
],
|
|||
|
"routes": [
|
|||
|
{
|
|||
|
"name": "default",
|
|||
|
"match": {
|
|||
|
"prefix": "/"
|
|||
|
},
|
|||
|
"route": {
|
|||
|
"cluster": "inbound|9080||",
|
|||
|
"timeout": "0s",
|
|||
|
"maxStreamDuration": {
|
|||
|
"maxStreamDuration": "0s",
|
|||
|
"grpcTimeoutHeaderMax": "0s"
|
|||
|
}
|
|||
|
},
|
|||
|
"decorator": {
|
|||
|
"operation": "reviews.default.svc.cluster.local:9080/*"
|
|||
|
}
|
|||
|
}
|
|||
|
]
|
|||
|
}
|
|||
|
],
|
|||
|
"validateClusters": false
|
|||
|
},
|
|||
|
/*省略部分内容*/
|
|||
|
}
|
|||
|
}
|
|||
|
],
|
|||
|
/*省略部分内容*/
|
|||
|
],
|
|||
|
"listenerFilters": [
|
|||
|
/*省略部分内容*/
|
|||
|
],
|
|||
|
"listenerFiltersTimeout": "0s",
|
|||
|
"continueOnListenerFiltersTimeout": true,
|
|||
|
"trafficDirection": "INBOUND"
|
|||
|
}
|
|||
|
]
|
|||
|
```
|
|||
|
|
|||
|
既然 Inbound Handler 的流量中将来自任意地址的对该 Pod `9080` 端口的流量路由到 `inbound|9080||` Cluster,那么我们运行 `istioctl pc cluster reviews-v1-545db77b95-jkgv2 --port 9080 --direction inbound -o json` 查看下该 Cluster 配置,你将获得类似下面的输出。
|
|||
|
|
|||
|
```json
|
|||
|
[
|
|||
|
{
|
|||
|
"name": "inbound|9080||",
|
|||
|
"type": "ORIGINAL_DST",
|
|||
|
"connectTimeout": "10s",
|
|||
|
"lbPolicy": "CLUSTER_PROVIDED",
|
|||
|
"circuitBreakers": {
|
|||
|
"thresholds": [
|
|||
|
{
|
|||
|
"maxConnections": 4294967295,
|
|||
|
"maxPendingRequests": 4294967295,
|
|||
|
"maxRequests": 4294967295,
|
|||
|
"maxRetries": 4294967295,
|
|||
|
"trackRemaining": true
|
|||
|
}
|
|||
|
]
|
|||
|
},
|
|||
|
"cleanupInterval": "60s",
|
|||
|
"upstreamBindConfig": {
|
|||
|
"sourceAddress": {
|
|||
|
"address": "127.0.0.6",
|
|||
|
"portValue": 0
|
|||
|
}
|
|||
|
},
|
|||
|
"metadata": {
|
|||
|
"filterMetadata": {
|
|||
|
"istio": {
|
|||
|
"services": [
|
|||
|
{
|
|||
|
"host": "reviews.default.svc.cluster.local",
|
|||
|
"name": "reviews",
|
|||
|
"namespace": "default"
|
|||
|
}
|
|||
|
]
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
]
|
|||
|
```
|
|||
|
|
|||
|
我们看其中的 `TYPE` 为 `ORIGINAL_DST`,将流量发送到原始目标地址(Pod IP),因为原始目标地址即当前 Pod,你还应该注意到 `upstreamBindConfig.sourceAddress.address` 的值被改写为了 `127.0.0.6`,而且对于 Pod 内流量是通过 `lo` 网卡发送的,这刚好呼应了上文中的 iptables `ISTIO_OUTPUT` 链中的第一条规则,根据该规则,流量将被透传到 Pod 内的应用容器。
|
|||
|
|
|||
|
### 理解 Outbound Handler
|
|||
|
|
|||
|
在本示例中 `reviews` 会向 `ratings` 服务发送 HTTP 请求,请求的地址是:`http://ratings.default.svc.cluster.local:9080/`,Outbound Handler 的作用是将 iptables 拦截到的本地应用程序向外发出的流量,经由 Envoy 代理路由到上游。
|
|||
|
|
|||
|
Envoy 监听在 15001 端口上监听所有 Outbound 流量,Outbound Handler 处理,然后经过 `virtualOutbound` Listener、`0.0.0.0_9080` Listener,然后通过 Route 9080 找到上游的 cluster,进而通过 EDS 找到 Endpoint 执行路由动作。
|
|||
|
|
|||
|
**`ratings.default.svc.cluster.local:9080` 路由**
|
|||
|
|
|||
|
运行 `istioctl proxy-config routes reviews-v1-545db77b95-jkgv2 --name 9080 -o json` 查看 route 配置,因为 sidecar 会根据 HTTP header 中的 domains 来匹配 VirtualHost,所以下面只列举了 `ratings.default.svc.cluster.local:9080` 这一个 VirtualHost。
|
|||
|
|
|||
|
```json
|
|||
|
[
|
|||
|
{
|
|||
|
"name": "9080",
|
|||
|
"virtualHosts": [
|
|||
|
{
|
|||
|
"name": "ratings.default.svc.cluster.local:9080",
|
|||
|
"domains": [
|
|||
|
"ratings.default.svc.cluster.local",
|
|||
|
"ratings.default.svc.cluster.local:9080",
|
|||
|
"ratings",
|
|||
|
"ratings:9080",
|
|||
|
"ratings.default.svc",
|
|||
|
"ratings.default.svc:9080",
|
|||
|
"ratings.default",
|
|||
|
"ratings.default:9080",
|
|||
|
"10.8.8.106",
|
|||
|
"10.8.8.106:9080"
|
|||
|
],
|
|||
|
"routes": [
|
|||
|
{
|
|||
|
"name": "default",
|
|||
|
"match": {
|
|||
|
"prefix": "/"
|
|||
|
},
|
|||
|
"route": {
|
|||
|
"cluster": "outbound|9080||ratings.default.svc.cluster.local",
|
|||
|
"timeout": "0s",
|
|||
|
"retryPolicy": {
|
|||
|
"retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
|
|||
|
"numRetries": 2,
|
|||
|
"retryHostPredicate": [
|
|||
|
{
|
|||
|
"name": "envoy.retry_host_predicates.previous_hosts"
|
|||
|
}
|
|||
|
],
|
|||
|
"hostSelectionRetryMaxAttempts": "5",
|
|||
|
"retriableStatusCodes": [
|
|||
|
503
|
|||
|
]
|
|||
|
},
|
|||
|
"maxStreamDuration": {
|
|||
|
"maxStreamDuration": "0s",
|
|||
|
"grpcTimeoutHeaderMax": "0s"
|
|||
|
}
|
|||
|
},
|
|||
|
"decorator": {
|
|||
|
"operation": "ratings.default.svc.cluster.local:9080/*"
|
|||
|
}
|
|||
|
}
|
|||
|
],
|
|||
|
"includeRequestAttemptCount": true
|
|||
|
},
|
|||
|
/*省略部分内容*/
|
|||
|
],
|
|||
|
"validateClusters": false
|
|||
|
}
|
|||
|
]
|
|||
|
```
|
|||
|
|
|||
|
从该 Virtual Host 配置中可以看到将流量路由到`outbound|9080||ratings.default.svc.cluster.local` 集群。
|
|||
|
|
|||
|
**`outbound|9080||ratings.default.svc.cluster.local` 集群的端点**
|
|||
|
|
|||
|
运行 `istioctl proxy-config endpoint reviews-v1-545db77b95-jkgv2 --port 9080 -o json --cluster "outbound|9080||ratings.default.svc.cluster.local"` 查看集群的 Endpoint 配置,结果如下。
|
|||
|
|
|||
|
```json
|
|||
|
[
|
|||
|
{
|
|||
|
"name": "outbound|9080||ratings.default.svc.cluster.local",
|
|||
|
"addedViaApi": true,
|
|||
|
"hostStatuses": [
|
|||
|
{
|
|||
|
"address": {
|
|||
|
"socketAddress": {
|
|||
|
"address": "10.4.1.12",
|
|||
|
"portValue": 9080
|
|||
|
}
|
|||
|
},
|
|||
|
"stats": [
|
|||
|
{
|
|||
|
"name": "cx_connect_fail"
|
|||
|
},
|
|||
|
{
|
|||
|
"name": "cx_total"
|
|||
|
},
|
|||
|
{
|
|||
|
"name": "rq_error"
|
|||
|
},
|
|||
|
{
|
|||
|
"name": "rq_success"
|
|||
|
},
|
|||
|
{
|
|||
|
"name": "rq_timeout"
|
|||
|
},
|
|||
|
{
|
|||
|
"name": "rq_total"
|
|||
|
},
|
|||
|
{
|
|||
|
"type": "GAUGE",
|
|||
|
"name": "cx_active"
|
|||
|
},
|
|||
|
{
|
|||
|
"type": "GAUGE",
|
|||
|
"name": "rq_active"
|
|||
|
}
|
|||
|
],
|
|||
|
"healthStatus": {
|
|||
|
"edsHealthStatus": "HEALTHY"
|
|||
|
},
|
|||
|
"weight": 1,
|
|||
|
"locality": {
|
|||
|
"region": "us-west2",
|
|||
|
"zone": "us-west2-a"
|
|||
|
}
|
|||
|
}
|
|||
|
],
|
|||
|
"circuitBreakers": {
|
|||
|
"thresholds": [
|
|||
|
{
|
|||
|
"maxConnections": 4294967295,
|
|||
|
"maxPendingRequests": 4294967295,
|
|||
|
"maxRequests": 4294967295,
|
|||
|
"maxRetries": 4294967295
|
|||
|
},
|
|||
|
{
|
|||
|
"priority": "HIGH",
|
|||
|
"maxConnections": 1024,
|
|||
|
"maxPendingRequests": 1024,
|
|||
|
"maxRequests": 1024,
|
|||
|
"maxRetries": 3
|
|||
|
}
|
|||
|
]
|
|||
|
},
|
|||
|
"observabilityName": "outbound|9080||ratings.default.svc.cluster.local"
|
|||
|
}
|
|||
|
]
|
|||
|
```
|
|||
|
|
|||
|
我们看到端点的地址是 `10.4.1.12`。实际上,Endpoint 可以是一个或多个,sidecar 将根据一定规则选择适当的 Endpoint 来路由。至此 `review` Pod找到了它上游服务 `rating` 的 Endpoint。
|
|||
|
|
|||
|
## 参考
|
|||
|
|
|||
|
- [Istio 中的 Sidecar 注入、透明流量劫持及流量路由过程详解 - jimmysong.io](https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing/)
|