Service Mesh如此火熱,背后的技術(shù)細(xì)節(jié)你了解多少?
在 Kubernetes 稱為容器編排的標(biāo)準(zhǔn)之后,Service Mesh 開始火了起來,但是很多文章講概念的多,講技術(shù)細(xì)節(jié)的少,所以專門寫一篇文章,來解析 Service Mesh 背后的技術(shù)細(xì)節(jié)。
Service Mesh 是 Kubernetes 支撐微服務(wù)能力拼圖的最后一塊
Kubernetes 是一個(gè)奇葩所在,它的組件復(fù)雜,概念復(fù)雜。在沒有實(shí)施微服務(wù)之前,你可能會(huì)覺得為什么 Kubernetes 要設(shè)計(jì)的這么復(fù)雜,但是一旦你要實(shí)施微服務(wù),你會(huì)發(fā)現(xiàn) Kubernetes 中的所有概念,都是有用的。
在我們微服務(wù)設(shè)計(jì)的十個(gè)要點(diǎn)中,我們會(huì)發(fā)現(xiàn) Kubernetes 都能夠有相應(yīng)的組件和概念,提供相應(yīng)的支持。
其中最后的一塊拼圖就是服務(wù)發(fā)現(xiàn),與熔斷限流降級(jí)。
眾所周知,Kubernetes 的服務(wù)發(fā)現(xiàn)是通過 Service 來實(shí)現(xiàn)的,服務(wù)之間的轉(zhuǎn)發(fā)是通過 kube-proxy 下發(fā) iptables 規(guī)則來實(shí)現(xiàn)的。
這個(gè)只能實(shí)現(xiàn)最基本的服務(wù)發(fā)現(xiàn)和轉(zhuǎn)發(fā)能力,不能滿足高并發(fā)應(yīng)用下的高級(jí)的服務(wù)特性,比較 Spring Cloud 和 Dubbo 有一定的差距,于是 Service Mesh 誕生了。
它期望講熔斷,限流,降級(jí)等特性,從應(yīng)用層,下沉到基礎(chǔ)設(shè)施層去實(shí)現(xiàn),從而使得 Kubernetes 和容器全面接管微服務(wù)。
以 Istio 為例講述 Service Mesh 中的技術(shù)關(guān)鍵點(diǎn)
就如 SDN 一樣,Service Mesh 將服務(wù)請(qǐng)求的轉(zhuǎn)發(fā)分為控制面和數(shù)據(jù)面,因而分析它,也是從數(shù)據(jù)面先分析轉(zhuǎn)發(fā)的能力,然后再分析控制面如何下發(fā)命令。今天這篇文章重點(diǎn)講述兩個(gè)組件 Envoy 和 Pilot。
一切從 Envoy 開始
我們首先來看,如果沒有融入 Service Mesh,Envoy 本身能夠做什么事情呢?
Envoy 是一個(gè)高性能的 C++ 寫的 Proxy 轉(zhuǎn)發(fā)器,那 Envoy 如何轉(zhuǎn)發(fā)請(qǐng)求呢?需要定一些規(guī)則,然后按照這些規(guī)則進(jìn)行轉(zhuǎn)發(fā)。
規(guī)則可以是靜態(tài)的,放在配置文件中的,啟動(dòng)的時(shí)候加載,要想重新加載,一般需要重新啟動(dòng),但是 Envoy 支持熱加載和熱重啟,一定程度上緩解了這個(gè)問題。
當(dāng)然最好的方式是規(guī)則設(shè)置為動(dòng)態(tài)的,放在統(tǒng)一的地方維護(hù),這個(gè)統(tǒng)一的地方在 Envoy 眼中看來稱為 Discovery Service,過一段時(shí)間去這里拿一下配置,就修改了轉(zhuǎn)發(fā)策略。
無論是靜態(tài)的,還是動(dòng)態(tài)的,在配置里面往往會(huì)配置如上圖中的四個(gè)東西:
- Listener,也即 Envoy 既然是 Proxy,專門做轉(zhuǎn)發(fā),就得監(jiān)聽一個(gè)端口,接入請(qǐng)求,然后才能夠根據(jù)策略轉(zhuǎn)發(fā),這個(gè)監(jiān)聽的端口稱為 Listener。
- Route,有時(shí)候多個(gè) Cluster 具有類似的功能,但是是不同的版本號(hào),可以通過 Route 規(guī)則,選擇將請(qǐng)求路由到某一個(gè)版本號(hào),也即某一個(gè) Cluster。
- Cluster,一個(gè) Cluster 是具有完全相同行為的多個(gè) Endpoint,也即如果有三個(gè)容器在運(yùn)行,就會(huì)有三個(gè) IP 和端口。
但是部署的是完全相同的三個(gè)服務(wù),他們組成一個(gè) Cluster,從 Cluster 到 Endpoint 的過程稱為負(fù)載均衡,可以輪詢等。
- Endpoint,是目標(biāo)的 IP 地址和端口,這個(gè)是 Proxy 最終將請(qǐng)求轉(zhuǎn)發(fā)到的地方。
這四個(gè)的靜態(tài)配置的例子如下:
如圖所示,Listener 被配置為監(jiān)聽本地 127.0.0.1 的 10000 接口,Route 配置為某個(gè) URL 的前綴轉(zhuǎn)發(fā)到哪個(gè) Cluster,Cluster 里面配置負(fù)載均衡策略,Hosts 里面是所有的 Endpoint。
如果你想簡(jiǎn)單的將 Envoy 使用起來,不用什么 Service Mesh,那么一個(gè)進(jìn)程,加上這個(gè)配置文件,就可以了,就能夠轉(zhuǎn)發(fā)請(qǐng)求了。
對(duì)于動(dòng)態(tài)配置,也應(yīng)該配置發(fā)現(xiàn)中心,也即 Discovery Service,對(duì)于上述四種配置,各對(duì)應(yīng)相應(yīng)的 DS,所以有 LDS、RDS、CDS、EDS。
動(dòng)態(tài)配置的例子如下:
控制面 Pilot 的工作模式
數(shù)據(jù)面 Envoy 可以通過加裝靜態(tài)配置文件的方式運(yùn)行,而動(dòng)態(tài)信息,需要從 Discovery Service 去拿。
Discovery Service 就是部署在控制面的,在 Istio 中,是 Pilot。
如上圖,為 Pilot 的架構(gòu),最下面一層是 Envoy 的 API,就是提供 Discovery Service 的 API。
這個(gè) API 的規(guī)則由 Envoy 定,但是不是 Pilot 調(diào)用 Envoy,而是 Envoy 去主動(dòng)調(diào)用 Pilot 的這個(gè) API。
Pilot 最上面一層稱為 Platform Adapter,這一層是干什么的呢?這一層不是 Kubernetes,Mesos 調(diào)用 Pilot,而是 Pilot 通過調(diào)用 Kubernetes 來發(fā)現(xiàn)服務(wù)之間的關(guān)系。
這是理解 Istio 比較繞的一個(gè)點(diǎn)。也即 Pilot 使用 Kubernetes 的 Service,僅僅使用它的服務(wù)發(fā)現(xiàn)功能,而不使用它的轉(zhuǎn)發(fā)功能。
Pilot 通過在 Kubernetes 里面注冊(cè)一個(gè) Controller 來監(jiān)聽事件,從而獲取 Service 和 Kubernetes 的 Endpoint 以及 Pod 的關(guān)系。
但是在轉(zhuǎn)發(fā)層面,就不會(huì)再使用 kube-proxy 根據(jù) service 下發(fā)的 iptables 規(guī)則進(jìn)行轉(zhuǎn)發(fā)了,而是將這些映射關(guān)系轉(zhuǎn)換成為 Pilot 自己的轉(zhuǎn)發(fā)模型,下發(fā)到 Envoy 進(jìn)行轉(zhuǎn)發(fā),Envoy 不會(huì)使用 kube-proxy 的那些 iptables 規(guī)則。
這樣就把控制面和數(shù)據(jù)面徹底分離開來,服務(wù)之間的相互關(guān)系是管理面的事情,不要和真正的轉(zhuǎn)發(fā)綁定在一起,而是繞到 Pilot 后方。
Pilot 另外一個(gè)對(duì)外的接口是 Rules API,這是給管理員的接口,管理員通過這個(gè)接口設(shè)定一些規(guī)則,這些規(guī)則往往是應(yīng)用于 Routes、Clusters、Endpoints 的。
而都有哪些 Clusters 和 Endpoints,是由 Platform Adapter 這面通過服務(wù)發(fā)現(xiàn)得到的。
自動(dòng)發(fā)現(xiàn)的這些 Clusters 和 Endpoints,外加管理員設(shè)置的規(guī)則,形成了 Pilot 的數(shù)據(jù)模型,其實(shí)就是他自己定義的一系列數(shù)據(jù)結(jié)構(gòu),然后通過 Envoy API 暴露出去,等待 Envoy 去拉取這些規(guī)則。
常見的一種人工規(guī)則是 Routes,通過服務(wù)發(fā)現(xiàn),Pilot 可以從 Kubernetes 那里知道 Service B 有兩個(gè)版本,一般是兩個(gè) Deployment,屬于同一個(gè) Service。
管理員通過調(diào)用 Pilot 的 Rules API,來設(shè)置兩個(gè)版本之間的 Route 規(guī)則,一個(gè)占 99% 的流量,另一個(gè)占 1% 的流量。
這兩方面信息形成 Pilot 的數(shù)據(jù)結(jié)構(gòu)模型,然后通過 Envoy API 下發(fā),Envoy 就會(huì)根據(jù)這個(gè)規(guī)則設(shè)置轉(zhuǎn)發(fā)策略了。
另一個(gè)常用的場(chǎng)景就是負(fù)載均衡,Pilot 通過 Kubernetes 的 Service 發(fā)現(xiàn) Service B 包含一個(gè) Deployment,但是有三個(gè)副本。
于是通過 Envoy API 下發(fā)規(guī)則,使得 Envoy 在這三個(gè)副本之間進(jìn)行負(fù)載均衡,而非通過 Kubernetes 本身 Service 的負(fù)載均衡機(jī)制。
以 Istio 為例解析 Service Mesh 的技術(shù)細(xì)節(jié)
了解了 Service Mesh 的大概原理,接下來我們通過一個(gè)例子來解析其中的技術(shù)細(xì)節(jié)。
凡是試驗(yàn)過 Istio 的同學(xué)都應(yīng)該嘗試過下面這個(gè) BookInfo 的例子,不很復(fù)雜,但是麻雀雖小五臟俱全。
在這個(gè)例子中,我們重點(diǎn)關(guān)注 ProductPage 這個(gè)服務(wù),對(duì) Reviews 服務(wù)的調(diào)用,這里涉及到路由策略和負(fù)載均衡。
Productpage 就是個(gè) Python 程序
Productpage 是一個(gè)簡(jiǎn)單的用 Python 寫的提供 Restful API 的程序。
在里面定義了很多的 Route,來接收 API 請(qǐng)求,并做相應(yīng)的操作。
在需要請(qǐng)求其他服務(wù),例如 reviews、ratings 的時(shí)候,則需要向后方發(fā)起 restful 調(diào)用。
從代碼可以看出,Productpage 對(duì)于后端的調(diào)用,都是通過域名來的。
對(duì)于 Productpage 這個(gè)程序來講,他覺得很簡(jiǎn)單,通過這個(gè)域名就可以調(diào)用了,既不需要通過服務(wù)發(fā)現(xiàn)系統(tǒng)獲取這個(gè)域名,也不需要關(guān)心轉(zhuǎn)發(fā)。
更意識(shí)不到自己是部署在 Kubernetes 上的,是否用了 Service Mesh,所以服務(wù)之間的通信完全交給了基礎(chǔ)設(shè)施層。
通過 Kubernetes 編排 Productpage
有了 Productpage 程序,接下來就是將它部署到 Kubernetes 上,這里沒有什么特殊的,用的就是 Kubernetes 默認(rèn)的編排文件。
首先定義了一個(gè) Deployment,使用 bookinfo 的容器鏡像,然后定義一個(gè) Service,用于這個(gè) Deployment 的服務(wù)發(fā)現(xiàn)。
通過 Kubernetes 編排 Reviews
這個(gè)稍微有些復(fù)雜,定義了三個(gè) Deployment,但是版本號(hào)分別為 V1、V2、V3,但是 Label 都是 app:reviews。
最后定義了一個(gè) Service,對(duì)應(yīng)的 Label 是app:reviews,作為這三個(gè) Deployment 的服務(wù)發(fā)現(xiàn)。
Istioctl 對(duì) Productpage 進(jìn)行定制化之一:嵌入 proxy_init 作為 InitContainer。
到目前為止,一切正常,接下來就是見證奇跡的時(shí)刻,也即 Istio 有個(gè)工具 Istioctl 可以對(duì)于 Yaml 文件進(jìn)行定制化。
定制化的第一項(xiàng)就是添加了一個(gè) InitContainer,這種類型的 Container 可以做一些初始化的工作后,成功退出,Kubernetes 不會(huì)保持它長(zhǎng)期運(yùn)行。
在這個(gè) InitContainer 里面做什么事情呢?
我們登錄進(jìn)去發(fā)現(xiàn),在這個(gè) InitContainer 里面運(yùn)行了一個(gè) Shell 腳本。
就是這個(gè) Shell 腳本在容器里面寫入了大量的 iptables 規(guī)則。
首先定義的一條規(guī)則是 ISTIO_REDIRECT 轉(zhuǎn)發(fā)鏈,這條鏈不分三七二十一,都將網(wǎng)絡(luò)包轉(zhuǎn)發(fā)給 Envoy 的 15000 端口。
但是一開始這條鏈沒有被掛到 iptables 默認(rèn)的幾條鏈中,所以不起作用。
接下來就是在 PREROUTING 規(guī)則中,使用這個(gè)轉(zhuǎn)發(fā)鏈,從而進(jìn)入容器的所有流量,都被先轉(zhuǎn)發(fā)到 Envoy 的 15000 端口。
Envoy 作為一個(gè)代理,已經(jīng)被配置好了,將請(qǐng)求轉(zhuǎn)發(fā)給 Productpage 程序。
Productpage 程序接受到請(qǐng)求,會(huì)轉(zhuǎn)向調(diào)用外部的 reviews 或者 ratings,從上面的分析我們知道,Productpage 只是做普通的域名調(diào)用。
當(dāng) Productpage 往后端進(jìn)行調(diào)用的時(shí)候,就碰到了 output 鏈,這個(gè)鏈會(huì)使用轉(zhuǎn)發(fā)鏈,將所有出容器的請(qǐng)求都轉(zhuǎn)發(fā)到 Envoy 的 15000 端口。
這樣無論是入口的流量,還是出口的流量,全部用 Envoy 做成了漢堡包。
Envoy 根據(jù)服務(wù)發(fā)現(xiàn)的配置,知道 reviews 或者 ratings 如何訪問,于是做最終的對(duì)外調(diào)用。
這個(gè)時(shí)候 iptables 規(guī)則會(huì)對(duì)從 Envoy 出去的流量做一個(gè)特殊處理,允許它發(fā)出去,不再使用上面的 output 規(guī)則。
Istioctl 對(duì) Productpage 進(jìn)行定制化之二:嵌入 Proxy 容器作為 Sidecar。
Istioctl 做的第二項(xiàng)定制化是嵌入 Proxy 容器作為 Sidecar,如下圖:
這個(gè)似乎看起來更加復(fù)雜,但是進(jìn)入容器我們可以看到,啟動(dòng)了兩個(gè)進(jìn)程。
一個(gè)是我們熟悉的 Envoy,它有一個(gè)配置文件是 /etc/istio/proxy/envoy-rev0.json。
我們?cè)偾懊嬷v述 Envoy 的時(shí)候說過,有了配置文件,Envoy 就能夠轉(zhuǎn)發(fā)了,我們先來看看配置文件里面都有啥。
在這里面配置了 Envoy 的管端口,等一下我們會(huì)通過這個(gè)端口查看 Envoy 被 Pilot 下發(fā)了哪些轉(zhuǎn)發(fā)策略。
然后就是動(dòng)態(tài)資源,也即從各種 Discovery Service 去拿轉(zhuǎn)發(fā)策略。
還有就是靜態(tài)資源,也即靜態(tài)配置的,需要重啟才能加載的。
這就是 pilot-agent 的作用,它是 Envoy 的一個(gè)簡(jiǎn)單的管理器,因?yàn)橛行╈o態(tài)資源,如果是 TLS 的證書,Envoy 還不支持動(dòng)態(tài)下發(fā),因而需要重新靜態(tài)配置,然后 pilot-agent 負(fù)責(zé)將 Envoy 進(jìn)行熱重啟加載。
好在 Envoy 有良好的熱重啟機(jī)制,重啟的時(shí)候,會(huì)先啟動(dòng)一個(gè)備用進(jìn)程,將轉(zhuǎn)發(fā)的統(tǒng)計(jì)數(shù)據(jù)通過 Shared Memory 在兩個(gè)進(jìn)程間共享。
深入解析 Pilot 的工作機(jī)制
Pilot 的工作機(jī)制展開后如上圖所示,istio config 是管理員通過管理接口下發(fā)的轉(zhuǎn)發(fā)規(guī)則。
Service Discovery 模塊對(duì)于 Kubernetes 來講,就是創(chuàng)建了一個(gè) Controller 來監(jiān)聽 Service 創(chuàng)建和刪除的事件。
當(dāng) Service 有變化時(shí),會(huì)通知 Pilot,Pilot 會(huì)根據(jù)變化更新下發(fā)給 Envoy 的規(guī)則。
Pilot 將管理員輸入的轉(zhuǎn)發(fā)策略配置和服務(wù)發(fā)現(xiàn)的當(dāng)前狀態(tài),變成 Pilot 自己的數(shù)據(jù)結(jié)構(gòu)模型,然后暴露成 Envoy 的 API,由于是 Envoy 來調(diào)用,因而要實(shí)現(xiàn)一個(gè)服務(wù)端,這里有 LDS、RDS、CDS、EDS。
接下來我們看,在 Pilot 上配置 Route 之后會(huì)發(fā)生什么?
如上圖,我們將所有的流量都發(fā)給版本 1。
我們查看 Envoy 的管理端口,可以看到只配置了 Reviews 的 V1。
當(dāng)我們修改路由為 V1 和 V3 比例是五十比五十。
可以看到 Envoy 的管理端口,路由有了兩個(gè)版本的配置,也對(duì)應(yīng)后端的兩個(gè) IP 地址。