全景剖析阿里雲容器網絡數據鏈路(六):ASM Istio

本系列文章由余凱執筆創作,聯合作者:阿里雲容器服務 謝石 對本文亦有貢獻

近幾年,企業基礎設施雲原生化的趨勢越來越明顯,從最開始的IaaS化到現在的微服務化,客戶的顆粒度精細化和可觀測性的需求也更加強烈。容器網絡爲了滿足客戶更高性能和更高密度的需求,也一直在高速的發展和演進中,這必然給客戶對雲原生網絡的可觀測性帶來了極高的門檻和挑戰。爲了提高雲原生網絡的可觀測性,同時便於客戶和前後線同學增加對業務鏈路的可讀性,ACK產研和AES聯合共建,合作開發了ack net-exporter和雲原生網絡數據面可觀測性系列,幫助客戶和前後線同學瞭解雲原生網絡架構體系,簡化對雲原生網絡的可觀測性的門檻,優化客戶運維和售後同學處理疑難問題的體驗 ,提高雲原生網絡的鏈路的穩定性。

1.png

△ 服務網格示例

2.png

△ Istio數據面示意圖

Kubernetes的橫空出現打破了底層服務器、底層網絡等計算資源的界限,給業務的靈活部署、快速恢復、彈性伸縮、資源效率最大化帶來了無限可能。但是業務場景的‘貪婪’是無限的,隨着微服務趨勢大肆發展,業務上對於同一個service,不同版本和流量控制有着更精細化的顆粒度的需求,最好能實現Pod維度的流量控制,可觀測性等等。這些在Kubernetes上是無法實現的:

  1. 從流量角度,K8s最小的控制維度是service, 其他比如金絲雀等發佈,藉助各種ingress controller或者其他組件實現,並且這些也無法實現Pod之間的流量和連接狀態的可觀測性。

  2. K8s給服務微型化,小型化創造了條件, 如果前後端服務存在調用關心,他們如果使用共享通信庫,則會在開發階段就要求所有微服務使用相同的邏輯語言和堆棧,這從某種程度上又大大限制微服務的獨立化,無法實現完全的‘漠不關心’

  3. 將原來集成在同一個ECS上的服務拆分成不同的模塊,這些模塊之間調用涉及跨ECS等,那麼必然需要在代碼開發階段需要考慮超時,重試,連接失敗等邏輯機制,而這些與微服務最核心的服務應用其實沒有太大關係,但是開發工作往往耗費大量的經歷在邏輯設計上。

那麼,有沒有辦法實現上述和微服務的業務完全隔離呢?Istio的出現給這個帶來了相對完美的解決方案,讓應用這和開發者更加關注業務本身的開發迭代。Istio利用了K8s的Pod概念,會根據使用者的配置,在每個被注入的Pod部署時,自動注入istio-proxy 容器和initial 容器。initial容器的目的是通過修改Pod單獨網絡命名空間的iptables規則,讓需要代理的流量進入到istio-proxy 監聽的端口, istio-proxy監聽出入 兩個端口,根據網格配置,來實現對出入流量的代理實現和干預。而被同一個istio注入的載體,都被視爲同一個服務網格之內,他們之間的調用已經脫離了service的層面,會命中相關的istio cluster配置的endpoint,這樣我們就可以實現Pod維度的流量管理、觀測性、安全性等配置。

本文是[全景剖析容器網絡數據鏈路]第六部分,主要介紹ASM Istio模式下,數據面鏈路的轉發鏈路,一是通過了解不同場景下的數據面轉發鏈路,從而探知客戶在不同的場景下訪問結果表現的原因,幫助客戶進一步優化業務架構;另一方面,通過深入瞭解轉發鏈路,在遇到容器網絡抖動時候,客戶運維以及阿里雲同學可以知道在哪些鏈路點進行部署觀測手動,從而進一步定界問題方向和原因。

系列一:全景剖析阿里雲容器網絡數據鏈路(一):Flannel

系列二:全景剖析阿里雲容器網絡數據鏈路(二):Terway ENI

系列三:全景剖析阿里雲容器網絡數據鏈路(三):Terway ENIIP

系列四:全景剖析阿里雲容器網絡數據鏈路(四):Terway IPVLAN+EBPF

系列五:全景剖析阿里雲容器網絡數據鏈路(五):Terway ENI-Trunking

ASM Istio 流量代理

1.1 Pod注入

ASM默認提供了一個Webhook控制器,可以將Sidecar代理自動添加到可用的Pod中。通過下面的命令可以看到ASM注入的集羣有個istio-sidecar-injector-1-15-3的mutatingwebhookconfiguration, 查看webhook內容,可以看到其中一條就是有 istio-inject:enabled 標籤的namespace裏的pod創建時候會自動注入。

3.png

4.png

5.png

除了命名空間維度,還有Pod維度,其他註解方式等多種維度實現K8s集羣是否被加入到Istio服務網格中。爲了充分利用服務網格的所有特性,服務網格中ACK集羣的應用Pod必須包含一個Sidecar代理。除了手動注入方式外,通常建議啓用自動注入的方式來簡化部署,ASM已經實現了注入配置的可視化操作,具體請見**多種方式靈活開啓自動注入 [ 1] **。

6.png

1.2 Pod流量轉發

通過describe被注入的Pod, 可以發現Pod中除了設置好的業務container,還多出兩個容器:istio-proxy和init  container:istio-init。這兩個容器的鏡像是一樣的,只是運行的命令的不一樣,這樣的好處是隻需要拉取一份鏡像,節省了拉取鏡像的時間。

7.png

Init Container

Init container 利用的是K8s的特性,一種具有特權的特殊容器,在Pod內的應用容器啓動之前運行。Init容器可以包括一些應用鏡像中不存在的實用工具和安裝腳本。每個Pod中可以包含多個容器和多個Init容器。他與普通容器很像,但是有自己獨特點:

  1. 多個init容器是串行運行的。也就是說多個init容器會依序運行,等上一個init容器運行完畢結束後,纔會開始運行下一個容器。

  2. 只有等到所有的init容器全部運行結束退出後,業務容器纔開始啓動,在這之前,pod不會處於ready。

  3. 如果Pod的Init容器失敗,kubelet根據pod設置的restartPolicy進行相應的action。

既然現在瞭解了Init container的作用,那我們來看一下istio-init在啓動的過程中做了哪些事情,可以通過下面的命令:

kubectl  logs -n istio-inject productpage-v1-797d845774-dndmk -c istio-init

8.png

9.png

可以看到istio-init在啓動過程中進行了一連串的iptables規則的生成和配置,比如出方向轉發到15001端口;入方向轉發到15006端口;訪問15008端口,直接return不進行流量劫持等等。那有什麼辦法可以自定義配置麼?查看pod的信息可以看到相關配置的啓動參數,也就通過相關規則實現了出入流量重定向到設置的端口。

10.png

-p: 所有出方向的流量被iptables重定向到15001端口

-z: 所有入方向的流量被iptables重定向到15006端口

-u: 用於排除用戶ID爲1337,可以視爲envoy應用本身使用UID 1337

-m: 流量重定向模式,“REDIRECT” 或 “TPROXY”

-i:  重定向出方向的地址範圍, “*” 表示重定向所有出站流量。

-x: 指將從重定向出方向中排除的 IP 地址範圍

-b: 重定向入站端口列表

-d: 重定向入站端口中排除的端口列表

我們從Pod的視角去觀察,將Pod視爲一個整體,裏面有istio-proxy容器和業務容器APP container

入方向流量轉發

11.png

根據上文的iptables規則,我們可以歸納出被入方向代理轉發的端口,比如80等,在Pod的網絡命名空間netfilter模塊經過流程是Client -> RREROUTING -> ISTIO_INBOUND -> ISTIO_IN_REDIRECT -> INPUT -> Envoy 15006(Inbound)-> OUTPUT -> ISTIO_OUTPUT  -> POSTROUTING -> APP 。這樣就實現了入方向流量先被轉發到sidecar容器後,在轉發到業務容器的監聽端口。其中在步驟5和6之間,流量會按照設置好的istio規則進行處理。

出方向流量轉發

12.png

根據上文的iptables規則,我們可以歸納出被入方向代理轉發的端口,比如80等,在Pod的網絡命名空間netfilter模塊經過流程是APP  > OUTPUT -> ISTIO_OUTPUT -> ISTIO_REDIRECT -> Envoy 15001(Outbound)-> OUTPUT -> ISTIO_OUTPUT  -> POSTROUTING -> DST。這樣就實現了出方向流量先被轉發到sidecar容器後,在轉發到目的監聽端口。其中在步驟d和e之間,流量會按照設置好的istio規則進行處理。

入方向流量免轉發

13.png

對於入方向的某些端口或者自定義端口,我們不需要它經過sidecar容器,iptables規則會設置將符合條件的入方向流量避免轉發到15006端口,直接轉發到業務容器監聽端口 RREROUTING -> ISTIO_INBOUND  -> INPUT -> APP。

出方向流量免轉發

14.png

對於出方向的某些端口或者自定義端口,我們不需要它經過sidecar容器,iptables規則會設置將符合條件的入方向流量避免轉發到15001端口,直接離開Pod的網絡命名空間 APP -> OUTPUT -> ISTIO_OUTPUT -> POSTROUTING  -> DST。

Istio-proxy

15.png

可以看到15001和15006被envoy應用所監聽,而envoy應用就是istio-proxy容器程序。Init容器啓動的時候根據所設置的參數中指定將出入站流量重定向到Envoy的模式爲 “REDIRECT”或者“TPROXY”。使用REDIRECT方式,一旦Pod注入了Sidecar代理之後,所有入站流量都是從Envoy重定向,Envoy將流量發送到綁定了本地地址(127.0.0.1)的應用程序,所以應用看不到真正的原始IP。在服務網格環境下如何保持服務訪問時的客戶端源IP呢?可以使用TPROXY模式,目前ASM已經支持了TPROXY模式,具體詳情請見:

https://help.aliyun.com/document_detail/464794.html

在TPROXY模式下,Pod的網絡命名空間的iptables會有mangle配置。

ADS聚合服務發現

16.png

我們已經知道了服務網格會在每個注入的Pod內注入兩個容器:istio-init和istio-proxy。一旦在網格控制面進行相關配置的修改,會通過pilot下發到每個istio-proxy容器去生效。而istio是通過xDS服務接口去實現相關配置的動態下發的,其中xDS包含了LDS(Listener Discover Service)、CDS(Cluster Discover Service)、EDS(Endpoint Discovery Service)和RDS(Route Discover Service)。一般情況下,在更新配置過程中應該先更新Cluster-> 之後CLuster的Endpoint 開始更新-> 開始更新Cluster和Endpoint相對應的Listener -> Route開始更新新配置的Listener信息 -> 最後刪除不在使用 Cluster 和Endpoint 以保證更新過程中流量無損。但是這些xDS接口是相互獨立,所以在配置下發的時候,存在某些依賴關係的DS因配置生效前後關係造成了部分流量被丟棄,這在某些生產環境中是無法接受的。

爲了保證數據面配置的一致性,服務網格利用gRPC流來進行ADS聚合發現服務,通過一個gRPC流來保證各個xDS接口的調用順序,避免各個接口獨立性造成數據配置的不匹配。詳細信息可以參考:
https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol

envoy-rev.json

17.png

可以看到istio-proxy啓動了pilot-agent程序,pilot-agent作爲父進程啓動了子進程/usr/local/bin/envoy。其中/etc/istio/proxy/envoy-rev.json是envoy初始化的配置文件。

Node
包含了istio-proxy所在節點,當前Pod,istio版本、ACK集羣ID、ASM版本、必要端口等相關信息。

18.png

admin

istio-proxy相關日誌,管理端口等信息

19.png

dynamic_resources

ADS相關配置信息,比如api協議,版本,超時時間等

20.png

static_resources

包含了prometheus_stats、agent、sds-grpc、xds-grpc和zipkin五個cluster和一個在15090上監聽的listener,xds-grpc cluster對應前面dynamic_resources中ADS配置。prometheus_stats cluster和15090用於對外提供prometheus採集端口。zipkin cluster是外部的zipkin服務器調用地址。

21.png

tracing

分佈式鏈路跟蹤,這裏的collector_cluster是前面static_resources裏面定義的zipkin cluster。

22.png

訪問日誌分析

通過前文,我們已經知道兩個互相被注入的pod訪問,流量會被各自的istio-proxy所劫持並處理,那麼只要分析客戶端和服務端的istio-proxy日誌並進行加工,就可以對流量進行可觀測性解讀。我們在這裏還是以官方例子來舉例。訪問http:///productpage , productpage應用會自動調用details服務,reviews服務。我們以productpage和details之間鏈路來進行舉例分析。

23.png

productpage-v1-797d845774-dndmk  IP是10.0.1.130,details應用的svc的名稱是details,svc地址是192.168.1.125,svc端口是9080

24.png

請求發送方productpage-v1-797d845774-dndmk的istio-proxy日誌

{"upstream_host":"10.0.1.127:9080","downstream_remote_address":"10.0.1.130:49586","downstream_local_address":"192.168.1.125:9080","duration":6,"upstream_cluster":"outbound|9080||details.istio-inject.svc.cluster.local","path":"/details/0","protocol":"HTTP/1.1","upstream_local_address":"10.0.1.130:50026","method":"GET","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36","route_name":"default","request_id":"834147c2-435f-94a7-af11-8491df9ab4f8","start_time":"2023-01-31T14:23:20.603Z","upstream_transport_failure_reason":null,"upstream_service_time":"5","response_flags":"-","bytes_received":0,"authority_for":"details:9080","authority":"details:9080","requested_server_name":null,"istio_policy_status":null,"trace_id":"9712c9f3da936a8c927f227bfe536c16","response_code":200,"x_forwarded_for":null,"bytes_sent":178}

請求接受方details-v1-6758dd9d8d-dtbdc 的istio-proxy日誌

{"x_forwarded_for":null,"start_time":"2023-01-31T14:23:20.608Z","method":"GET","response_flags":"-","route_name":"default","istio_policy_status":null,"requested_server_name":"outbound_.9080_._.details.istio-inject.svc.cluster.local","bytes_received":0,"request_id":"834147c2-435f-94a7-af11-8491df9ab4f8","response_code":200,"upstream_host":"10.0.1.127:9080","trace_id":"9712c9f3da936a8c927f227bfe536c16","downstream_remote_address":"10.0.1.130:50026","protocol":"HTTP/1.1","bytes_sent":178,"upstream_transport_failure_reason":null,"downstream_local_address":"10.0.1.127:9080","upstream_local_address":"127.0.0.6:46225","authority":"details:9080","authority_for":"details:9080","upstream_service_time":"0","upstream_cluster":"inbound|9080||","duration":1,"path":"/details/0","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"}

日誌內容解讀

"upstream_host":"10.0.1.127:9080",————對於outbound,此是上游某個Endpoint的地址和端口

downstream_remote_address":"10.0.1.130:49586"," ————對於outbound,此爲本pod-ip:隨機端口1

downstream_local_address":"192.168.1.125:9080","————對於outbound,此爲目的svc-ip:svc-port

duration":6,"  ———— 整個請求時間,單位ms

upstream_cluster":"outbound|9080||details.istio-inject.svc.cluster.local",———— cluster信息

"path":"/details/0",

"protocol":"HTTP/1.1",

"upstream_local_address":"10.0.1.130:50026", ————對於outbound,此爲本pod-ip:隨機端口2

"method":"GET",

"user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",

"route_name":"default",———— 路由名稱

"request_id":"834147c2-435f-94a7-af11-8491df9ab4f8",

"start_time":"2023-01-31T14:23:20.603Z",

"upstream_transport_failure_reason":null,

"upstream_service_time":"5",———— 上游返回請求時間,單位ms

"response_flags":"-",———— 返回標誌,關於連接或返回的詳細信息

"bytes_received":0,

"authority_for":"details:9080",

"authority":"details:9080",

"requested_server_name":null,

"istio_policy_status":null,

"trace_id":"9712c9f3da936a8c927f227bfe536c16",————  此ID爲唯一值,可以在上游istio-proxy對應

"response_code":200,———— 返回狀態碼

"x_forwarded_for":null

,"bytes_sent":178

日誌解讀可以詳細見官方連接:
https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage

25.png

UPSTREAM_HOST

上游主機的host,表示從envoy發出的請求的目的端

通常來說,對於outbound cluster,此值是「上游pod-ip : pod-port」 ,而對於 inbound cluster,此值是「本pod-ip : pod-port」

UPSTREAM_LOCAL_ADDRESS

上游連接中,當前envoy的本地地址

通常來說,對於outbound cluster,此值是「本pod-ip : 隨機端口2」 ,而對於inbound cluster,此值是「127.0.0.6: 隨機端口3」,此處的127.0.0.6對應了 【1.2 Pod流量轉發-Init Container】 中的iptables會將來自127.0.0.6的流量免於istio代理,因爲這個流量是從sidecar本身發出的

DONSTREAM_LOCAL_ADDRESS

下游連接中,當前envoy的本地地址

通常來說,對於outbound cluster,此值是「目的service-ip : service-port 」,而對於 inbound cluster,此值是「當前pod-ip : pod-port,此處和下游的upstream_host應該相對應。

DOWNSTREAM_REMOTE_ADDRESS

通常來說,對於outbound cluster,此值是「當前pod-ip : 隨機端口 」,而對於 inbound cluster,此值是「下游pod-ip : 隨機端口2」,此處和下游的upstream_local_address相對應

1.3 Envoy配置簡讀(數據鏈路)

背景

26.png

還是用官方的示例, 以productpage訪問reviews服務來舉例。

27.png

28.png

29.png

30.png

通過Kubernetes集羣資源,我們可一看到reviews有三個版本分別爲v1,v2,v3, pod數量各一個。SVC reviews是ClusterIP模式,svc端口是9080, targetport是pod的9080端口,v1,v2,v3 都被加到了reviews SVC的endpointslice。在未被istio注入的情況下, 集羣內productpage pod訪問reviews.istio-inject服務, 會被netfilter以round-robin的方式平均轉發到v1,v2,v3三個pod上, 每個pod應該承受1/3的流量。

在傳統的K8s集羣中,是無法通過K8s的resource控制不同版本的流量分配的。但是實際的生產環境,我們是有這方面的需求的。比如v1版本是線上業務版本,承載了主要業務流量, v2版本是開發完畢預上線版本, 本質上是不希望影響線上流量的,可能需要引流線上流量的5%到預發版本進行一段時間的觀察,來判斷新版本是否有問題,之後再進一步擴大引流比例至100%之後,v1版本才下線,從而實現業務角度的平滑遷移。或者比如v3是測試版本,我們希望觀察流量在網絡波動超時情況下,業務的自我容災和恢復情況的行爲是否符合預期,以前這種需求需要通過在業務代碼中寫好熔斷代碼,不同熔斷環境都需要重新發版,那麼像這種流量控制在ASM Istio就可以很容易的實現。

下面就是一個ASM Istio中的vs和dr的配置。

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  creationTimestamp: '2023-01-30T06:25:21Z'
  generation: 1
  name: reviews
  namespace: istio-inject
  resourceVersion: '651722274'
  uid: 63f715c9-b253-4fbb-8351-5313371df14e
spec:
  hosts:
    - reviews.istio-inject.svc.cluster.local
  http:
    - name: route
      route:
        - destination:
            host: reviews
            subset: v1
          weight: 10
        - destination:
            host: reviews
            subset: v2
          weight: 40
        - destination:
            host: reviews
            subset: v3
          weight: 50

其中在reviews vs的定義了集羣內訪問reviews.istio-inject.svc.cluster.local是的http協議的規則。其中指明瞭v1版本權重10%,v2版本權重40%,v3版本權重 50%

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  creationTimestamp: '2023-01-30T06:28:46Z'
  generation: 2
  name: reviews
  namespace: istio-inject
  resourceVersion: '654863578'
  uid: fdbdfcea-1fcd-453e-96fb-ce41c91ded9b
spec:
  host: reviews
  subsets:
    - labels:
        version: v1
      name: v1
    - labels:
        version: v2
      name: v2
    - labels:
        version: v3
      name: v3
  trafficPolicy:
    connectionPool:
      http:
        http2MaxRequests: 1000
        maxRequestsPerConnection: 10
      tcp:
        maxConnections: 100
    outlierDetection:
      baseEjectionTime: 15m
      consecutive5xxErrors: 7
      interval: 5m

reviews dr的定義了集羣內reviews的幾個版本,並定義了相關流量策略。其中http2MaxRequests表明http最大的請求數。maxRequestsPerConnection表明每個連接最大的請求數。tcp最大連接數是100。在熔斷配置中,每隔5min中檢測一次,連續7次5xx,把後端移除endpoint 15min。

通過前文我們知道pilot通過xDS接口將服務網格的配置下發到每個被注入的pod中的istio-proxy中。那麼對於每個pod中的istio-proxy,我們是否有辦法去查看相關的加載的配置信息呢?istio-proxy通過15000端口對外暴露管理端口,我們可以通過如圖所示的命令獲取到相關的配置信息。

其中可以通過curl 127.0.0.1:15000/config_dump可以獲取到完整的配置信息,由於此配置信息超過1萬多行,我們就不在這裏做全部的展示,感興趣的同學可以自行研究下,下文會針對此config_dump信息中的cluster,Listener,endpoint,route等關鍵信息做個相關展示和簡要說明,同時也和前文的xDS做個呼應。

kubectl exec -n istio-inject productpage-v1-797d845774-dndmk -c istio-proxy  -it -- curl 127.0.0.1:15000/config_dump

31.png

Bootstrap

Envoy的初始化配置,與前文中的envoy-rev0.json是一致的。其中drainDuration —— 熱重啓期間Envoy關閉連接的時間(以秒爲單位),默認是45s。

parentShutdownDuration ——  熱重啓期間,在完全關閉父進程之前,等到的最大時間,默認60s。此數值應該大於drainDuration。

terminationDrainDuration —— 默認5s。proxy在關閉之前,允許連接關閉時間。通過前文,我們知道pod流量先過istio再被轉發到業務容器。當應用發版時候,如果保證現有連接優雅關閉,保證istio-proxy容器在業務容器完全關閉現有連接後,再退出是發版更新時候需要考慮的問題,此值是實現業務平滑更新需要考慮的。

32.png

static_resources

config_dump中靜態資源,是來自envoy-rev0.json, 裏面包含了prometheus_stats、agent、sds-grpc、xds-grpc和zipkin等配置

33.png

dynamic_resources

動態資源,是通過xDS接口下發到每個istio-proxy容器生效的ASM Istio的配置。也是上述reviews dr,vs配置後通過ASM管控側下發的。我們接下來關注動態資源配置

Listeners

Envoy採用的listener來接受到達Envoy的流量請求。Listener和IP Sock、Unix Domain Socket綁定,也可以不綁定到具體的端口,而是接收從其他listener轉發來的流量。ASM Istio就是利用了Envoy listener的這一特性來實現轉發給不同的服務請求交給不同的Listeners來處理。

還是以productpage訪問reviews來舉例, productpage訪問的是reviews的9080端口,根據上文我們知道productpage container訪問外部的9080端口會被先轉發到15001端口,所以我們先看下15001的端口listeners。

VirtualOutbound

Envoy在15001端口創建了Listeners,所有被iptables轉發的對外請求都會被轉到envoy的15001端口。可以從配置看到,envoy接受到了流量後,並不會做相關的業務動作,而是根據 "use_original_dst": true, 這個配置,依據請求的目的端口轉發到相應的listeners 進行處理。

34.png

那麼肯定有個疑問了,如果請求的目的端口並沒有配置相關的listeners設置,流量該如何進行處理呢?這個取決於outboundTrafficPolicy的配置,詳情請見:
https://istio.io/latest/docs/reference/config/istio.mesh.v1alpha1/#MeshConfig-OutboundTrafficPolicy-Mode

35.png

Outbound

outbound監聽命名是0.0.0.0_9080, 表明發向任何IP地址的9080端口都被此監聽涵蓋。"bind_to_port”: false此值表明監聽沒有綁定到tcp端口,流量是有virtualOutbound轉發而來的。那麼首先我們需要區別這個監聽是爲了入方向還是出方向呢?對於入方向,流量會經過15006端口的virtualInbound 的listeners,是不會進入0.0.0.0_9080的listeners。

36.png

從配置上可以看到filter中並沒有特殊的志敏篩選條件,說明接受任何流量,其中config_source 爲ads,表明這個是來自動態發現。

根據前文可以可看到revirews,ratings,details幾個service都是9080端口,這些應用都被同一個服務網格注入,那麼productpage訪問的目的地址的9080,Envoy如何剛知道是哪個service?是如何判斷如果目的端口未9080的地址不是網格內,該如何處理呢?通過上圖"route_config_name": "9080" 可以看到存在一個‘9080’的路由規則,在這個路由規則中規定不同的請求目的地的不同的處理結果,下一小節我們將討論。

Route

前文我們已經知道productpage應用訪問reviews的9080端口會被listeners outbound 0.0.0.0_9080路由規則到9080的路由。以下是‘9080’ 路由的全部信息。我們可以看到一共有5個virtual_hosts, 分別是allow_any、details、productpage、ratings、和reviews。其中後面4個對應4個不同outbound的cluster, allow_any對應的是PassthroughCluster,當出方向請求找到相應的Cluster規則時候,會採用默認直接通過。

可能有小夥伴很奇怪productpage爲什麼不直接調用ratings服務,爲什麼productpage envoy配置會包含ratings的信息。這是因爲ASM  Istio控制面是無法感知到數據面各個服務之間是如何調用的,因此會將所有的配置信息都下發到被注入的envoy裏面,這樣保證了網格配置的一致性,但是隨着網格服務配置的增多,每個envoy接受和本envoy不相關的配置信息就會變多,這樣對envoy資源使用會有一定影響,如果小夥伴有很好的envoy開發能力,並且對業務之間調用非常熟悉,想去除掉本pod中envoy無關的規則,可以通過sidecar規則自定義配置對egress和ingress進行調整,詳情請見:

https://istio.io/latest/docs/reference/config/networking/sidecar/


    {
     "version_info": "2023-01-30T06:25:21Z/19",
     "route_config": {
      "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
      "name": "9080",
      "virtual_hosts": [
       {
        "name": "allow_any",
        "domains": [
         "*"
        ],
        "routes": [
         {
          "match": {
           "prefix": "/"
          },
          "route": {
           "cluster": "PassthroughCluster",
           "timeout": "0s",
           "max_grpc_timeout": "0s"
          },
          "name": "allow_any"
         }
        ],
        "include_request_attempt_count": true
       },
       {
        "name": "details.istio-inject.svc.cluster.local:9080",
        "domains": [
         "details.istio-inject.svc.cluster.local",
         "details.istio-inject.svc.cluster.local:9080",
         "details",
         "details:9080",
         "details.istio-inject.svc",
         "details.istio-inject.svc:9080",
         "details.istio-inject",
         "details.istio-inject:9080",
         "192.168.1.125",
         "192.168.1.125:9080"
        ],
        "routes": [
         {
          "match": {
           "prefix": "/"
          },
          "route": {
           "cluster": "outbound|9080||details.istio-inject.svc.cluster.local",
           "timeout": "0s",
           "retry_policy": {
            "retry_on": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
            "num_retries": 2,
            "retry_host_predicate": [
             {
              "name": "envoy.retry_host_predicates.previous_hosts",
              "typed_config": {
               "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
              }
             }
            ],
            "host_selection_retry_max_attempts": "5",
            "retriable_status_codes": [
             503
            ]
           },
           "max_stream_duration": {
            "max_stream_duration": "0s",
            "grpc_timeout_header_max": "0s"
           }
          },
          "decorator": {
           "operation": "details.istio-inject.svc.cluster.local:9080/*"
          },
          "name": "default"
         }
        ],
        "include_request_attempt_count": true
       },
       {
        "name": "istio-ingressgateway.istio-system.svc.cluster.local:9080",
        "domains": [
         "istio-ingressgateway.istio-system.svc.cluster.local",
         "istio-ingressgateway.istio-system.svc.cluster.local:9080",
         "istio-ingressgateway.istio-system",
         "istio-ingressgateway.istio-system:9080",
         "istio-ingressgateway.istio-system.svc",
         "istio-ingressgateway.istio-system.svc:9080",
         "192.168.1.250",
         "192.168.1.250:9080"
        ],
        "routes": [
         {
          "match": {
           "prefix": "/"
          },
          "route": {
           "cluster": "outbound|9080||istio-ingressgateway.istio-system.svc.cluster.local",
           "timeout": "0s",
           "retry_policy": {
            "retry_on": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
            "num_retries": 2,
            "retry_host_predicate": [
             {
              "name": "envoy.retry_host_predicates.previous_hosts",
              "typed_config": {
               "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
              }
             }
            ],
            "host_selection_retry_max_attempts": "5",
            "retriable_status_codes": [
             503
            ]
           },
           "max_stream_duration": {
            "max_stream_duration": "0s",
            "grpc_timeout_header_max": "0s"
           }
          },
          "decorator": {
           "operation": "istio-ingressgateway.istio-system.svc.cluster.local:9080/*"
          "name": "default"
         }
        ],
        "include_request_attempt_count": true
       },
       },     
       {
        "name": "productpage.istio-inject.svc.cluster.local:9080",
        "domains": [
         "productpage.istio-inject.svc.cluster.local",
         "productpage.istio-inject.svc.cluster.local:9080",
         "productpage",
         "productpage:9080",
         "productpage.istio-inject.svc",
         "productpage.istio-inject.svc:9080",
         "productpage.istio-inject",
         "productpage.istio-inject:9080",
         "192.168.6.226",
         "192.168.6.226:9080"
        ],
        "routes": [
         {
          "match": {
           "prefix": "/"
          },
          "route": {
           "cluster": "outbound|9080||productpage.istio-inject.svc.cluster.local",
           "timeout": "0s",
           "retry_policy": {
            "retry_on": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
            "num_retries": 2,
            "retry_host_predicate": [
             {
              "name": "envoy.retry_host_predicates.previous_hosts",
              "typed_config": {
               "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
              }
             }
            ],
            "host_selection_retry_max_attempts": "5",
            "retriable_status_codes": [
             503
            ]
           },
           "max_stream_duration": {
            "max_stream_duration": "0s",
            "grpc_timeout_header_max": "0s"
           }
          },
          "decorator": {
           "operation": "productpage.istio-inject.svc.cluster.local:9080/*"
          },
          "name": "default"
         }
        ],
        "include_request_attempt_count": true
       },
       {
        "name": "ratings.istio-inject.svc.cluster.local:9080",
        "domains": [
         "ratings.istio-inject.svc.cluster.local",
         "ratings.istio-inject.svc.cluster.local:9080",
         "ratings",
         "ratings:9080",
         "ratings.istio-inject.svc",
         "ratings.istio-inject.svc:9080",
         "ratings.istio-inject",
         "ratings.istio-inject:9080",
         "192.168.1.172",
         "192.168.1.172:9080"
        ],
        "routes": [
         {
          "match": {
           "prefix": "/"
          },
          "route": {
           "cluster": "outbound|9080||ratings.istio-inject.svc.cluster.local",
           "timeout": "0s",
           "retry_policy": {
            "retry_on": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
            "num_retries": 2,
            "retry_host_predicate": [
             {
              "name": "envoy.retry_host_predicates.previous_hosts",
              "typed_config": {
               "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
              }
             }
            ],
            "host_selection_retry_max_attempts": "5",
            "retriable_status_codes": [
             503
            ]
           },
           "max_stream_duration": {
            "max_stream_duration": "0s",
            "grpc_timeout_header_max": "0s"
           }
          },
          "decorator": {
           "operation": "ratings.istio-inject.svc.cluster.local:9080/*"
          },
          "name": "default"
         }
        ],
        "include_request_attempt_count": true
       },
       {
        "name": "reviews.istio-inject.svc.cluster.local:9080",
        "domains": [
         "reviews.istio-inject.svc.cluster.local",
         "reviews.istio-inject.svc.cluster.local:9080",
         "reviews",
         "reviews:9080",
         "reviews.istio-inject.svc",
         "reviews.istio-inject.svc:9080",
         "reviews.istio-inject",
         "reviews.istio-inject:9080",
         "192.168.4.113",
         "192.168.4.113:9080"
        ],
        "routes": [
         {
          "match": {
           "prefix": "/"
          },
          "route": {
           "weighted_clusters": {
            "clusters": [
             {
              "name": "outbound|9080|v1|reviews.istio-inject.svc.cluster.local",
              "weight": 10
             },
             {
              "name": "outbound|9080|v2|reviews.istio-inject.svc.cluster.local",
              "weight": 40
             },
             {
              "name": "outbound|9080|v3|reviews.istio-inject.svc.cluster.local",
              "weight": 50
             }
            ],
            "total_weight": 100
           },
           "timeout": "0s",
           "retry_policy": {
            "retry_on": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
            "num_retries": 2,
            "retry_host_predicate": [
             {
              "name": "envoy.retry_host_predicates.previous_hosts",
              "typed_config": {
               "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
              }
             }
            ],
            "host_selection_retry_max_attempts": "5",
            "retriable_status_codes": [
             503
            ]
           },
           "max_stream_duration": {
            "max_stream_duration": "0s",
            "grpc_timeout_header_max": "0s"
           }
          },
          "metadata": {
           "filter_metadata": {
            "istio": {
             "config": "/apis/networking.istio.io/v1alpha3/namespaces/istio-inject/virtual-service/reviews"
            }
           }
          },
          "decorator": {
           "operation": "reviews:9080/*"
          },
          "name": "route"
         }
        ],
        "include_request_attempt_count": true
       }
      ],
      "validate_clusters": false
     },
     "last_updated": "2023-01-30T06:25:21.804Z"
    },

我們還是以productpage調用reviews來舉例, Envoy會根據 HTTP header 中domains 來匹配VirtualHost中domain,所以可以看到domains中列舉了reviews的集羣內的長短域名,和svc的地址。match “/” 會路由到三個cluster "outbound|9080|v1|reviews.istio-inject.svc.cluster.local"、"outbound|9080|v2|reviews.istio-inject.svc.cluster.local"和"outbound|9080|v3|reviews.istio-inject.svc.cluster.local",權重分別爲10,40,50,名稱是‘route’,看到這裏是不是有點熟悉?對的,這和前文 [1.3 Envoy配置簡讀-背景]中reviews的VS中的設置,故我們在vs中的相關配置信息,最終envoy會轉爲route的配置加載後生效。

37.png

38.png

通過前面我們還可以看到默認超時是0s,默認重試次數是2次,重試條件是"connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes"。

Cluster

outbound cluster

根據上一下小節,productpage訪問reviews,會被productpage中的istio-proxy匹配到‘9080’路由-> 依據vs配置的相關信息,進入三個cluster( "outbound|9080|v1|reviews.istio-inject.svc.cluster.local"、"outbound|9080|v2|reviews.istio-inject.svc.cluster.local"和"outbound|9080|v3|reviews.istio-inject.svc.cluster.local")中一個。這裏我們就以"outbound|9080|v1|reviews.istio-inject.svc.cluster.local"cluster 爲例

39.png

40.png

outbound|9080|v1|reviews.istio-inject.svc.cluster.local cluster配置中可以看到,其類型爲EDS,即表示該Cluster的endpoint來自於動態發現,動態發現中eds_config則表明是由ads下發的。同樣可以看到與前文[1.3 Envoy配置簡讀-背景]中reviews的dr中的設置熟悉的配置,比如connectionpool,outlierDetection等這些相關配置信息,最終envoy會轉爲cluster的配置加載後生效。

接下來我們稍微探討下其他幾個特殊的cluster。

BlackHoleCluster

Envoy對於找不到後端處理請求的會默認丟棄,所以會有統一的一個blackholecluster,沒有任何指明的後端svc,任何無匹配後端的流量會被轉發到這個cluster。

41.png

PassthroughCluster

和BlackHoleCluter正好相反,發向PassthroughCluster的請求會被直接發送到器請求連接中的原始目地的,type類型是"type": "ORIGINAL_DST",表明其發到原始的目的地址:端口

42.png

Inbound Cluster

inbound Cluster是爲了pod上的入方向的請求,對於reviews來說,其對應的Inbound Cluster只有一個inbound|9080。

43.png

Endpoint

從endpoint文件內容可以看到,reviews cluster “outbound|9080|v1|reviews.istio-inject.svc.cluster.local”只有1個endpoint地址,是reviews-v1-74fb8fdbd8-qwsjq的pod ip 10.0.3.29。

44.png

45.png

至此,我們大概梳理完畢服務網格內兩個服務之間訪問,istio-proxy日誌解讀和配置對應關係。

Tips

前文的config_dump文件太長,解讀起來其實比較費力,服務網格提供了asmctl工具可以很方便的去解讀listeners,route,cluster,endpoint等,詳細信息請見:
https://help.aliyun.com/document_detail/313187.html

[root@shycmain ~]# asmctl --asmconfig asmconfig pc  listeners  productpage-v1-797d845774-dndmk.istio-inject --port 9080
ADDRESS PORT MATCH                        DESTINATION
0.0.0.0 9080 Trans: raw_buffer; App: HTTP Route: 9080
0.0.0.0 9080 ALL                          PassthroughCluster

[root@shycmain ~]# asmctl --asmconfig asmconfig pc  routes  productpage-v1-797d845774-dndmk.istio-inject  --name 9080
NOTE: This output only contains routes loaded via RDS.
NAME     DOMAINS                               MATCH     VIRTUAL SERVICE
9080     details                               /*
9080     istio-ingressgateway.istio-system     /*
9080     productpage                           /*
9080     ratings                               /*
9080     reviews                               /*        reviews.istio-inject

[root@shycmain ~]# asmctl --asmconfig asmconfig pc  cluster  productpage-v1-797d845774-dndmk.istio-inject --fqdn reviews.istio-inject.svc.cluster.local
SERVICE FQDN                               PORT     SUBSET     DIRECTION     TYPE     DESTINATION RULE
reviews.istio-inject.svc.cluster.local     9080     -          outbound      EDS      reviews.istio-inject
reviews.istio-inject.svc.cluster.local     9080     v1         outbound      EDS      reviews.istio-inject
reviews.istio-inject.svc.cluster.local     9080     v2         outbound      EDS      reviews.istio-inject
reviews.istio-inject.svc.cluster.local     9080     v3         outbound      EDS      reviews.istio-inject

[root@shycmain ~]# asmctl --asmconfig asmconfig pc  endpoint  productpage-v1-797d845774-dndmk.istio-inject  --cluster "outbound|9080|v1|reviews.istio-inject.svc.cluster.local"
ENDPOINT           STATUS      OUTLIER CHECK     CLUSTER
10.0.3.29:9080     HEALTHY     OK                outbound|9080|v1|reviews.istio-inject.svc.cluster.local

企業擁抱容器化總結

本篇文章主要聚焦在ASM Istio服務網格模式下,被注入pod的數據面流量轉發鏈路情況。istio靈活注入實現了在Pod維度對流量的定製化配置和觀測性,帶來了業務鏈路角度實現的更多種的可能。在服務網格中配置gateway,virtualservice,destinationrule等規則在通過xDS下發到envoy後,會通過listeners, route、cluster、endpoint等一個環節一個環節最終體現在流量轉發規則上。那麼在運用ASM遇到不符合預期情況時,這些環節都是需要考慮的方向。ASM除了流量管理,還有安全,鑑權,可觀測方面的便捷運用,這些方面的配置,最終也會體現在相關的網格服務資源配置上,感興趣的小夥伴可以參考**ASM官方文檔 [ 2] **。

參考鏈接

https://istio.io/latest/docs/reference/config/networking/sidecar/

https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage

[1] 多種方式靈活開啓自動注入

https://help.aliyun.com/document_detail/186136.html

[2] 官方文檔

https://help.aliyun.com/product/147365.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章