Istio 中的 Sidecar 注入及透明流量劫持过程详解
圖片來源:上海五角場 by Jimmy Song
本文基于 Istio 1.5.1 版本,將為大家介紹以下內容:
什么是 sidecar 模式和它的優勢在哪里。
Istio 中是如何做 sidecar 注入的?
Sidecar proxy 是如何做透明流量劫持的?
流量是如何路由到 upstream 的?
在此之前我曾寫過基于 Istio 1.1 版本的《理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持》,Istio 1.5 與 Istio 1.1 中的 sidecar 注入和流量劫持環節最大的變化是:
iptables 改用命令行工具,不再使用 shell 腳本。
sidecar inbound 和 outbound 分別指定了端口,而之前是使用同一個端口(15001)。
注:本文中部分內容收錄于 ServiceMesher 社區出品的開源電子書?Istio Handbook(https://github.com/servicemesher/istio-handbook),歡迎大家參與到本書的 review 和寫作工作中來。
Sidecar 模式
將應用程序的功能劃分為單獨的進程運行在同一個最小調度單元中(例如 Kubernetes 中的 Pod)可以被視為 sidecar 模式。如下圖所示,sidecar 模式允許您在應用程序旁邊添加更多功能,而無需額外第三方組件配置或修改應用程序代碼。
就像連接了 Sidecar 的三輪摩托車一樣,在軟件架構中, Sidecar 連接到父應用并且為其添加擴展或者增強功能。Sidecar 應用與主應用程序松散耦合。它可以屏蔽不同編程語言的差異,統一實現微服務的可觀察性、監控、日志記錄、配置、斷路器等功能。
使用 Sidecar 模式的優勢
使用 sidecar 模式部署服務網格時,無需在節點上運行代理,但是集群中將運行多個相同的 sidecar 副本。在 sidecar 部署方式中,每個應用的容器旁都會部署一個伴生容器(如 Envoy 或 MOSN),這個容器稱之為 sidecar 容器。Sidecar 接管進出應用容器的所有流量。在 Kubernetes 的 Pod 中,在原有的應用容器旁邊注入一個 Sidecar 容器,兩個容器共享存儲、網絡等資源,可以廣義的將這個包含了 sidecar 容器的 Pod 理解為一臺主機,兩個容器共享主機資源。
因其獨特的部署結構,使得 sidecar 模式具有以下優勢:
將與應用業務邏輯無關的功能抽象到共同基礎設施,降低了微服務代碼的復雜度。
因為不再需要編寫相同的第三方組件配置文件和代碼,所以能夠降低微服務架構中的代碼重復度。
Sidecar 可獨立升級,降低應用程序代碼和底層平臺的耦合度。
Istio 中的 sidecar 注入
Istio 中提供了以下兩種 sidecar 注入方式:
使用 istioctl 手動注入。
基于 Kubernetes 的 突變 webhook 入駐控制器(mutating webhook addmission controller 的自動 sidecar 注入方式。
不論是手動注入還是自動注入,sidecar 的注入過程都需要遵循如下步驟:
Kubernetes 需要了解待注入的 sidecar 所連接的 Istio 集群及其配置;
Kubernetes 需要了解待注入的 sidecar 容器本身的配置,如鏡像地址、啟動參數等;
Kubernetes 根據 sidecar 注入模板和以上配置填充 sidecar 的配置參數,將以上配置注入到應用容器的一側;
使用下面的命令可以手動注入 sidecar。
istioctl?kube-inject?-f?${YAML_FILE}?|?kuebectl?apply?-f?-該命令會使用 Istio 內置的 sidecar 配置來注入,下面使用 Istio詳細配置請參考 Istio 官網。
注入完成后您將看到 Istio 為原有 pod template 注入了 initContainer 及 sidecar proxy相關的配置。
Init 容器
Init 容器是一種專用容器,它在應用程序容器啟動之前運行,用來包含一些應用鏡像中不存在的實用工具或安裝腳本。
一個 Pod 中可以指定多個 Init 容器,如果指定了多個,那么 Init 容器將會按順序依次運行。只有當前面的 Init 容器必須運行成功后,才可以運行下一個 Init 容器。當所有的 Init 容器運行完成后,Kubernetes 才初始化 Pod 和運行應用容器。
Init 容器使用 Linux Namespace,所以相對應用程序容器來說具有不同的文件系統視圖。因此,它們能夠具有訪問 Secret 的權限,而應用程序容器則不能。
在 Pod 啟動過程中,Init 容器會按順序在網絡和數據卷初始化之后啟動。每個容器必須在下一個容器啟動之前成功退出。如果由于運行時或失敗退出,將導致容器啟動失敗,它會根據 Pod 的 restartPolicy 指定的策略進行重試。然而,如果 Pod 的 restartPolicy 設置為 Always,Init 容器失敗時會使用 RestartPolicy 策略。
在所有的 Init 容器沒有成功之前,Pod 將不會變成 Ready 狀態。Init 容器的端口將不會在 Service中進行聚集。正在初始化中的 Pod 處于 Pending 狀態,但應該會將 Initializing 狀態設置為 true。Init 容器運行完成以后就會自動終止。
關于 Init 容器的詳細信息請參考 Init 容器 - Kubernetes 中文指南/云原生應用架構實踐手冊。
Sidecar 注入示例分析
以 Istio 官方提供的 bookinfo 中 productpage 的 YAML 為例,關于 bookinfo 應用的詳細 YAML 配置請參考 bookinfo.yaml。
下文將從以下幾個方面講解:
Sidecar 容器的注入
iptables 規則的創建
路由的詳細過程
再查看下 productpage 容器的 Dockerfile。
FROM?python:3.7.4-slimCOPY?requirements.txt?./ RUN?pip?install?--no-cache-dir?-r?requirements.txtCOPY?test-requirements.txt?./ RUN?pip?install?--no-cache-dir?-r?test-requirements.txtCOPY?productpage.py?/opt/microservices/ COPY?tests/unit/*?/opt/microservices/ COPY?templates?/opt/microservices/templates COPY?static?/opt/microservices/static COPY?requirements.txt?/opt/microservices/ARG?flood_factor ENV?FLOOD_FACTOR?${flood_factor:-0}EXPOSE?9080 WORKDIR?/opt/microservices RUN?python?-m?unittest?discoverUSER?1CMD?["python",?"productpage.py",?"9080"]我們看到 Dockerfile 中沒有配置 ENTRYPOINT,所以 CMD 的配置 python productpage.py 9080 將作為默認的 ENTRYPOINT,記住這一點,再看下注入 sidecar 之后的配置。
$?istioctl?kube-inject?-f?samples/bookinfo/platform/kube/bookinfo.yaml我們只截取其中與 productpage 相關的 Deployment 配置中的部分 YAML 配置。
??????containers:-?image:?docker.io/istio/examples-bookinfo-productpage-v1:1.15.0?#?應用鏡像name:?productpageports:-?containerPort:?9080-?args:-?proxy-?sidecar-?--domain-?$(POD_NAMESPACE).svc.cluster.local-?--configPath-?/etc/istio/proxy-?--binaryPath-?/usr/local/bin/envoy-?--serviceCluster-?productpage.$(POD_NAMESPACE)-?--drainDuration-?45s-?--parentShutdownDuration-?1m0s-?--discoveryAddress-?istiod.istio-system.svc:15012-?--zipkinAddress-?zipkin.istio-system:9411-?--proxyLogLevel=warning-?--proxyComponentLogLevel=misc:error-?--connectTimeout-?10s-?--proxyAdminPort-?"15000"-?--concurrency-?"2"-?--controlPlaneAuthPolicy-?NONE-?--dnsRefreshRate-?300s-?--statusPort-?"15020"-?--trust-domain=cluster.local-?--controlPlaneBootstrap=falseimage:?docker.io/istio/proxyv2:1.5.1?#?sidecar?proxyname:?istio-proxyports:-?containerPort:?15090name:?http-envoy-promprotocol:?TCPinitContainers:-?command:-?istio-iptables-?-p-?"15001"-?-z-?"15006"-?-u-?"1337"-?-m-?REDIRECT-?-i-?'*'-?-x-?""-?-b-?'*'-?-d-?15090,15020image:?docker.io/istio/proxyv2:1.5.1?#?init?容器name:?istio-initIstio 給應用 Pod 注入的配置主要包括:
Init 容器 istio-init:用于 pod 中設置 iptables 端口轉發
Sidecar 容器 istio-proxy:運行 sidecar 代理,如 Envoy 或 MOSN
接下來將分別解析下這兩個容器。
Init 容器解析
Istio 在 pod 中注入的 Init 容器名為 istio-init,我們在上面 Istio 注入完成后的 YAML 文件中看到了該容器的啟動命令是:
istio-iptables?-p?15001?-z?15006?-u?1337?-m?REDIRECT?-i?'*'?-x?""?-b?'*'?-d?15090,15020我們再檢查下該容器的 Dockerfile 看看 ENTRYPOINT 是怎么確定啟動時執行的命令。
#?前面的內容省略 #?The?pilot-agent?will?bootstrap?Envoy. ENTRYPOINT?["/usr/local/bin/pilot-agent"]我們看到 istio-init 容器的入口是 /usr/local/bin/istio-iptables 命令行,該命令行工具的代碼的位置在 Istio 源碼倉庫的 tools/istio-iptables 目錄。
注意:在 Istio 1.1 版本時還是使用 isito-iptables.sh 命令行來操作 IPtables。
Init 容器啟動入口
Init 容器的啟動入口是 istio-iptables 命令行,該命令行工具的用法如下:
$?istio-iptables?[flags]-p:?指定重定向所有?TCP?流量的?sidecar?端口(默認為?$ENVOY_PORT?=?15001)-m:?指定入站連接重定向到?sidecar?的模式,“REDIRECT”?或?“TPROXY”(默認為?$ISTIO_INBOUND_INTERCEPTION_MODE)-b:?逗號分隔的入站端口列表,其流量將重定向到 Envoy(可選)。使用通配符?“*”?表示重定向所有端口。為空時表示禁用所有入站重定向(默認為?$ISTIO_INBOUND_PORTS)-d:?指定要從重定向到 sidecar 中排除的入站端口列表(可選),以逗號格式分隔。使用通配符“*”?表示重定向所有入站流量(默認為?$ISTIO_LOCAL_EXCLUDE_PORTS)-o:逗號分隔的出站端口列表,不包括重定向到 Envoy 的端口。-i:?指定重定向到 sidecar 的 IP 地址范圍(可選),以逗號分隔的 CIDR 格式列表。使用通配符?“*”?表示重定向所有出站流量。空列表將禁用所有出站重定向(默認為?$ISTIO_SERVICE_CIDR)-x:?指定將從重定向中排除的 IP 地址范圍,以逗號分隔的 CIDR 格式列表。使用通配符?“*”?表示重定向所有出站流量(默認為?$ISTIO_SERVICE_EXCLUDE_CIDR)。-k:逗號分隔的虛擬接口列表,其入站流量(來自虛擬機的)將被視為出站流量。-g:指定不應用重定向的用戶的 GID。(默認值與?-u param 相同)-u:指定不應用重定向的用戶的 UID。通常情況下,這是代理容器的 UID(默認值是 1337,即 istio-proxy 的 UID)。-z:?所有進入?pod/VM?的?TCP?流量應被重定向到的端口(默認?$INBOUND_CAPTURE_PORT?= 15006)。以上傳入的參數都會重新組裝成 iptables 規則,關于該命令的詳細用法請訪問 tools/istio-iptables/pkg/cmd/root.go。
該容器存在的意義就是讓 sidecar 代理可以攔截所有的進出 pod 的流量,15090 端口(Mixer 使用)和 15092 端口(Ingress Gateway)除外的所有入站(inbound)流量重定向到 15006 端口(sidecar),再攔截應用容器的出站(outbound)流量經過 sidecar 處理(通過 15001 端口監聽)后再出站。關于 Istio 中端口用途請參考 Istio 官方文檔。
命令解析
這條啟動命令的作用是:
將應用容器的所有流量都轉發到 sidecar 的 15006 端口。
使用 istio-proxy 用戶身份運行, UID 為 1337,即 sidecar 所處的用戶空間,這也是 istio-proxy 容器默認使用的用戶,見 YAML 配置中的 runAsUser 字段。
使用默認的 REDIRECT 模式來重定向流量。
將所有出站流量都重定向到 sidecar 代理(通過 15001 端口)。
因為 Init 容器初始化完畢后就會自動終止,因為我們無法登陸到容器中查看 iptables 信息,但是 Init 容器初始化結果會保留到應用容器和 sidecar 容器中。
iptables 注入解析
為了查看 iptables 配置,我們需要登陸到 sidecar 容器中使用 root 用戶來查看,因為 kubectl 無法使用特權模式來遠程操作 docker 容器,所以我們需要登陸到 productpage pod 所在的主機上使用 docker 命令登陸容器中查看。
如果您使用 minikube 部署的 Kubernetes,可以直接登錄到 minikube 的虛擬機中并切換為 root 用戶。查看 iptables 配置,列出 NAT(網絡地址轉換)表的所有規則,因為在 Init 容器啟動的時候選擇給 istio-iptables 傳遞的參數中指定將入站流量重定向到 sidecar 的模式為 REDIRECT,因此在 iptables 中將只有 NAT 表的規格配置,如果選擇 TPROXY 還會有 mangle 表配置。iptables 命令的詳細用法請參考 iptables 命令。
我們僅查看與 productpage 有關的 iptables 規則如下。
#?進入?minikube?并切換為?root?用戶,minikube?默認用戶為?docker $?minikube?ssh $?sudo?-i#?查看?productpage?pod?的?istio-proxy?容器中的進程 $?docker?top?`docker?ps|grep?"istio-proxy_productpage"|cut?-d?"?"?-f1` UID?????????????????PID?????????????????PPID????????????????C???????????????????STIME???????????????TTY?????????????????TIME????????????????CMD 1337????????????????10576???????????????10517???????????????0???????????????????08:09???????????????????????????????????00:00:07????????????/usr/local/bin/pilot-agent?proxy?sidecar?--domain?default.svc.cluster.local?--configPath?/etc/istio/proxy?--binaryPath?/usr/local/bin/envoy?--serviceCluster?productpage.default?--drainDuration?45s?--parentShutdownDuration?1m0s?--discoveryAddress?istiod.istio-system.svc:15012?--zipkinAddress?zipkin.istio-system:9411?--proxyLogLevel=warning?--proxyComponentLogLevel=misc:error?--connectTimeout?10s?--proxyAdminPort?15000?--concurrency?2?--controlPlaneAuthPolicy?NONE?--dnsRefreshRate?300s?--statusPort?15020?--trust-domain=cluster.local?--controlPlaneBootstrap=false 1337????????????????10660???????????????10576???????????????0???????????????????08:09???????????????????????????????????00:00:33????????????/usr/local/bin/envoy?-c?/etc/istio/proxy/envoy-rev0.json?--restart-epoch?0?--drain-time-s?45?--parent-shutdown-time-s?60?--service-cluster?productpage.default?--service-node?sidecar~172.17.0.16~productpage-v1-7f44c4d57c-ksf9b.default~default.svc.cluster.local?--max-obj-name-len?189?--local-address-ip-version?v4?--log-format?[Envoy?(Epoch?0)]?[%Y-%m-%d?%T.%e][%t][%l][%n]?%v?-l?warning?--component-log-level?misc:error?--concurrency?2#?進入?nsenter?進入?sidecar?容器的命名空間(以上任何一個都可以) $?nsenter?-n?--target?10660在該進程的命名空間下查看其 iptables 規則鏈。
#?查看 NAT 表中規則配置的詳細信息。 $?iptables?-t?nat?-L?-v # PREROUTING 鏈:用于目標地址轉換(DNAT),將所有入站 TCP 流量跳轉到 ISTIO_INBOUND 鏈上。 Chain?PREROUTING?(policy?ACCEPT?2701?packets,?162K?bytes)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination2701??162K?ISTIO_INBOUND??tcp??--??any????any?????anywhere?????????????anywhere# INPUT 鏈:處理輸入數據包,非 TCP 流量將繼續 OUTPUT 鏈。 Chain?INPUT?(policy?ACCEPT?2701?packets,?162K?bytes)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination# OUTPUT 鏈:將所有出站數據包跳轉到 ISTIO_OUTPUT 鏈上。 Chain?OUTPUT?(policy?ACCEPT?79?packets,?6761?bytes)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination15???900?ISTIO_OUTPUT??tcp??--??any????any?????anywhere?????????????anywhere# POSTROUTING 鏈:所有數據包流出網卡時都要先進入POSTROUTING 鏈,內核根據數據包目的地判斷是否需要轉發出去,我們看到此處未做任何處理。 Chain?POSTROUTING?(policy?ACCEPT?79?packets,?6761?bytes)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination# ISTIO_INBOUND 鏈:將所有入站流量重定向到 ISTIO_IN_REDIRECT 鏈上,目的地為 15090(mixer 使用)和 15020(Ingress gateway 使用,用于 Pilot 健康檢查)端口的流量除外,發送到以上兩個端口的流量將返回 iptables 規則鏈的調用點,即 PREROUTING 鏈的后繼 POSTROUTING。 Chain?ISTIO_INBOUND?(1?references)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination0?????0?RETURN?????tcp??--??any????any?????anywhere?????????????anywhere?????????????tcp?dpt:ssh2???120?RETURN?????tcp??--??any????any?????anywhere?????????????anywhere?????????????tcp?dpt:150902699??162K?RETURN?????tcp??--??any????any?????anywhere?????????????anywhere?????????????tcp?dpt:150200?????0?ISTIO_IN_REDIRECT??tcp??--??any????any?????anywhere?????????????anywhere# ISTIO_IN_REDIRECT 鏈:將所有的入站流量跳轉到本地的 15006 端口,至此成功的攔截了流量到 sidecar 中。 Chain?ISTIO_IN_REDIRECT?(3?references)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination0?????0?REDIRECT???tcp??--??any????any?????anywhere?????????????anywhere?????????????redir?ports?15006# ISTIO_OUTPUT 鏈:選擇需要重定向到 Envoy(即本地)?的出站流量,所有非 localhost 的流量全部轉發到 ISTIO_REDIRECT。為了避免流量在該 Pod 中無限循環,所有到 istio-proxy 用戶空間的流量都返回到它的調用點中的下一條規則,本例中即 OUTPUT 鏈,因為跳出 ISTIO_OUTPUT 規則之后就進入下一條鏈 POSTROUTING。如果目的地非 localhost 就跳轉到 ISTIO_REDIRECT;如果流量是來自 istio-proxy 用戶空間的,那么就跳出該鏈,返回它的調用鏈繼續執行下一條規則(OUTPUT 的下一條規則,無需對流量進行處理);所有的非 istio-proxy 用戶空間的目的地是 localhost 的流量就跳轉到 ISTIO_REDIRECT。 Chain?ISTIO_OUTPUT?(1?references)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination0?????0?RETURN?????all??--??any????lo??????127.0.0.6????????????anywhere0?????0?ISTIO_IN_REDIRECT??all??--??any????lo??????anywhere????????????!localhost????????????owner?UID?match?13370?????0?RETURN?????all??--??any????lo??????anywhere?????????????anywhere?????????????!?owner?UID?match?133715???900?RETURN?????all??--??any????any?????anywhere?????????????anywhere?????????????owner?UID?match?13370?????0?ISTIO_IN_REDIRECT??all??--??any????lo??????anywhere????????????!localhost????????????owner?GID?match?13370?????0?RETURN?????all??--??any????lo??????anywhere?????????????anywhere?????????????!?owner?GID?match?13370?????0?RETURN?????all??--??any????any?????anywhere?????????????anywhere?????????????owner?GID?match?13370?????0?RETURN?????all??--??any????any?????anywhere?????????????localhost0?????0?ISTIO_REDIRECT??all??--??any????any?????anywhere?????????????anywhere# ISTIO_REDIRECT 鏈:將所有流量重定向到 Sidecar(即本地)?的 15001 端口。 Chain?ISTIO_REDIRECT?(1?references)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination0?????0?REDIRECT???tcp??--??any????any?????anywhere?????????????anywhere?????????????redir?ports?15001下圖展示的是 productpage 服務請求訪問 http://reviews.default.svc.cluster.local:9080/,當流量進入 reviews 服務內部時,reviews 服務內部的 sidecar proxy 是如何做流量攔截和路由轉發的。
第一步開始時,productpage Pod 中的 sidecar 已經通過 EDS 選擇出了要請求的 reviews 服務的一個 Pod,知曉了其 IP 地址,發送 TCP 連接請求。
reviews 服務有三個版本,每個版本有一個實例,三個版本中的 sidecar 工作步驟類似,下文只以其中一個 Pod 中的 sidecar 流量轉發步驟來說明。
理解 iptables
iptables 是 Linux 內核中的防火墻軟件 netfilter 的管理工具,位于用戶空間,同時也是 netfilter 的一部分。Netfilter 位于內核空間,不僅有網絡地址轉換的功能,也具備數據包內容修改、以及數據包過濾等防火墻功能。
在了解 Init 容器初始化的 iptables 之前,我們先來了解下 iptables 和規則配置。
下圖展示了 iptables 調用鏈。
iptables 中的表
Init 容器中使用的的 iptables 版本是 v1.6.0,共包含 5 張表:
raw 用于配置數據包,raw 中的數據包不會被系統跟蹤。
filter 是用于存放所有與防火墻相關操作的默認表。
nat 用于 網絡地址轉換(例如:端口轉發)。
mangle 用于對特定數據包的修改(參考損壞數據包)。
security 用于強制訪問控制 網絡規則。
注:在本示例中只用到了 nat 表。
不同的表中的具有的鏈類型如下表所示:
理解 iptables 規則關于 iptables 的詳細介紹請參考常見 iptables 使用規則場景整理。
查看 istio-proxy 容器中的默認的 iptables 規則,默認查看的是 filter 表中的規則。
$?iptables?-L?-v Chain?INPUT?(policy?ACCEPT?350K?packets,?63M?bytes)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destinationChain?FORWARD?(policy?ACCEPT?0?packets,?0?bytes)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destinationChain?OUTPUT?(policy?ACCEPT?18M?packets,?1916M?bytes)pkts?bytes?target?????prot?opt?in?????out?????source???????????????destination我們看到三個默認的鏈,分別是 INPUT、FORWARD 和 OUTPUT,每個鏈中的第一行輸出表示鏈名稱(在本例中為INPUT/FORWARD/OUTPUT),后跟默認策略(ACCEPT)。
下圖是 iptables 的建議結構圖,流量在經過 INPUT 鏈之后就進入了上層協議棧,比如
每條鏈中都可以添加多條規則,規則是按照順序從前到后執行的。我們來看下規則的表頭定義。
pkts:處理過的匹配的報文數量
bytes:累計處理的報文大小(字節數)
target:如果報文與規則匹配,指定目標就會被執行。
prot:協議,例如 tdp、udp、icmp 和 all。
opt:很少使用,這一列用于顯示 IP 選項。
in:入站網卡。
out:出站網卡。
source:流量的源 IP 地址或子網,后者是 anywhere。
destination:流量的目的地 IP 地址或子網,或者是 anywhere。
還有一列沒有表頭,顯示在最后,表示規則的選項,作為規則的擴展匹配條件,用來補充前面的幾列中的配置。prot、opt、in、out、source 和 destination 和顯示在 destination 后面的沒有表頭的一列擴展條件共同組成匹配規則。當流量匹配這些規則后就會執行 target。
關于 iptables 規則請參考常見 iptables 使用規則場景整理。
target 支持的類型
target 類型包括 ACCEPT、REJECT、DROP、LOG 、SNAT、MASQUERADE、DNAT、REDIRECT、RETURN 或者跳轉到其他規則等。只要執行到某一條鏈中只有按照順序有一條規則匹配后就可以確定報文的去向了,除了 RETURN 類型,類似編程語言中的 return 語句,返回到它的調用點,繼續執行下一條規則。target 支持的配置詳解請參考 iptables 詳解(1):iptables 概念。
從輸出結果中可以看到 Init 容器沒有在 iptables 的默認鏈路中創建任何規則,而是創建了新的鏈路。
流量路由過程詳解
流量路由分為 Inbound 和 Outbound 兩個過程,下面將根據上文中的示例及 sidecar 的配置為讀者詳細分析此過程。
理解 Inbound Handler
Inbound handler 的作用是將 iptables 攔截到的 downstream 的流量轉交給 localhost,與 Pod) 內的應用程序容器建立連接。假設其中一個 Pod) 的名字是 reviews-v1-54b8794ddf-jxksn,運行 istioctl proxy-config listener reviews-v1-54b8794ddf-jxksn 查看該 Pod) 中的具有哪些 Listener。
ADDRESS????????????PORT??????TYPE 172.17.0.15????????9080??????HTTP?<---?接收所有?Inbound?HTTP?流量,該地址即為業務進程的真實監聽地址 172.17.0.15????????15020?????TCP?<---?Ingress?Gateway,Pilot?健康檢查 10.109.20.166??????15012?????TCP?<---?Istiod?http?dns 10.103.34.135??????14250?????TCP?<--+ 10.103.34.135??????14267?????TCP????| 10.103.34.135??????14268?????TCP????| 10.104.122.175?????15020?????TCP????| 10.104.122.175?????15029?????TCP????| 10.104.122.175?????15030?????TCP????| 10.104.122.175?????15031?????TCP????| 10.104.122.175?????15032?????TCP????| 10.104.122.175?????15443?????TCP????| 10.104.122.175?????31400?????TCP????|?接收與?0.0.0.0:15006?監聽器配對的?Outbound?流量 10.104.122.175?????443???????TCP????| 10.104.62.18???????15443?????TCP????| 10.104.62.18???????443???????TCP????| 10.106.201.253?????16686?????TCP????| 10.109.20.166??????443???????TCP????| 10.96.0.1??????????443???????TCP????| 10.96.0.10?????????53????????TCP????| 10.96.0.10?????????9153??????TCP????| 10.98.184.149??????15011?????TCP????| 10.98.184.149??????15012?????TCP????| 10.98.184.149??????443???????TCP????| 0.0.0.0????????????14250?????TCP????| 0.0.0.0????????????15010?????TCP????| 0.0.0.0????????????15014?????TCP????| 0.0.0.0????????????15090?????HTTP???| 0.0.0.0????????????20001?????TCP????| 0.0.0.0????????????3000??????TCP????| 0.0.0.0????????????80????????TCP????| 0.0.0.0????????????8080??????TCP????| 0.0.0.0????????????9080??????TCP????| 0.0.0.0????????????9090??????TCP????| 0.0.0.0????????????9411??????TCP?<--+ 0.0.0.0????????????15001?????TCP?<---?接收所有經?iptables?攔截的?Outbound?流量并轉交給虛擬監聽器處理 0.0.0.0????????????15006?????TCP?<---?接收所有經?iptables?攔截的?Inbound?流量并轉交給虛擬監聽器處理當來自 productpage 的流量抵達 reviews Pod 的時候,downstream 已經明確知道 Pod) 的 IP 地址為 172.17.0.16 所以才會訪問該 Pod),所以該請求是 172.17.0.15:9080。
virtualInbound Listener
從該 Pod) 的 Listener 列表中可以看到,0.0.0.0:15006/TCP 的 Listener(其實際名字是 virtualInbound)監聽所有的 Inbound 流量,下面是該 Listener 的詳細配置。
{"name":?"virtualInbound","address":?{"socketAddress":?{"address":?"0.0.0.0","portValue":?15006}}, "filterChains":?[{"filters":?[/*省略部分內容*/{"filterChainMatch":?{"destinationPort":?9080,"prefixRanges":?[{"addressPrefix":?"172.17.0.15","prefixLen":?32}],"applicationProtocols":?["istio-peer-exchange","istio","istio-http/1.0","istio-http/1.1","istio-h2"]},"filters":?[{"name":?"envoy.filters.network.metadata_exchange","config":?{"protocol":?"istio-peer-exchange"}},{"name":?"envoy.http_connection_manager","typedConfig":?{"@type":?"type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager","statPrefix":?"inbound_172.17.0.15_9080","routeConfig":?{"name":?"inbound|9080|http|reviews.default.svc.cluster.local","virtualHosts":?[{"name":?"inbound|http|9080","domains":?["*"],"routes":?[{"name":?"default","match":?{"prefix":?"/"},"route":?{"cluster":?"inbound|9080|http|reviews.default.svc.cluster.local","timeout":?"0s","maxGrpcTimeout":?"0s"},"decorator":?{"operation":?"reviews.default.svc.cluster.local:9080/*"}}]}],"validateClusters":?false}/*省略部分內容*/ }Inbound handler 的流量被 virtualInbound Listener 轉移到 172.17.0.15_9080 Listener,我們在查看下該 Listener 配置。
運行 istioctl pc listener reviews-v1-54b8794ddf-jxksn --address 172.17.0.15 --port 9080 -o json 查看。
[{"name":?"172.17.0.15_9080","address":?{"socketAddress":?{"address":?"172.17.0.15","portValue":?9080}},"filterChains":?[{"filterChainMatch":?{"applicationProtocols":?["istio-peer-exchange","istio","istio-http/1.0","istio-http/1.1","istio-h2"]},"filters":?[{"name":?"envoy.http_connection_manager","config":?{...?"routeConfig":?{"name":?"inbound|9080|http|reviews.default.svc.cluster.local","virtualHosts":?[{"name":?"inbound|http|9080","domains":?["*"],"routes":?[{"name":?"default","match":?{"prefix":?"/"},"route":?{"cluster":?"inbound|9080|http|reviews.default.svc.cluster.local","timeout":?"0s","maxGrpcTimeout":?"0s"},"decorator":?{"operation":?"reviews.default.svc.cluster.local:9080/*"}}]}],}...},{"filterChainMatch":?{"transportProtocol":?"tls"},"tlsContext":?{...},"filters":?[...]}], ... }]我們看其中的 filterChains.filters 中的 envoy.http_connection_manager 配置部分,該配置表示流量將轉交給Clusterinbound|9080|http|reviews.default.svc.cluster.local 處理。
Cluster inbound|9080|http|reviews.default.svc.cluster.local
運行 istioctl proxy-config cluster reviews-v1-54b8794ddf-jxksn --fqdn reviews.default.svc.cluster.local --direction inbound -o json 查看該Cluster的配置如下。
[{"name":?"inbound|9080|http|reviews.default.svc.cluster.local","type":?"STATIC","connectTimeout":?"1s","loadAssignment":?{"clusterName":?"inbound|9080|http|reviews.default.svc.cluster.local","endpoints":?[{"lbEndpoints":?[{"endpoint":?{"address":?{"socketAddress":?{"address":?"127.0.0.1","portValue":?9080}}}}]}]},"circuitBreakers":?{"thresholds":?[{"maxConnections":?4294967295,"maxPendingRequests":?4294967295,"maxRequests":?4294967295,"maxRetries":?4294967295}]}} ]可以看到該Cluster的 Endpoint 直接對應的就是 localhost,再經過 iptables 轉發流量就被應用程序容器消費了。
理解 Outbound Handler
因為 reviews 會向 ratings 服務發送 HTTP 請求,請求的地址是:http://ratings.default.svc.cluster.local:9080/,Outbound handler 的作用是將 iptables 攔截到的本地應用程序發出的流量,經由 sidecar 判斷如何路由到 upstream。
應用程序容器發出的請求為 Outbound 流量,被 iptables 劫持后轉移給 Outbound handler 處理,然后經過 virtualOutbound Listener、0.0.0.0_9080 Listener,然后通過 Route 9080 找到 upstream 的 cluster,進而通過 EDS 找到 Endpoint 執行路由動作。
Route ratings.default.svc.cluster.local:9080
reviews 會請求 ratings 服務,運行 istioctl proxy-config routes reviews-v1-54b8794ddf-jxksn --name 9080 -o json 查看 route 配置,因為 sidecar 會根據 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.98.49.62","10.98.49.62: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,resource-exhausted,retriable-status-codes","numRetries":?2,"retryHostPredicate":?[{"name":?"envoy.retry_host_predicates.previous_hosts"}],"hostSelectionRetryMaxAttempts":?"5","retriableStatusCodes":?[503]},"maxGrpcTimeout":?"0s"},"decorator":?{"operation":?"ratings.default.svc.cluster.local:9080/*"}}]}, ..]從該 Virtual Host 配置中可以看到將流量路由到Clusteroutbound|9080||ratings.default.svc.cluster.local。
Endpoint outbound|9080||ratings.default.svc.cluster.local
運行 istioctl proxy-config endpoint reviews-v1-54b8794ddf-jxksn --port 9080 -o json 查看 Endpoint 配置,我們只選取其中的 outbound|9080||ratings.default.svc.cluster.localCluster的結果如下。
{"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 可以是一個或多個,sidecar 將根據一定規則選擇適當的 Endpoint 來路由。至此 Review 服務找到了它 upstream 服務 Rating 的 Endpoint。
小結
本文使用了 Istio 官方提供的 bookinfo 示例,按圖索驥得帶領讀者了解了 sidecar 注入、iptables 透明流量劫持及 sidecar 中流量路由背后的實現細節。Sidecar 模式和流量透明劫持是 Istio 服務網格的特色和基礎功能,理解該功能的背后過程及實現細節,將有助于大家理解 Service Mesh 的原理和 Istio Handbook 后面章節中的內容,因此希望讀者可以在自己的環境中從頭來試驗一遍以加深理解。
使用 iptables 做流量劫持只是 service mesh 的數據平面中做流量劫持的方式之一,還有更多的流量劫持方案,下面引用自 云原生網絡代理 MOSN 官網中給出的流量劫持部分的描述。
使用 iptables 做流量劫持時存在的問題
目前 Istio 使用 iptables 實現透明劫持,主要存在以下三個問題:
需要借助于 conntrack 模塊實現連接跟蹤,在連接數較多的情況下,會造成較大的消耗,同時可能會造成 track 表滿的情況,為了避免這個問題,業內有關閉 conntrack 的做法。
iptables 屬于常用模塊,全局生效,不能顯式的禁止相關聯的修改,可管控性比較差。
iptables 重定向流量本質上是通過 loopback 交換數據,outbond 流量將兩次穿越協議棧,在大并發場景下會損失轉發性能。
上述幾個問題并非在所有場景中都存在,比方說某些場景下,連接數并不多,且 NAT 表未被使用到的情況下,iptables 是一個滿足要求的簡單方案。為了適配更加廣泛的場景,透明劫持需要解決上述三個問題。
透明劫持方案優化
使用 tproxy 處理 inbound 流量
tproxy 可以用于 inbound 流量的重定向,且無需改變報文中的目的 IP/端口,不需要執行連接跟蹤,不會出現 conntrack 模塊創建大量連接的問題。受限于內核版本,tproxy 應用于 outbound 存在一定缺陷。目前 Istio 支持通過 tproxy 處理 inbound 流量。
使用 hook connect 處理 outbound 流量
為了適配更多應用場景,outbound 方向通過 hook connect 來實現,實現原理如下:
無論采用哪種透明劫持方案,均需要解決獲取真實目的 IP/端口的問題,使用 iptables 方案通過 getsockopt 方式獲取,tproxy 可以直接讀取目的地址,通過修改調用接口,hook connect 方案讀取方式類似于tproxy。
實現透明劫持后,在內核版本滿足要求(4.16以上)的前提下,通過 sockmap 可以縮短報文穿越路徑,進而改善 outbound 方向的轉發性能。
參考
Debugging Envoy and Istiod - istio.io
揭開 Istio Sidecar 注入模型的神秘面紗 - istio.io
MOSN 作為 Sidecar 使用時的流量劫持方案 - mosn.io
原文地址:https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing/
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Istio 中的 Sidecar 注入及透明流量劫持过程详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入理解kestrel的应用
- 下一篇: 用long类型让我出了次生产事故,写代码