[ 翻譯 ] WePay的服務網格高可用
在本系列的前面兩篇文章, ofollow,noindex" target="_blank">在Webpay中使用Linkerd作為服務網格代理 以及 Sidecar和DaemonSet: 容器模式之爭 中,我們深入探討了一些服務網格的細節,分別是服務網格代理( Linkerd)和這些代理的容器模式。
在本系列的第三部分中,我們將站在一個更高層面來看待服務網格系統。具體來說,我們將會一起來看看服務網格系統的健康狀況,並講述該如何使用各組資料來定義WePay基礎架構中服務網格架構的高可用。
全景圖
和我們在本系列中前面討論的服務網格設定一樣,我們將一起來看看一個跑在 Google Kubernetes Engine (GKE)的 Kubernetes 叢集上的高可用和模組化的服務網格。
我們在此之前經歷了幾次服務網格架構設計的迭代,而支援模組化的那個最能滿足我們的需求和需要。在我們的模組化設計中,服務網格的資料面板或者說代理( Linkerd )以及服務發現或者說控制面板( Namerd )被分成兩個獨立的模組,以便維護和監控。在這樣的服務網格架構中,服務之間可以使用Namerd相互發現,並且可以藉助Linkerd將請求路由到其他服務,此外它還提供負載均衡和監控指標。
圖1:服務網格模組即Namerd服務和Linkerd代理
圖1展示了Namerd和Linkerd作為Webpay基礎設施中實現服務網格的模組,還有將 Webpay的Sensu設定 作為核心,讓我們能夠在出現任何問題時檢視服務網格系統的狀態,並且在某些地方無法正常工作時發出告警。
正如前面所提到的那樣,在這篇文章中,我們將會把重點放在如何通過監控工具簡化並實現一個高可用的服務網格系統。我們還將詳細介紹監控系統涉及到的一些內容,一起來看看在一個系統裡什麼操作被認為是正常的,什麼則是異常。
服務網格監控的ABC
上一節介紹了我們是如何通過明確定義的模組來簡化服務網格的架構。使用該架構時,我們把一些時間花在了確定系統中每個模組的監控方式,隨後是整個系統。我們只想簡單的回答這樣一個問題,“應該怎樣監控系統才能在遇到問題時能夠拿到第一手資料,還有,我們的告警物件是誰?”
通過不斷解答這些問題,我們發現並改進了整個系統的各個部分。例如,在我們的POC階段,我們最開始使用的一個解決方案,其中將Namerd當作Kubernetes中Linkerd的sidecar。一旦我們認識到這兩個服務擁有各自的生命週期和健康檢查定義會是一個更好的做法時,我們將Namerd遷移到了 HAProxy 後面的叢集,後端由 Google Compute Engine 例項組成。在多次迭代後,我們把Namerd重構成了Kubernetes的service,在那裡它通過一個 Kubernetes load balancer 暴露給Linkerd代理。
圖2:通過K8S的namer發現的服務網格棧
在圖2中,Namerd會在Kubernetes叢集中做複製同步,Linkerd代理通過它的Kubernetes load balancer與Namerd通訊,它會以proxy的形式執行Kubernetes proxy來監視每個Kubernetes叢集的更改,而每個Kubernetes代理會向它的master上報叢集更改的資訊,反饋到Namerd。
這只是一個示例,其中每個模組的適用範圍的明確極大地簡化了服務網格系統裡服務的監控和生命週期管理。在確定了每個模組的適用範圍後,我們根據系統中模組的高可用要求對它們進行了測試:
- 在可能的情況下,系統應當能夠自愈。
- 監控系統必須能夠上報系統中每個模組內部和外部的健康狀態。
- 儘可能地提高系統的自愈能力,並在未能自愈或無法修復時發出告警。
下面部分介紹了每個Namerd和Linkerd模組的一些重要的監控單元。我們還會詳細介紹我們開發的一些工具,針對每個Google Cloud專案和Kubernetes叢集,分別在監控和告警方面實現外部和內部的可見性。
服務發現,A-OK
服務發現是服務網路系統的重要組成部分,對我們來說,它是基礎設施裡的一項關鍵服務,它能給出在每個資料中心裡發現了多少服務。服務發現如果中斷可能會影響路由,如果中斷延長到幾分鐘,那麼可能會讓服務網格里發現彼此的所有服務的路由停止工作。 因此,如何監控namerd的服務發現取決於Namerd本身的心跳,以及proxy的執行狀況(如圖2所示),它會將服務端點的更改上報給Namerd。
發現服務的心跳
作為服務網格的發現服務,Namerd內部的健康狀況通過監控它的每個例項實現。我們將Namerd遷移到Kubernetes的主要原因之一是為了得到更好的自愈能力和監控,這和我們所監控和觀察跑在資料中心的所有微服務的方式一致。
我們再仔細研究一下執行在Kubernetes環境中的Namerd的副本,圖3顯示了叢集中該服務的N個副本。 Kubernetes的排程程式會監視每個副本,以確保每時每刻存在N個實時的副本。此外,每個副本配置成在一個很短的時間間隔內使用 Kubernetes的health scheduler 進行ping操作,比如每隔幾秒鐘,這樣來衡量它的響應能力。如果這些檢查中有任何一次不成功或是無法響應,那麼Kubernetes會重啟受影響的副本。這樣一來,副本將會重新啟動然後重新繫結之前配置的名稱。
此外,我們可能會遇到所有副本同時受影響的情況。在這種情況下,我們會通過設定一個更高級別的心跳檢測獲悉Namerd完整的停機時間。這個心跳檢測配置在Kubernetes的外部,並且它會在Namerd的 Kubernetes load balancer 後面尋找至少一個健康且可用的後端例項。我們通過在資料中心裡使用Sensu系統的檢查執行器來實現這一目標。
Sensu的檢查器會每隔一個很短的時間間隔(每10秒左右)執行一次,以確保始終至少有一個後端例項是可用的。這個Sensu的檢查即是針對Kubernetes中Namerd的load balancer做簡單的HTTP ping。在圖4中,Sensu服務端向其中一個檢查執行程式發出訊號,讓它去ping Namerd,然後它會向配置好的通知系統上報結果。如果心跳連續多次執行失敗,則會向通知服務傳送告警,便於運維團隊進一步調查。
我們讓每個副本彼此相對獨立地執行,對Namerd服務進行一個完整的健康測試,這主要是通過將每個例項的名稱專用於該副本來實現的。在該模式下,每個副本都是服務本身的一個完整表達,而除了其配置之外沒有其他的依賴項。此外,我們還解決了在服務需要橫向擴充套件來適應高交易量情況下的可擴充套件性。
所有服務,隨時發現
現在可以通過監控Namerd的操作來發現任何可能存在的執行時問題,我們可以把精力專注在識別可能存在的服務發現問題,即圖4中的發現檢查。這類監控的目的是測試Namerd使用的不同型別的代理或 namer 。
我們實現了一個自定義的Sensu檢查,它針對被測試的發現型別設定了對應的 dtab配置 。這些dtab配置定義了一個特定的範疇,也稱為 名稱空間 :
GET /api/1/dtabs/<namespace> HTTP/1.1 HTTP/1.1 200 OK { { "prefix":"/srv/default", "dst":"/#/io.l5d.k8s/prefix/portName" }, { "prefix":"/svc", "dst":"/srv" } }
程式碼示例1:從Namerd獲取實時的dtab配置
名稱空間的一個例子就是用於HTTP1協議的出向linkerd代理的dtab配置。參考程式碼示例1中的一個名稱空間配置,其中包含一個微服務名稱和名稱空間,要求Namerd解析該名稱來發現該微服務的檢查嘗試次數:
POST /dtab/delegator.json HTTP/1.1 { "dtab":<dtab_configuration>, "namespace":<namespace_name>, "path":"/prefix/service" } HTTP/1.1 200 OK { "type":"delegate", "path":"/prefix/service", "delegate":{ "type":"alt", "path":"/srv/prefix/service", "dentry":{ "prefix":"/svc", "dst":"/srv" }, "alt":[ { "type":"neg", ... }, { "type":"leaf", "path":"/#/io.l5d.k8s/prefix/portName/service", "dentry":{ "prefix":"/srv/default", "dst":"/#/io.l5d.k8s/prefix/portName" }, "bound":{ "addr":{ "type":"bound", ... }, "id":"/%/io.l5d.k8s.daemonset/mesh/...", "path":"/" } }, ... ] } }
程式碼示例2:通過Namerd解析一個服務的名字
在程式碼示例2的API呼叫中可以看到,名稱空間A發現了Service X,因此在Namerd的響應主體中返回了 “type”:“leaf”
物件。 在同一請求中,所有其他的發現路由都返回了 “type”:“neg”
,根據API呼叫中的請求主體沒有識別出到Service X的路徑。
該檢查用到的每個名稱空間都和協議及路由器型別,入向/出向,設定等有關。比如,HTTP/1.1協議有一條用於傳送方路由的dtab,以及用於接收路由的另一個dtab,而且為了簡單起見,這還只是考慮了在一個可發現域範圍的情況,其中不包括外部服務或實體。
由於發現是每個微服務環境的核心,所有發現的檢查都被視為對整體服務發現的健康狀況至關重要,如果問題在短時間內無法自我糾正,將會相應地觸發告警。
探測監聽的路由
就像監控Namerd的發現一樣,監控和測試生產環境裡使用Linkerd做代理的資料面板包含兩個維度,每個維度都為我們提供了有關服務網格代理如何執行的不同視角。一個維度是使用一個外部的watcher監控代理執行的健康狀況,在這裡即 Kubernetes的health scheduler 。另一方面,如果在給定代理是健康的條件下,它們是否可以成功地將請求路由到同一叢集或是跨叢集的其他節點上的代理呢?
檢查代理的執行健康狀況的目標是發現那些可以通過重啟有問題的容器解決的問題。因此,我們需要配置一個健康檢查,確保經過代理的一個完整迴圈,它的響應碼可以被轉換成Kubernetes裡的物件,即響應碼是200表明狀態是healthy,而非200的響應碼則會將容器標記成unhealthy:
GET /admin/ping HTTP/1.1 HTTP/1.1 200 OK pong
程式碼示例3:針對每個代理做簡單的健康檢查
這些健康檢查可以根據基礎設施環境自定義任意的複雜度,但是一般在基礎設施裡,只需要做基本的健康檢查就能發現潛在的問題,作為監控檢查的一部分,程式碼示例3展示了對代理容器的簡單ping探測。
圖5:帶有Kubernetes健康檢查探測的Linkerd sidecar代理
圖5展示了基礎設施中每個代理在一個很短的時間間隔裡發生的ping,無論每個代理組使用什麼 容器模式 。
當我們開始檢視本節開頭提到的第二個維度,即檢查路由的內部執行狀況時,檢查代理會變得更有趣。換句話說,如果路由情況從外面看起來很好的話,我們就得看看代理是否健康並且是否能夠路由到不同的域:
圖6:兩個Kubernetes叢集之間內部和外部的探測
要做更明確一點的探測測試的話,我們需要在每個域裡種下一個服務,在這裡即是兩個不同的Kubernetes叢集,這樣一來便可以按需從內部探測任何微服務。在圖6中,在叢集A中啟動探測,指示探測服務A向其代理髮送探測目標服務1的請求。要在這一設定下實現全域覆蓋的話,我們可以在叢集B中使用探測服務B啟動一個相同的探測任務,其中探測本身是從叢集B中發起,但是最終是以叢集A的服務1為目標服務。相同的模式可以擴充套件到任意數量的叢集,使用我們的探測服務的按需功能進行任意數量的迭代。
此外,一套基礎設施環境中存在多種協議的情況也是適用的,比如,同時有 HTTP/1.1
或 HTTP/2
,可以將Probe Service配置成在單次探測檢查中使用這些協議中的任意一種來探測目標:
就像發現檢查那樣,任何路由檢查都是至關重要的,如果問題沒有自愈,那麼會觸發通知或告警。如果在服務網格的環境裡某些東西不能正常工作,我們可以通過這樣端到端的監控形式,彌補基礎設施中可能出錯的地方。
高可用,然後超越
服務網格是一個技術棧,它將控制面板,資料流和負載平衡三塊獨立開來,而且通過一套高可用的設定,我們得以始終獲得它所能帶來的所有好處,並提供更好的保證。
在我們的高可用設定中,我們:
- 將控制面板和資料平面單獨分開,從而最小化我們的監控範圍,而且對於監控棧來說監控物件也變得更加具象。
- 檢視不同的健康檢查的檢查維度,並確保容器操作和功能檢查是分別單獨監控的。
- ......並且,使用這些監控設定時,我們讓可操作的監控事件變得更加清晰而且可以告警出來。
由於從高可用的服務網格設定源源不斷獲得的信心,我們將越來越多的微服務遷移到了服務網格,而且利用了服務網格附帶的所有功能。因此,在本系列的下一篇文章中,我們將深入探討我們的應用程式,REST或gRPC應用,是如何利用服務網格的,以及我們如何在WePay的基礎架構中管理服務網格的生命週期。