理解 Istio Service Mesh 中 Envoy Sidecar 代理的路由轉發

本文以 Istio 官方的 bookinfo 示例來講解在進入 Pod 的流量被 iptables 轉交給 Envoy sidecar 後,Envoy 是如何做路由轉發的,詳述了 Inbound 和 Outbound 處理過程。

下面是 Istio 官方

理解 Istio Service Mesh 中 Envoy Sidecar 代理的路由轉發

提供的 bookinfo 的請求流程圖,假設 bookinfo 應用的所有服務中沒有配置 DestinationRule。

Sidecar 注入及流量劫持步驟概述

下面是從 Sidecar 注入、Pod 啟動到 Sidecar proxy 攔截流量及 Envoy 處理路由的步驟概覽。

1. Kubernetes 通過 Admission Controller 自動注入,或者用戶使用 istioctl 命令手動注入 sidecar 容器。

2. 應用 YAML 配置部署應用,此時 Kubernetes API server 接收到的服務創建配置文件中已經包含了 Init 容器及 sidecar proxy。

3. 在 sidecar proxy 容器和應用容器啟動之前,首先運行 Init 容器,Init 容器用於設置 iptables(Istio 中默認的流量攔截方式,還可以使用 BPF、IPVS 等方式) 將進入 pod 的流量劫持到 Envoy sidecar proxy。所有 TCP 流量(Envoy 目前只支持 TCP 流量)將被 sidecar 劫持,其他協議的流量將按原來的目的地請求。

4. 啟動 Pod 中的 Envoy sidecar proxy 和應用程序容器。

Sidecar proxy 與應用容器的啟動順序問題

啟動 sidecar proxy 和應用容器,究竟哪個容器先啟動呢?正常情況是 Envoy Sidecar 和應用程序容器全部啟動完成後再開始接收流量請求。但是我們無法預料哪個容器會先啟動,那麼容器啟動順序是否會對 Envoy 劫持流量有影響呢?答案是肯定的,不過分為以下兩種情況。

情況1:應用容器先啟動,而 sidecar proxy 仍未就緒

這種情況下,流量被 iptables 轉移到 15001 端口,而 Pod 中沒有監聽該端口,TCP 鏈接就無法建立,請求失敗。

情況2:Sidecar 先啟動,請求到達而應用程序仍未就緒

這種情況下請求也肯定會失敗,至於是在哪一步開始失敗的,留給讀者來思考。

問題:如果為 sidecar proxy 和應用程序容器添加就緒和存活探針是否可以解決該問題呢?

5. 不論是進入還是從 Pod 發出的 TCP 請求都會被 iptables 劫持,inbound 流量被劫持後經 Inbound Handler 處理後轉交給應用程序容器處理,outbound 流量被 iptables 劫持後轉交給 Outbound Handler 處理,並確定轉發的 upstream 和 Endpoint。

6. Sidecar proxy 請求 Pilot 使用 xDS 協議同步 Envoy 配置,其中包括 LDS、EDS、CDS 等,不過為了保證更新的順序,Envoy 會直接使用 ADS 向 Pilot 請求配置更新。

Envoy 如何處理路由轉發

下圖展示的是 productpage 服務請求訪問 http://reviews.default.svc.cluster.local:9080/,當流量進入 reviews 服務內部時,reviews 服務內部的 Envoy Sidecar 是如何做流量攔截和路由轉發的。

理解 Istio Service Mesh 中 Envoy Sidecar 代理的路由轉發

第一步開始時,productpage Pod 中的 Envoy sidecar 已經通過 EDS 選擇出了要請求的 reviews 服務的一個 Pod,知曉了其 IP 地址,發送 TCP 連接請求。

Istio 官網中的 Envoy 配置深度解析中是以發起 HTTP 請求的一方來詳述 Envoy 做流量轉發的過程,而本文中考慮的是接受 downstream 的流量的一方,它既要接收 downstream 發來的請求,自己還需要請求其他服務,例如 reviews 服務中的 Pod 還需要請求 ratings 服務。

reviews 服務有三個版本,每個版本有一個實例,三個版本中的 sidecar 工作步驟類似,下文只以 reviews-v1-cb8655c75-b97zc 這一個 Pod 中的 Sidecar 流量轉發步驟來說明。

理解 Inbound Handler

Inbound handler 的作用是將 iptables 攔截到的 downstream 的流量轉交給 localhost,與 Pod 內的應用程序容器建立連接。

查看下 reviews-v1-cb8655c75-b97zc pod 中的 Listener。

運行 istioctl pc listener reviews-v1-cb8655c75-b97zc 查看該 Pod 中的具有哪些 Listener。

ADDRESS PORT TYPE

172.33.3.3 9080 HTTP

10.254.0.1 443 TCP

10.254.4.253 80 TCP |

10.254.4.253 8080 TCP |

10.254.109.182 443 TCP |

10.254.22.50 15011 TCP |

10.254.22.50 853 TCP |

10.254.79.114 443 TCP |

10.254.143.179 15011 TCP |

10.254.0.2 53 TCP | 接收與 0.0.0.0_15001 監聽器配對的 Outbound 非 HTTP 流量

10.254.22.50 443 TCP |

10.254.16.64 42422 TCP |

10.254.127.202 16686 TCP |

10.254.22.50 31400 TCP |

10.254.22.50 8060 TCP |

10.254.169.13 14267 TCP |

10.254.169.13 14268 TCP |

10.254.32.134 8443 TCP |

10.254.118.196 443 TCP

0.0.0.0 15004 HTTP

0.0.0.0 8080 HTTP |

0.0.0.0 15010 HTTP |

0.0.0.0 8088 HTTP |

0.0.0.0 15031 HTTP |

0.0.0.0 9090 HTTP |

0.0.0.0 9411 HTTP | 接收與 0.0.0.0_15001 配對的 Outbound HTTP 流量

0.0.0.0 80 HTTP |

0.0.0.0 15030 HTTP |

0.0.0.0 9080 HTTP |

0.0.0.0 9093 HTTP |

0.0.0.0 3000 HTTP |

0.0.0.0 8060 HTTP |

0.0.0.0 9091 HTTP

0.0.0.0 15001 TCP

當來自 productpage 的流量抵達 reviews Pod 的時候已經,downstream 必須明確知道 Pod 的 IP 地址為 172.33.3.3 所以才會訪問該 Pod,所以該請求是 172.33.3.3:9080。

virtual Listener

從該 Pod 的 Listener 列表中可以看到,0.0.0.0:15001/TCP 的 Listener(其實際名字是 virtual)監聽所有的 Inbound 流量,下面是該 Listener 的詳細配置。

{

"name": "virtual",

"address": {

"socketAddress": {

"address": "0.0.0.0",

"portValue": 15001

}

},

"filterChains": [

{

"filters": [

{

"name": "envoy.tcp_proxy",

"config": {

"cluster": "BlackHoleCluster",

"stat_prefix": "BlackHoleCluster"

}

}

]

}

],

"useOriginalDst": true

}

UseOriginalDst:從配置中可以看出 useOriginalDst 配置指定為 true,這是一個布爾值,缺省為 false,使用 iptables 重定向連接時,proxy 接收的端口可能與原始目的地址的端口不一樣,如此處 proxy 接收的端口為 15001,而原始目的地端口為 9080。當此標誌設置為 true 時,Listener 將連接重定向到與原始目的地址關聯的 Listener,此處為 172.33.3.3:9080。如果沒有與原始目的地址關聯的 Listener,則連接由接收它的 Listener 處理,即該 virtual Listener,經過 envoy.tcp_proxy 過濾器處理轉發給 BlackHoleCluster,這個 Cluster 的作用正如它的名字,當 Envoy 找不到匹配的虛擬監聽器時,就會將請求發送給它,並返回 404。這個將於下文提到的 Listener 中設置 bindToPort 相呼應。

注意:該參數將被廢棄,請使用原始目的地址的 Listener filter 替代。該參數的主要用途是:Envoy 通過監聽 15001 端口將 iptables 攔截的流量經由其他 Listener 處理而不是直接轉發出去,詳情見 Virtual Listener。

Listener 172.33.3.3_9080

上文說到進入 Inbound handler 的流量被 virtual Listener 轉移到 172.33.3.3_9080 Listener,我們在查看下該 Listener 配置。

運行 istioctl pc listener reviews-v1-cb8655c75-b97zc --address 172.33.3.3 --port 9080 -o json 查看。

[{

"name": "172.33.3.3_9080",

"address": {

"socketAddress": {

"address": "172.33.3.3",

"portValue": 9080

}

},

"filterChains": [

{

"filterChainMatch": {

"transportProtocol": "raw_buffer"

},

"filters": [

{

"name": "envoy.http_connection_manager",

"config": {

...

"route_config": {

"name": "inbound|9080||reviews.default.svc.cluster.local",

"validate_clusters": false,

"virtual_hosts": [

{

"domains": [

"*"

],

"name": "inbound|http|9080",

"routes": [

{

...

"route": {

"cluster": "inbound|9080||reviews.default.svc.cluster.local",

"max_grpc_timeout": "0.000s",

"timeout": "0.000s"

}

}

]

}

]

},

"use_remote_address": false,

...

}

}

],

"deprecatedV1": {

"bindToPort": false

}

...

},

{

"filterChainMatch": {

"transportProtocol": "tls"

},

"tlsContext": {...

},

"filters": [...

]

}

],

...

}]

bindToPort:注意其中有一個 bindToPort 的配置,其值為 false,該配置的缺省值為 true,表示將 Listener 綁定到端口上,此處設置為 false 則該 Listener 只能處理其他 Listener 轉移過來的流量,即上文所說的 virtual Listener,我們看其中的 filterChains.filters 中的 envoy.http_connection_manager 配置部分:

"route_config": {

"name": "inbound|9080||reviews.default.svc.cluster.local",

"validate_clusters": false,

"virtual_hosts": [

{

"domains": [

"*"

],

"name": "inbound|http|9080",

"routes": [

{

...

"route": {

"cluster": "inbound|9080||reviews.default.svc.cluster.local",

"max_grpc_timeout": "0.000s",

"timeout": "0.000s"

}

}

]

}

]

}

該配置表示流量將轉交給 Cluster inbound|9080||reviews.default.svc.cluster.local 處理。

Cluster inbound|9080||reviews.default.svc.cluster.local

運行 istioctl pc cluster reviews-v1-cb8655c75-b97zc --fqdn reviews.default.svc.cluster.local --direction inbound -o json 查看該 Cluster 的配置如下。

[

{

"name": "inbound|9080||reviews.default.svc.cluster.local",

"connectTimeout": "1.000s",

"hosts": [

{

"socketAddress": {

"address": "127.0.0.1",

"portValue": 9080

}

}

],

"circuitBreakers": {

"thresholds": [

{}

]

}

}

]

可以看到該 Cluster 的 Endpoint 直接對應的就是 localhost,再經過 iptables 轉發流量就被應用程序容器消費了。

理解 Outbound Handler

因為 reviews 會向 ratings 服務發送 HTTP 請求,請求的地址是:http://ratings.default.svc.cluster.local:9080/,Outbound handler 的作用是將 iptables 攔截到的本地應用程序發出的流量,經由 Envoy 判斷如何路由到 upstream。

應用程序容器發出的請求為 Outbound 流量,被 iptables 劫持後轉移給 Envoy Outbound handler 處理,然後經過 virtual Listener、0.0.0.0_9080 Listener,然後通過 Route 9080 找到 upstream 的 cluster,進而通過 EDS 找到 Endpoint 執行路由動作。

Route 9080

reviews 會請求 ratings 服務,運行 istioctl proxy-config routes reviews-v1-cb8655c75-b97zc --name 9080 -o json 查看 route 配置,因為 Envoy 會根據 HTTP header 中的 domains 來匹配 VirtualHost,所以下面只列舉了 ratings.default.svc.cluster.local:9080 這一個 VirtualHost。

[{

"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.cluster",

"ratings.default.svc.cluster:9080",

"ratings.default.svc",

"ratings.default.svc:9080",

"ratings.default",

"ratings.default:9080",

"10.254.234.130",

"10.254.234.130:9080"

],

"routes": [

{

"match": {

"prefix": "/"

},

"route": {

"cluster": "outbound|9080||ratings.default.svc.cluster.local",

"timeout": "0.000s",

"maxGrpcTimeout": "0.000s"

},

"decorator": {

"operation": "ratings.default.svc.cluster.local:9080/*"

},

"perFilterConfig": {...

}

}

]

},

..]

從該 Virtual Host 配置中可以看到將流量路由到 Cluster outbound|9080||ratings.default.svc.cluster.local。

Endpoint outbound|9080||ratings.default.svc.cluster.local

Istio 1.1 以前版本不支持使用 istioctl 命令直接查詢 Cluster 的 Endpoint,可以使用查詢 Pilot 的 debug 端點的方式折中。

kubectl exec reviews-v1-cb8655c75-b97zc -c istio-proxy curl http://istio-pilot.istio-system.svc.cluster.local:9093/debug/edsz > endpoints.json

endpoints.json 文件中包含了所有 Cluster 的 Endpoint 信息,我們只選取其中的 outbound|9080||ratings.default.svc.cluster.local Cluster 的結果如下。

{

"clusterName": "outbound|9080||ratings.default.svc.cluster.local",

"endpoints": [

{

"locality": {

},

"lbEndpoints": [

{

"endpoint": {

"address": {

"socketAddress": {

"address": "172.33.100.2",

"portValue": 9080

}

}

},

"metadata": {

"filterMetadata": {

"istio": {

"uid": "kubernetes://ratings-v1-8558d4458d-ns6lk.default"

}

}

}

}

]

}

]

}

Endpoint 可以是一個或多個,Envoy 將根據一定規則選擇適當的 Endpoint 來路由。

:Istio 1.1 將支持 istioctl pc endpoint 命令來查詢 Endpoint。


分享到:


相關文章: