kubernetes-handbook/usecases/istio-traffic-routing.md

316 lines
14 KiB
Markdown
Raw Permalink Normal View History

2022-04-27 12:12:43 +08:00
# 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下游地址
- PORTEnvoy 监听器监听的端口
- 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/)