基于Istio的高级流量管理二(Envoy流量劫持、Istio架构、高级流量管理)
文章目錄
- 一、Envoy流量劫持機制(Iptables規則流轉)
- 1、流量出向劫持流程
- (1)envoy怎樣劫持入向流量?
- (2)Envoy劫持到流量之后,干什么?(查詢目的地)
- (3)獲取目的地址之后,怎么到達目的地呢?
- 2、流量入向劫持流程
- 3、流量出入總結圖
- 二、Istio、Envoy整體請求流程架構
- 三、Istio高級流量管理實踐
- 1、Istio流量控制簡介
- 2、Istio基本配置對象解析
- (1)Gateway(一般用IngressGateway,EgressGateway一般不用)
- (2)VirtualService
- 3、istio實際配置場景
- (1)規則匹配的優先(類似location規則匹配的優先級)
- (2)rewrite跳轉
- (3)設置HTTPS網站
- (4)金絲雀發布
- (5)兩個版本服務之間按比例拆分流量
- (6)超時與重試
- (7)錯誤注入(混沌測試)
- (8)條件規則
- (9)流量鏡像
- (10)規則委托
- (11)熔斷器與服務訪問請求數限制
- (12)外部服務納入Istio管控
- (13)遙測(監控收集請求的metrics)
- 4、請求跟蹤(服務調用鏈路的可觀測性)
- (1)jaeger的安裝
- (2)jaeger的使用
- (3)鏈路監控的必要條件 Headers 傳遞
- (4)采集頻率控制
在看下面內容之前,先看下這個圖,你必須要明白, 什么時候查路由表做路由判決,什么時候去匹配iptables規則。
一、Envoy流量劫持機制(Iptables規則流轉)
k create ns sidecar// 設置這個 namespace 要自動注入 istio sidecar kubectl label namespace sidecar istio-injection=enabled
其實,一個業務pod被創建,不只是會注入一個envoy sidecar,還會注入一個init container,它的主要作用就是設置一些iptables,使所有請求業務pod的流量和業務pod請求出去的流量都要被envoy抓上來,即下圖所描述的那樣:
我們這個部分的目的就是講清楚這些iptables規則,讓你對Envoy的流量劫持有個詳細的了解!
現在我們要在一個toolbox容器內,訪問一個nginx容器,他們都是在sidecar命名空間下,執行下面這個命令:
# 因為在一個命名空間所以可以省去后綴 curl nginx未使用service mesh,一般的數據包流轉是,kube_DNS將nginx解析成service ip,變成訪問service ip,然后因為我們現在在容器中嘛,因此先通過默認路由到主機,后面就是在主機上的操作了,主機發現訪問service ip,先做routing decision,因為service沒有路由(因為serivce ip其實就是一個虛擬ip,他的作用就是對一組pod ip的映射,因此iptables postrouting就是完成這個映射的),那么路由判決應該就走主機默認路由所屬的網卡出。再根據主機上的kube-proxy 配置的iptables規則 kube_svc chain 去以1/3、1/2、1的概率分別轉到幾個pod中。多說一句,kube proxy實際上就是一個控制循環,watch service 、endpoint、node資源的變更,然后通過修改ipvs或者iptables達到轉發到后端pod的目的。
那么用了service mesh又該怎樣流轉呢?
1、流量出向劫持流程
(1)envoy怎樣劫持入向流量?
curl nginx,首先也還是kube_DNS解析成service ip,然后再做route decision(注意這里是在容器上做的路由判決,上面未使用service mesh的數據包流轉的流程,是在主機上做的路由判決,其實也要在容器上做路由判決,但是由于主要操作都在主機上,因此只說了主機的路由判決,因此就沒提容器的,但是istio主要操作是在容器里的),沒有service路由,路由判決應該就走容器默認路由所屬的網卡出,然后就到了output chain、postrouting chain(istio沒有postrouting)。下面就是Initcontainer注入的所有Iptables規則:
docker inspect 5273deaee3a6|grep -i pid nsenter -t 2777406 -n iptables-save -t nat
15001是什么進程呢?他是envoy監聽的端口,用于接收出向流量的,15006是用于接收入向流量的。這樣流量就進入了envoy。
(2)Envoy劫持到流量之后,干什么?(查詢目的地)
注意envoy對流量分為outbound(15001端口)、inbound(15006端口),而且用了兩個端口進行監聽,然后因為我們要訪問80端口,因此找outbound 80
//然后ctrl f在頁面搜索15001,往下找80監聽器, 當然你也可以指定參數--port 80,直接找到listener // 因為要展示virtualOutbound就沒指定參數 [root@vms120 httpbin]# istioctl proxy-config listener httpbin-85d76b4bb6-nrlqv -o json {"name": "virtualOutbound", // 虛擬監聽器"address": {"socketAddress": {"address": "0.0.0.0","portValue": 15001}}, ..."name": "0.0.0.0_80","address": {"socketAddress": {"address": "0.0.0.0","portValue": 80}},"filterChains": [{"filterChainMatch": {"transportProtocol": "raw_buffer","applicationProtocols": ["http/1.1","h2c"]},"filters": [{"name": "envoy.filters.network.http_connection_manager","typedConfig": {"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager","statPrefix": "outbound_0.0.0.0_80", // outbound 80"rds": {"configSource": {"ads": {},"initialFetchTimeout": "0s","resourceApiVersion": "V3"},"routeConfigName": "80" // 路由配置命名為80有人可能會納悶,我80服務這么多,各個服務都要起個outbound 80監聽器,這我咋識別,注意這個80只是做個分類,不是監聽,所有80端口的服務都用這一個listener,然后用一個路由配置(比如上面的“80”)。至于到底那個服務,匹配那個路由還要根據domain來識別。
然后通過查看80 的RDS,確定自己需要轉發的對象,因此我們執行:
// 這里面有所有80服務的路由,可以通過domains分辨那個服務匹配的是那個路由 [root@vms120 httpbin]# istioctl proxy-config route httpbin-85d76b4bb6-nrlqv --name 80 -ojson {"name": "nginx.sidecar.svc.cluster.local:80","domains": ["nginx.sidecar.svc.cluster.local","nginx.sidecar.svc.cluster.local:80","nginx","nginx:80","nginx.sidecar.svc","nginx.sidecar.svc:80","nginx.sidecar","nginx.sidecar:80","10.99.179.59","10.99.179.59:80"],"routes": [{"name": "default","match": {"prefix": "/" // 不管匹配的URL是什么,都會交給下面的cluster處理},"route": {"cluster": "outbound|80||nginx.sidecar.svc.cluster.local", // cluster name接下來我們就看"cluster": "outbound|80||nginx.sidecar.svc.cluster.local"是什么。
[root@vms120 httpbin]# istioctl proxy-config cluster httpbin-85d76b4bb6-nrlqv --fqdn=nginx.sidecar.svc.cluster.local SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE nginx.sidecar.svc.cluster.local 80 - outbound EDS我們可以發現CDS下面又關聯了個EDS,那我們查一下這個EDS是干什么的。
[root@vms120 httpbin]# istioctl proxy-config endpoint httpbin-85d76b4bb6-nrlqv --cluster outbound|80||nginx.sidecar.svc.cluster.local ENDPOINT STATUS OUTLIER CHECK CLUSTER 10.244.216.30:80 HEALTHY OK outbound|80||nginx.sidecar.svc.cluster.local // 可以看到 CLUSTER nginx.sidecar.svc.cluster.local實際上的終點地址為 10.244.216.30:80查看 pod 的IP 就是 CLUSTER nginx.sidecar.svc.cluster.local實際上的終點
[root@vms120 httpbin]# kubectl get po -A -o wide|grep 10.244.216.30 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-85b98978db-dbdjl 2/2 Running 2 (5h25m ago) 22h 10.244.216.30 vms121.rhce.cc <none> <none>上面只是基本的尋址,你可以通過在istio配置各種復雜的流量管理策略,從而改變LDS、RDS配置,從而達到流量管理的效果。
(3)獲取目的地址之后,怎么到達目的地呢?
現在我們知道了目的地是10.244.216.30:80,envoy就會設置目的地址為10.244.216.30,然后發出數據包,那么又會經過routing decision,目的是pod ip,在容器上路由判決還是在默認路由網卡,又會經過output,主機上又會進入ISTIO_OUTPUT Chain。
這時候你可以回去看之前的iptables規則,你就會發現這樣一條規則:
2、流量入向劫持流程
進來先到prerouting chain,init container iptables規則如下:
如上圖我們可以看到,是redirect到了15006端口,這個是envoy監聽的端口,專門用作進流量的(inbound),出流量用的是15001。
到了Envoy以后,還是會先去找對應listener,15006是一個virtualInbound虛擬監聽器,然后我們要找inbound 80 listener:
# 查看所有listener包括inbound outbound // 其中輸出的0.0.0.0 istioctl proxy-config listener nginx-5c78d859b9-ckd9z -n sidecar --port 80
我們從上圖可以看出,outbound和inbound還是有點不一樣,畢竟outbound的所有80服務的路由配置都在一個Route里面,但是inbound卻是每個都拆開了,但是內容的確都是一樣的。
然后根據Route的名字查詢詳細信息:
istioctl proxy-config route nginx-5c78d859b9-ckd9z -n sidecar --name music-quiz.music.svc.cluster.local:80 -o json
根據上面cluster中的fqdn,查看cluster信息:
發現是個EDS,那就查endpoint信息:
這里就可以得出ip了。其實上面這些路由配置、cluster、ep這些都和outbound是一樣的。拿到pod ip發現是本地,然后envoy又發出去,又走output,gid 1337,因此return掉了,然后根據ip就路由到了業務pod。
3、流量出入總結圖
二、Istio、Envoy整體請求流程架構
架構圖如下(紅方框代表istio watch哪些對象,紅圓框是代碼解讀)
三、Istio高級流量管理實踐
上面這個圖是流量管理的常見業務場景,上圖是金絲雀發布,下圖是按照客戶端的設備類型進行分流。
1、Istio流量控制簡介
重點:Istio統一了入向流量(ingress)、mesh流量(envoy)、出向流量(egress)。
istio 引入了服務版本的概念,可以通過版本(v1、v2)或環境(staging、prod)對服務進一步的細分。這些版本不一定是不同的API版本,他們可能是部署在不同環境(staging、prod或者dev 等)中的同一服務的不同迭代(使用這種方式的常見場景包括A/B 測試或者金絲雀部署)。
istio 的流量路由規則可以根據服務版本來對服務之間流量進行附加控制。
Istio流量管理規則允許運維人員為每個服務/版本設置故障恢復的全局默認值。然而,服務的消費者也可以通過特殊的HTTP頭提供的請求級別值覆蓋超時和重試的默認值。在Envoy代理的實現中,對應的header分別是x-envoy-upstream-rq-timeout-ms和x-envoy-max-retries。
(1)為什么需要錯誤注入:微服務架構下,需要測試端到端的故障恢復能力。
(2)Istio允許在網絡層面按協議注入錯誤來模擬錯誤,無需通過應用層面刪除pod,或者人為在TCP層造成網絡故障來模擬。
(3)注入的錯誤可以基于特定的條件,可以設置出現錯誤的比例:Delay - 提高網絡延時、Aborts - 直接返回特定的錯誤碼
(1)istio 目前僅允許三種負載均衡模式:輪詢、隨機、和帶權重的最少請求。
(2)除了負載均衡外,Envoy 還會定期檢查池中每個實例的運行狀況。Envoy遵循熔斷器風格模式, 根據健康檢查API調用的失敗率將實例分類為不健康和健康兩種。當給定實例的健康檢查失敗次數超過預定閾值時,將會被從負載均衡池中彈出。類似地,當通過的健康檢查數超過預定國值時,該實例將被添加回負載均衡池。您可以在處理故障中了解更多有關 Envoy 的故障處理功能。
(3)服務可以通過使用 HTTP 503 響應健康檢查來主動減輕負擔。在這種情況下,服務實例將立即從調用者的負載均衡池中刪除。
2、Istio基本配置對象解析
之前分析了在pod中訪問service的mesh流量的流程,是必須要經過envoy的。接下來我們要實踐的是用戶從外部訪問域名的流量控制流程,這個是從IngressGateway進來的。因為金絲雀發布這些都是面向用戶的這種外部流量嘛。
(1)Gateway(一般用IngressGateway,EgressGateway一般不用)
IngressGateway這其實對應的就是Envoy Listener。
[root@vms120 networking]# cat bookinfo-gateway.yaml apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata:name: bookinfo-gateway spec:selector:istio: ingressgateway // 這篩選的就是IngressGateway podservers:- port:number: 80name: httpprotocol: HTTPhosts:- "bookinfo.bianmc.com"上面這個的語義就是向istio: ingressgateway這個selector篩選的pod的envoy插入一個監聽80端口、http協議、domain為bookinfo.bianmc.com 的listener
(2)VirtualService
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata:name: bookinfo spec:hosts:- "bookinfo.bianmc.com"gateways:- bookinfo-gateway // 指定gateways 轉發規則,要與這個listener關聯http:- match:- port: 80 // 默認,可以不寫uri:exact: /productpageroute:- destination:host: productpageport:number: 9080解讀:訪問域名 bookinfo.bianmc.com 如果 match 端口80,路徑 /productpage ,就 destination 轉發到目的地名為 productpage 的svc,svc的端口為9080,所以 VirtualService 對應的是Envoy中的路由 route 轉發規則。
除了這兩個基本對象之外,還有DestinationRule(金絲雀發布)、ServiceEntry和WorkloadEntry(外部服務納入istio管控)三個擴展對象,這個就在后面講解。
3、istio實際配置場景
(1)規則匹配的優先(類似location規則匹配的優先級)
重點看紅色部分的字,規則匹配是從上到下匹配,當匹配到之后就不再往后匹配,因此我們應該把精確匹配,范圍較小的匹配放在前面,像匹配所有這種或者范圍大的應該放在后面。
(2)rewrite跳轉
rewrite跳轉規則,只用修改VirtualService即可。
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata:name: bookinfo spec:hosts:- "bookinfo.bianmc.com"gateways:- bookinfo-gatewayhttp:- match:- uri:exact: /productpage/hellorewrite: // 跳轉到那個urluri: /productpageroute:- destination:host: productpageport:number: 9080服務版本更新,原來的/productpage/hello接口被廢棄了,現在變成了/productpage接口,但是用戶不知道這個呀,用戶還是去訪問/productpage/hello,因此我們要將訪問uri: /productpage/hello跳轉到uri: /productpage的訪問。
(3)設置HTTPS網站
- 生成 https 證書
- 創建存放證書的 secret ,也可以在 VirtualService 中直接調用證書文件
- 創建 Gateway 和 VirtualService
測試:
[root@vms120 ~]# curl --resolve bookinfo.bianmc.com:443:10.101.89.49 https://bookinfo.bianmc.com/productpage -v -k(4)金絲雀發布
現在我們有兩個版本的Service,一個version: v1,一個version: v2,現在我們就要對這兩個版本做精細的流量控制,配置如下:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata:name: canary spec:hosts:- canaryhttp:- match:- headers:user:exact: jesseroute:- destination:host: canarysubset: v2- route:- destination:host: canarysubset: v1我們先來看上面這個配置,匹配host = canary,匹配headers中 user = jesse這個用戶的請求路由會發送subset: v2去,subset v2這是什么,這就引出DestinationRule這個對象:
apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata:name: canary spec:host: canarytrafficPolicy:loadBalancer:simple: RANDOMsubsets:- name: v1labels:version: v1- name: v2labels:version: v2trafficPolicy:loadBalancer:simple: ROUND_ROBIN觀察這個對象我們可以看出,首先匹配host = canary,然后定義了一個默認的后端pod負載均衡策略是RANDOM,然后又定義了兩個subset v1和v2,v1匹配Service中有label version: v1的,且采用默認的RANDOM策略,v2匹配Service中有label version: v2的,采用ROUND_ROBIN 輪詢策略。
合在一起,就是jesse這個用戶的請求會發送給version: v2 Service,且Service到后端pod的負載均衡策略是輪詢。其他用戶的請求會發送給version: v1 Service,且負載均衡策略是隨機的。這樣就做到了金絲雀發布。像什么新版本嘗鮮申請,就是給你的用戶請求,打了個header標記newversion = true,所有帶著這個的請求都發到新版本,這樣你就得到新版本的頁面。
(5)兩個版本服務之間按比例拆分流量
注意兩個加起來weight值必須要達到100%。
(6)超時與重試
(7)錯誤注入(混沌測試)
(8)條件規則
(9)流量鏡像
mirror規則可以使Envoy截取所有request,并在轉發請求的同時,將request轉發至Mirror版本中,同時在Header的Host/Authority加上-shadow。注意這些請求會工作在 fire and forget模式,所有response都會被丟棄。
(10)規則委托
主要是減少主配置的復雜度,各個子業務線可以靈活配置自己的下面各個更小服務的路由規則。
(11)熔斷器與服務訪問請求數限制
注意熔斷器是outlierDetection里面定義的,熔斷器服務訪問請求數限制這個是為了保護服務做的設置
(12)外部服務納入Istio管控
Istio內部會維護一個服務注冊表,可以用ServiceEntry向其中加入額外的條目。通常這個對象用來啟用對istio服務網格之外的服務發出請求。
ServiceEntry中使用hosts字段來指定目標,字段值可以是一個完全限定名,也可以時個通配符域名。
這樣就把一個外部的服務,變成了集群內部的一個Service了。就可以納入istio管理。
(13)遙測(監控收集請求的metrics)
4、請求跟蹤(服務調用鏈路的可觀測性)
在微服務中往往一次請求會盡力N多服務,那么每個服務的響應狀態這個業務經過哪些服務對開發或問題排查就顯得額外重要,鏈路監控是其中的一種解決方案,把微服務中的調用鏈進行記錄并且通過可視化的方式進行展示,行業中相對成熟的解決方案就是zipkin,但是因為zipkin的界面并不是那么友好一般我們配合著jaeger進行使用,istio也對它進行了整合.
(1)jaeger的安裝
- 部署jaeger
- Jaeger 部署完畢后,需要指定 Istio 代理向 jaeger Deployment 發送流量。在安裝時,可以使用以下命令進行配置。
- jaeger service采用nodeport暴露出去,這樣我們就可以通過瀏覽器訪問jaeger的web頁面了
(2)jaeger的使用
在 Jaeger dashboard里從Service下選擇productpage,點擊Find Traces 按鈕,可以看到跟蹤信息:
進到下一層可以看到每個服務的調用層次以及總體消耗時間的分布:
再展開可以看到更多的相關內容:
(3)鏈路監控的必要條件 Headers 傳遞
為什么使用服務網格之后還需要傳遞指定的Headers呢? 這里就要從鏈路監控的機制來說了,在服務網格之前需要鏈路監控每個程序都需要向鏈路監控服務器發送消息,由第一個程序找鏈接監控發起ID獲取,接下來的每個程序被調用的時候都需要告知鏈路監控系統我是在這個鏈路ID之中,此時才能關聯整個鏈路.
雖然 Istio 代理能夠自動發送 Span 信息,但還是需要一些輔助手段來把整個跟蹤過程統一起來。應用程序應該自行傳播跟蹤相關的 HTTP Header,這樣在代理發送 Span 信息的時候,才能正確的把同一個跟蹤過程統一起來。
為了完成跟蹤的傳播過程,應用應該從請求源頭中收集下列的 HTTP Header,并傳播給外發請求:
x-request-id x-b3-traceid x-b3-spanid x-b3-parentspanid x-b3-sampled x-b3-flags x-ot-span-context如果查看示例服務,可以看到productpage服務(Python)從HTTP請求中提取所需的標頭:
def getForwardHeaders(request):headers = {}incoming_headers = [ 'x-request-id','x-b3-traceid','x-b3-spanid','x-b3-parentspanid','x-b3-sampled','x-b3-flags','x-ot-span-context']for ihdr in incoming_headers:val = request.headers.get(ihdr)if val is not None:headers[ihdr] = val#print "incoming: "+ihdr+":"+valreturn headers上面是python的寫法,go語言的寫法有點不一樣:
func rootHandler(w http.ResponseWriter, r *http.Request) {// 我現在是service0,然后去調用service1,我在調用service1的時候需要把header傳過去req, err := http.NewRequest("GET", "http://service1", nil)if err != nil {fmt.Printf("%s", err)}lowerCaseHeader := make(http.Header)// go和python不一樣的地方在于,把header拿出來的時候會把首字母變成大寫,我們需要轉成小寫for key, value := range r.Header {lowerCaseHeader[strings.ToLower(key)] = value}req.Header = lowerCaseHeaderclient := &http.Client{}resp, err := client.Do(req)if err != nil {glog.Info("HTTP get failed with error: ", "error", err)} else {glog.Info("HTTP get succeeded")}if resp != nil {resp.Write(w)}glog.V(4).Infof("Respond in %d ms", delay) }(4)采集頻率控制
Istio 默認捕獲所有請求的跟蹤。例如,何時每次訪問時都使用上面的 Bookinfo 示例應用程序 / productpage你在 Jaeger 看到了相應的痕跡儀表板。鏈路監控每次和鏈路服務器通訊也是有性能消耗的,在一個每天千萬pv的業務下把所有鏈路全部采集下來是不合適的,無論從CPU還是磁盤空間都很容易出現瓶頸,并且鏈路監控并不是日志是一種排查手段,所以我們需要在生產環境下進行采集頻率的限制:
找到pilot中PILOT_TRACE_SAMPLING環境變量從100%修改成10%的采集率:
> kubectl -n istio-system edit deploy istio-pilot ...- name: PILOT_TRACE_SAMPLINGvalue: "10" ...再去刷新頁面10次在JaegerUI只會看到一次調用,這邊最小精度是0.01%有效值是0.0~100.0(不需要此功能可以完全不開啟)
總結
以上是生活随笔為你收集整理的基于Istio的高级流量管理二(Envoy流量劫持、Istio架构、高级流量管理)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SMT集成电路板MES系统解决方案
- 下一篇: ATM系统-实验二:Use Case图与