# 深入理解Istio Service Mesh中的Envoy Sidecar代理的路由轉發
**注意:本書中的 Service Mesh 章節已不再維護,請轉到 [istio-handbook](https://jimmysong.io/istio-handbook) 中瀏覽。**
本文以 Istio 官方的 [bookinfo 示例](https://preliminary.istio.io/zh/docs/examples/bookinfo)來講解在進入 Pod 的流量被 iptables 轉交給 Envoy sidecar 后,Envoy 是如何做路由轉發的,詳述了 Inbound 和 Outbound 處理過程。關于流量攔截的詳細分析請參考[理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持](https://jimmysong.io/posts/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/)。
下面是 Istio 官方提供的 bookinfo 的請求流程圖,假設 bookinfo 應用的所有服務中沒有配置 DestinationRule。

下面是 Istio 自身組件與 Bookinfo 示例的連接關系圖,我們可以看到所有的 HTTP 連接都在 9080 端口監聽。

可以在 [Google Drive](https://drive.google.com/open?id=19ed3_tkjf6RgGboxllMdt_Ytd5_cocib) 上下載原圖。
## 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 和應用程序容器。這一步的過程請參考[通過管理接口獲取完整配置](https://zhaohuabing.com/post/2018-09-25-istio-traffic-management-impl-intro/#%E9%80%9A%E8%BF%87%E7%AE%A1%E7%90%86%E6%8E%A5%E5%8F%A3%E8%8E%B7%E5%8F%96%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE)。
> **Sidecar proxy 與應用容器的啟動順序問題**
>
> 啟動 sidecar proxy 和應用容器,究竟哪個容器先啟動呢?正常情況是 Envoy Sidecar 和應用程序容器全部啟動完成后再開始接收流量請求。但是我們無法預料哪個容器會先啟動,那么容器啟動順序是否會對 Envoy 劫持流量有影響呢?答案是肯定的,不過分為以下兩種情況。
>
> **情況1:應用容器先啟動,而 sidecar proxy 仍未就緒**
>
> 這種情況下,流量被 iptables 轉移到 15001 端口,而 Pod 中沒有監聽該端口,TCP 鏈接就無法建立,請求失敗。
>
> **情況2:Sidecar 先啟動,請求到達而應用程序仍未就緒**
>
> 這種情況下請求也肯定會失敗,至于是在哪一步開始失敗的,留給讀者來思考。
**問題**:如果為 sidecar proxy 和應用程序容器添加[就緒和存活探針](https://jimmysong.io/kubernetes-handbook/guide/configure-liveness-readiness-probes.html)是否可以解決該問題呢?
**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 是如何做流量攔截和路由轉發的。可以在 [Google Drive](https://drive.google.com/file/d/1n-h235tm8DnL_RqxTTA95rgGtrLkBsyr/view?usp=sharing) 上下載原圖。

第一步開始時,`productpage` Pod 中的 Envoy sidecar 已經通過 EDS 選擇出了要請求的 `reviews` 服務的一個 Pod,知曉了其 IP 地址,發送 TCP 連接請求。
Istio 官網中的 [Envoy 配置深度解析](https://preliminary.istio.io/zh/help/ops/traffic-management/proxy-cmd/#envoy-%E9%85%8D%E7%BD%AE%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90)中是以發起 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。
```ini
ADDRESS PORT TYPE
172.33.3.3 9080 HTTP <--- 接收所有 Inbound HTTP 流量,該地址即為當前 Pod 的 IP 地址
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 <--- 接收所有經 iptables 攔截的 Inbound 和 Outbound 流量并轉交給虛擬監聽器處理
```
當來自 `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 的詳細配置。
```json
{
"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 接收的端口可能與[原始目的地址](http://www.servicemesher.com/envoy/configuration/listener_filters/original_dst_filter.html)的端口不一樣,如此處 proxy 接收的端口為 15001,而原始目的地端口為 9080。當此標志設置為 true 時,Listener 將連接重定向到與原始目的地址關聯的 Listener,此處為 `172.33.3.3:9080`。如果沒有與原始目的地址關聯的 Listener,則連接由接收它的 Listener 處理,即該 `virtual` Listener,經過 `envoy.tcp_proxy` 過濾器處理轉發給 `BlackHoleCluster`,這個 Cluster 的作用正如它的名字,當 Envoy 找不到匹配的虛擬監聽器時,就會將請求發送給它,并返回 404。這個將于下文提到的 Listener 中設置 `bindToPort` 相呼應。
**注意**:該參數將被廢棄,請使用[原始目的地址](http://www.servicemesher.com/envoy/configuration/listener_filters/original_dst_filter.html)的 Listener filter 替代。該參數的主要用途是:Envoy 通過監聽 15001 端口將 iptables 攔截的流量經由其他 Listener 處理而不是直接轉發出去,詳情見 [Virtual Listener](https://zhaohuabing.com/post/2018-09-25-istio-traffic-management-impl-intro/#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` 查看。
```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`](https://www.envoyproxy.io/docs/envoy/v1.6.0/api-v1/listeners/listeners) 的配置,其值為 `false`,該配置的缺省值為 `true`,表示將 Listener 綁定到端口上,此處設置為 `false` 則該 Listener 只能處理其他 Listener 轉移過來的流量,即上文所說的 `virtual` Listener,我們看其中的 filterChains.filters 中的 `envoy.http_connection_manager` 配置部分:
```json
"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 的配置如下。
```json
[
{
"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 執行路由動作。這一部分可以參考 Istio 官網中的 [Envoy 深度配置解析](https://preliminary.istio.io/zh/help/ops/traffic-management/proxy-cmd/#envoy-%E9%85%8D%E7%BD%AE%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90)。
**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。
```json
[{
"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 端點的方式折中。
```bash
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 的結果如下。
```json
{
"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。
## 參考
- [調試 Envoy 和 Pilot - istio.io](https://preliminary.istio.io/zh/help/ops/traffic-management/proxy-cmd/)
- [理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持 - jimmysong.io](https://jimmysong.io/posts/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/)
- [Istio流量管理實現機制深度解析 - zhaohuabing.com](https://zhaohuabing.com/post/2018-09-25-istio-traffic-management-impl-intro/)
- 序言
- 云原生
- 云原生(Cloud Native)的定義
- CNCF - 云原生計算基金會簡介
- CNCF章程
- 云原生的設計哲學
- Play with Kubernetes
- 快速部署一個云原生本地實驗環境
- Kubernetes與云原生應用概覽
- 云原生應用之路——從Kubernetes到Cloud Native
- 云原生編程語言
- 云原生編程語言Ballerina
- 云原生編程語言Pulumi
- 云原生的未來
- Kubernetes架構
- 設計理念
- Etcd解析
- 開放接口
- CRI - Container Runtime Interface(容器運行時接口)
- CNI - Container Network Interface(容器網絡接口)
- CSI - Container Storage Interface(容器存儲接口)
- Kubernetes中的網絡
- Kubernetes中的網絡解析——以flannel為例
- Kubernetes中的網絡解析——以calico為例
- 具備API感知的網絡和安全性管理開源軟件Cilium
- Cilium架構設計與概念解析
- 資源對象與基本概念解析
- Pod狀態與生命周期管理
- Pod概覽
- Pod解析
- Init容器
- Pause容器
- Pod安全策略
- Pod的生命周期
- Pod Hook
- Pod Preset
- Pod中斷與PDB(Pod中斷預算)
- 集群資源管理
- Node
- Namespace
- Label
- Annotation
- Taint和Toleration(污點和容忍)
- 垃圾收集
- 控制器
- Deployment
- StatefulSet
- DaemonSet
- ReplicationController和ReplicaSet
- Job
- CronJob
- Horizontal Pod Autoscaling
- 自定義指標HPA
- 準入控制器(Admission Controller)
- 服務發現
- Service
- Ingress
- Traefik Ingress Controller
- 身份與權限控制
- ServiceAccount
- RBAC——基于角色的訪問控制
- NetworkPolicy
- 存儲
- Secret
- ConfigMap
- ConfigMap的熱更新
- Volume
- Persistent Volume(持久化卷)
- Storage Class
- 本地持久化存儲
- 集群擴展
- 使用自定義資源擴展API
- 使用CRD擴展Kubernetes API
- Aggregated API Server
- APIService
- Service Catalog
- 資源調度
- QoS(服務質量等級)
- 用戶指南
- 資源對象配置
- 配置Pod的liveness和readiness探針
- 配置Pod的Service Account
- Secret配置
- 管理namespace中的資源配額
- 命令使用
- Docker用戶過度到kubectl命令行指南
- kubectl命令概覽
- kubectl命令技巧大全
- 使用etcdctl訪問kubernetes數據
- 集群安全性管理
- 管理集群中的TLS
- kubelet的認證授權
- TLS bootstrap
- 創建用戶認證授權的kubeconfig文件
- IP偽裝代理
- 使用kubeconfig或token進行用戶身份認證
- Kubernetes中的用戶與身份認證授權
- Kubernetes集群安全性配置最佳實踐
- 訪問Kubernetes集群
- 訪問集群
- 使用kubeconfig文件配置跨集群認證
- 通過端口轉發訪問集群中的應用程序
- 使用service訪問群集中的應用程序
- 從外部訪問Kubernetes中的Pod
- Cabin - Kubernetes手機客戶端
- Kubernetic - Kubernetes桌面客戶端
- Kubernator - 更底層的Kubernetes UI
- 在Kubernetes中開發部署應用
- 適用于kubernetes的應用開發部署流程
- 遷移傳統應用到Kubernetes中——以Hadoop YARN為例
- 最佳實踐概覽
- 在CentOS上部署Kubernetes集群
- 創建TLS證書和秘鑰
- 創建kubeconfig文件
- 創建高可用etcd集群
- 安裝kubectl命令行工具
- 部署master節點
- 安裝flannel網絡插件
- 部署node節點
- 安裝kubedns插件
- 安裝dashboard插件
- 安裝heapster插件
- 安裝EFK插件
- 生產級的Kubernetes簡化管理工具kubeadm
- 使用kubeadm在Ubuntu Server 16.04上快速構建測試集群
- 服務發現與負載均衡
- 安裝Traefik ingress
- 分布式負載測試
- 網絡和集群性能測試
- 邊緣節點配置
- 安裝Nginx ingress
- 安裝配置DNS
- 安裝配置Kube-dns
- 安裝配置CoreDNS
- 運維管理
- Master節點高可用
- 服務滾動升級
- 應用日志收集
- 配置最佳實踐
- 集群及應用監控
- 數據持久化問題
- 管理容器的計算資源
- 集群聯邦
- 存儲管理
- GlusterFS
- 使用GlusterFS做持久化存儲
- 使用Heketi作為Kubernetes的持久存儲GlusterFS的external provisioner
- 在OpenShift中使用GlusterFS做持久化存儲
- GlusterD-2.0
- Ceph
- 用Helm托管安裝Ceph集群并提供后端存儲
- 使用Ceph做持久化存儲
- 使用rbd-provisioner提供rbd持久化存儲
- OpenEBS
- 使用OpenEBS做持久化存儲
- Rook
- NFS
- 利用NFS動態提供Kubernetes后端存儲卷
- 集群與應用監控
- Heapster
- 使用Heapster獲取集群和對象的metric數據
- Prometheus
- 使用Prometheus監控kubernetes集群
- Prometheus查詢語言PromQL使用說明
- 使用Vistio監控Istio服務網格中的流量
- 分布式跟蹤
- OpenTracing
- 服務編排管理
- 使用Helm管理Kubernetes應用
- 構建私有Chart倉庫
- 持續集成與發布
- 使用Jenkins進行持續集成與發布
- 使用Drone進行持續集成與發布
- 更新與升級
- 手動升級Kubernetes集群
- 升級dashboard
- 領域應用概覽
- 微服務架構
- 微服務中的服務發現
- 使用Java構建微服務并發布到Kubernetes平臺
- Spring Boot快速開始指南
- Service Mesh 服務網格
- 企業級服務網格架構
- Service Mesh基礎
- Service Mesh技術對比
- 采納和演進
- 定制和集成
- 總結
- Istio
- 安裝并試用Istio service mesh
- 配置請求的路由規則
- 安裝和拓展Istio service mesh
- 集成虛擬機
- Istio中sidecar的注入規范及示例
- 如何參與Istio社區及注意事項
- Istio教程
- Istio免費學習資源匯總
- 深入理解Istio Service Mesh中的Envoy Sidecar注入與流量劫持
- 深入理解Istio Service Mesh中的Envoy Sidecar代理的路由轉發
- Linkerd
- Linkerd 使用指南
- Conduit
- Condiut概覽
- 安裝Conduit
- Envoy
- Envoy的架構與基本術語
- Envoy作為前端代理
- Envoy mesh教程
- SOFAMesh
- SOFAMesh中的Dubbo on x-protocol
- SOFAMosn
- 使用 SOFAMosn 構建 SOFAMesh
- 大數據
- Spark standalone on Kubernetes
- 運行支持Kubernetes原生調度的Spark程序
- Serverless架構
- 理解Serverless
- FaaS-函數即服務
- OpenFaaS快速入門指南
- 邊緣計算
- 人工智能