Netflix OSS、Spring Cloud還是Kubernetes? 都要吧!


Netflix OSS是由Netflix公司主持開發的一套代碼框架和庫,目的是解決上了規模之后的分布式系統可能出現的一些有趣問題。對於當今時代的Java開發者們來說,Netflix OSS簡直就是在雲端開發微服務的代名詞服務發現負載均衡容錯等對於可擴展的分布式系統來說都是非常非常重要的概念,Netflix對這些問題都給出了很好的解決方案。在這里Netflix要對那些在廣大的開源社區中為這些代碼框架和庫做出過貢獻的人們簡單地說聲“謝謝”,還有許多互聯網公司也做出了貢獻,所以在這里一並謝過。可是有一些比較大的互聯網公司卻為自己做的一些東西申請了專利,還把代碼都保留起來沒有開源,這實在不太好。

不過,Netflix OSS的許多內容都是在一個已經過去的年代寫出來的,那時所有東西都只能運行在AWS雲上而沒有其它選擇。關於那個年代的許多寶貴遺產和前提假設都已經被封裝到了Netflix的庫里面,對於現在你運行的環境(比如Linux容器)已經不適用了。在Linux容器Docker容器管理系統等等出現之后,我們越來越看到把我們的微服務運行在Linux容器(公有雲、私有雲,或者都要,等等)里的巨大價值。另外,因為這些容器都是直接把這些服務打包起來、對外不透明的,所以我們傾向於不要過多關心在容器里面運行的到底是什么技術(是Java?還是Node.js?或者Go?)。Netflix OSS主要是為Java開發者服務的,它是許多的庫、框架和配置的集合,你需要把它們包含在你的Java程序或服務代碼里面。

這就帶來了第一個問題。

種類眾多的微服務是可以用各種不同的框架或語言實現的,但像服務發現、負載均衡、容錯等等功能還是非常重要而且必不可少的。如果我們在容器里面運行這些服務,我們可以借助於強大的語言無關的架構來做各種事情,比如構建打包部署健康檢查滾動升級藍綠發布安全、還有其它等等各種事情。作為一種類型的KubernetesOpenShift專注於為企業用戶服務,它把所有事情都幫你做完了:沒有什么東西必須要你的應用層程序去知道或者處理的了。你只要簡簡單單的實現你的應用程序和服務就好,只要專注於它們應該做的功能就好。

這樣是不是說這些架構可以幫忙把大家從服務發現、負載均衡、容錯等功能中解放出來?那為什么這些還要是應用層的事?

如果你使用Kubernets(或者某些變種),那么答案就是:是的。

Kubernetes方式的服務發現

用Netflix OSS時通常你要建立一台服務發現服務器,讓所有可以被各種客戶端發現的服務注冊在這里。比如你用Netflix Ribbon來與各種其它服務交互,就需要能找出來它們都運行在哪里。各種服務可能下線,可能按它們自己的運行需要退出,也有可能我們要向集群中加入更多服務來做橫向擴展。這種集中式的服務發現注冊機制通常可以幫助我們跟蹤集群中有哪些服務是可用的。

問題之一是,做為一個開發人員你需要做這些事情:

  • 決定我到底是想要一個AP系統(ConsulEureka等)還是CP系統(ZooKeeperetcd等等)。
  • 想明白在上了規模時如何運行、管理和監控這些系統(這可不是一個小小的演示系統)。

而且,你要找到對應你使用的編程語言的客戶端庫,才能與服務發現機制通信。回到我們剛才討論的問題,微服務可能是用許多不同種語言實現的,所以可能一個成熟的Java客戶端庫好找,但相應的Go或Node.js庫就沒有了,那你只好自己寫一個。可對每一種語言和每一個程序員,他們都可能對怎么實現這樣的客戶端庫有自己的想法,這樣你就會忙於維護各種不同的客戶端庫,做的事情功能都是一樣的可是實現邏輯卻各自不同。也有可能每種語言都會使用它自己的服務發現服務器而且也有它自己現成的客戶端庫呢?所以你就要為每一種語言都管理和維護不同的服務發現服務器嗎?不管哪種方式都是非常令人討厭的。

如果我們使用DNS能解決問題嗎?

這樣就解決了客戶端庫的問題嗎?DNS是所有使用TCP/UDP的系統自帶的,不管你是部署在單機、雲、容器、Windows或是Solaris等等哪種系統上。你的客戶端程序只需要指向一個域名(比如http://awesomefooservice/),然后底層框架就知道該把服務路由到DNS指向的地方了(也可能是用VIP加負載均衡,或者輪詢DNS等等)。好!現在我們不必再關心我需要使用什么樣的客戶端程序來發現一個服務了,我只需要使用任意一種TCP客戶端就好。我也不必關心如何管理DNS集群,它是網絡路由器自帶的,大家都用得很熟。

但DNS對彈性發現的情況可能表現很糟糕

缺點:DNS對於彈性的、動態的服務集群就表現不好了。要向集群中加入新服務時該怎么辦?要下線服務呢?服務的IP地址可能緩存在DNS服務器或路由器中(也許並不屬於你的管轄范圍),也可能在你自己的IP棧中。還有如果你的程序或者服務要偵聽非標准的80端口呢?DNS是默認使用標准的80端口的。要讓DNS使用非標准端口,你就要用到DNS SRV記錄,這樣就又回到了最初的問題,你需要在應用程序層有特殊的客戶端庫來發現這些記錄。

Kubernetes服務

咱們就用Kubernetes吧。反正我們要在Docker或者Linux容器里面運行東西,那就最好是把Docker容器(或者Rocket容器,或者Hyper.sh容器等等)運行在Kubernetes里面了。

(看,我的技術其實很差,尤其是對於那些看起來好象很“簡單”的技術——因為你不可能用很復雜的組件來搭建出一個很復雜的系統。你會想要簡單的組件。但寫出簡單的組件這事本身其實很復雜。要我理解象Google或Red Hat做的和Kubernetes相關的、來簡化分布式系統部署、管理等等的事對於我來說實在是象是天方夜譚。:) )

使用Kubernetes我們只要創建並使用Kubernetes服務就好了。我們不必浪費時間去搭建服務發現服務器、寫定制化的客戶端庫、調校DNS等等。它直接就可用,我們只要直接進入我們微服務的下一個主題即可,即提供業務價值。

它是怎么工作的呢?

下面這些簡單的抽象都是Kubernetes實現了來支持這些功能的:

Pod很簡單,它們基本上就是你的Linux容器。標簽也很簡單,它們基本上就是一些用於標記你的Pod的鍵-值型字符串(比如Pod A有如下標簽:app=cassandra、tier=backend、version=1.0、language=java)。這些標簽可以是任何你想起的名字。

最后一個概念是服務。也很簡單,一個服務就是一個固定集群IP地址。這個IP地址是虛擬的,可以用來指向真正提供服務的Pod或者容器。那這個IP地址怎么知道該找哪一個Pod或者容器呢?它是用“標簽選擇器”來找到所有符合你要找的標簽的Pod的。比如,假如我們需要一個有“app=cassandra AND tier=backend”標簽的Kubernetes服務,它就會幫我們找到一個VIP,指向任何一個同時有這兩個標簽的Pod。這個選擇器是動態工作的,任何加入集群的Pods都會根據它所帶有的標簽立刻自動參與服務發現。

另一個用Kubernetes作為Pod選擇服務的好處是Kubernetes非常智能,它知道哪個Pod是屬於哪個服務的,還會考慮它的存活和健康情況。Kubernetes會使用內置的存活和健康檢查機制來判斷一個Pod是否應該被加入集群,依據是它是否存活和它是否在正常工作。對於那些不符合條件的它會直接剔除掉。

要注意的是,一個Kubernetes服務的實例不是一個什么“東西”,也不是應用、Docker容器等等任何東西,它是個虛擬的東西,所以自然也不會有單點故障。它就是一個由Kubernetes路由的IP地址。

這對於程序員來說實在是令人難以置信的強大和簡單。現在如果一個應用想要使用一個Cassandra服務,它會直接使用固定IP地址來找到Cassandra數據庫。但寫死一個固定IP地址肯定不是什么好做法,因為當你想要把你的程序或者服務挪個地方時就會遇到麻煩。所以一般做法是改IP(或者加個配置項),可這樣又加重了程序配置功能的負擔,最終解決方案通常就是DNS。

使用Kubernetes內的DNS集群就可以解決上述問題。因為對於一個特定的運行環境(開發、QA等)IP地址是固定的,那我們就不介意直接使用固定IP,反正它也永遠不會變。但如果我們是使用DNS的話,舉個例子,就可以把程序配置成與http://awesomefooservice上的服務交互,這樣不管我們的運行環境怎么變,從開發改到QA再改到生產,我們都會部署那些Kubernetes服務,業務程序就不需要改了。

我們不需要做額外的配置,也不用費心DNS緩存或SRV記錄、定制客戶端庫或管理額外的服務發現框架等等。Pod可以自動加入集群或從集群中剔除掉,Kubernetes服務的標簽選擇器會動態的依據標簽分組。業務程序只需要與http://awesomefooservice/交互即可,隨便你是個Java應用,或者是Python、Node.js、Perl、Go、.NET、Ruby、C++、Scala、Groovy等什么語言寫的都行。這種服務發現機制就不強求必須使用什么特定的客戶端,你隨便用就好了。

這樣服務發現功能就大大簡化了。

客戶端側的負載均衡怎么辦?

這事很有趣。Netflix提供了Eureka和Ribbon兩個用於客戶端側的負載均衡,你也可以組合使用。基本實現就是服務注冊(Eureka/Consul/ZooKeeper等等)功能在跟蹤集群中都有什么服務,並且會向關心這些信息的客戶端更新這些信息。這樣客戶端就知道了集群中都有哪些節點,它只需要選一個(隨機,或者固定,或者任何它自己定制的算法)然后調用就好。等下一次再調用時,它想的話它也可以換另一個來調用。這樣的好處是我們不需要那些可能會迅速成為系統瓶頸的軟/硬負載均衡器。另一個重要方面是,當客戶端知道服務在哪里之后,它直接與服務交互即可,中間不需要再經過中轉。

但依我拙見,客戶端側的負載均衡案例只能占實際情況的5%。我來解釋一下。

我們想要的是一種理想的、可擴展的負載均衡機制,而且不要有額外的設備和客戶端庫等。大多數情況下我們並不介意處理過程中請求會多一跳到負載均衡器(想想看,可能你99%的應用都是這么做的)。我們可能會碰到這樣的情況:服務A要調用B,B還要調用C,然后D、E,等等,想像一下那條調用鏈。這種情況下如果每次調用都要增加額外的一跳的話,我們的整體延遲就會變得很大。所以可能的解決方案就是要“減掉這多余的一跳”,但這一跳也可能不止是到負載均衡器的,更好的辦法是要減少那條調用鏈的層級。請參考我博客上事件驅動系統專題中關於“自治與集中”的討論,我已經考慮過這樣的問題了。

根據上文將Kubernetes服務用作服務發現一節所描述的辦法 ,我們可以有不錯的負載均衡機制(也不會有各種服務注冊、定制客戶端、DNS缺點等額外開銷)。當我們通過DNS或IP來與Kubernetes服務交互時,Kubernetes會默認地就在集群中的Pod之間做負載均衡(注意集群是由標簽和標簽選擇器定義的)。如果你不想有負載均衡的額外一跳也不用擔心,虛擬IP是直接指向Pod的,不會經過實際的物理網絡中轉

那95%的案例就輕松搞定了!所以不必過度設計,簡單就好。

那剩下的5%的情況怎么辦?可能你會有這樣的情況,你的程序要在運行時決定調用集群中的哪個具體終端節點。一般來說你可能會想用些復雜的定制算法,而不是常用的輪詢、隨機、固定某一個等等。這時就可以使用客戶端側的負載均衡機制。你仍然可以用Kubernetes的服務發現機制來找出集群中有哪些Pod是可用的,再根據標簽來決定調用哪個。fabric8.io社區的Kubeflix項目為Ribbon提供了發現插件,比如通過Kubernetes的REST API獲得一個服務的所有Pod,然后調用者可以用代碼來根據業務選擇具體調用哪個,不限編程語言。對於這些情況,花些精力來實現根據不同客戶端情況定制的發現機制代碼庫是值得的,更好的做法是把這些定制的邏輯模塊化,把依賴關系從業務程序中獨立出去。這樣使用Kubernetes時,就可以把這些獨立的算法模塊也隨着你的程序和服務部署上去,就可以方便的使用定制的負載均衡算法了。

我還是那句話,這是5%的需要有額外復雜處理的情況。對於95%的情況,使用內置的機制就夠了。

容錯又怎么辦?

在搭建有依賴關系的系統時要時刻記得每個模塊要對別人提供什么服務,就是說即使調用方不存在或者崩潰了,它也要記得自己的義務。Kubernetes在容錯方面又有什么功能呢?

Kubernetes的確是有自愈功能的。如果一個Pod或者Pod中的一個容器掛掉了,Kubernetes可以把它再拉起來以維持ReplicaSet的不變性。比如你配置想要有10個叫“foo”的Pod,那Kubernetes就會幫你一直維持這個數量。即使某個Pod掛了,它也會再拉起一個來保持住10這個總數。

自愈功能太強大了,而且是隨着Kubernetes原生提供的,但我們討論這個的原因是如果被依賴物(數據庫或其他服務)掛掉了,依賴它的業務程序該怎么樣?這完全要靠業務程序自己決定怎么處理了。舉例來說,如果你想在Netflix上看個電影,就會有個請求發送到授權服務上,校驗你是否有權限去看那個電影。可如果授權服務掛了該怎么辦呢?就不給用戶看那個電影嗎?或者把出錯日志打給用戶看?Netflix的做法是允許用戶看。當授權服務出錯時,允許一個無權限的用戶看某個電影,這種體驗比直接拒絕要好得多,也許人家有這個權限呢?

比較好的做法是優雅的降級,或者找出替代方案來持續提供服務。Netflix Hystrix就是一個非常好的Java解決方案,它實現了機制來做隔離熔斷回滾。每一種都是針對不同業務的具體實現,所以在這種情況下,針對不同的編程語言有定制的客戶端庫也是非常合理的。

Kubernetes也有類似功能嗎?當然!

再看看強大的Kubeflix項目,你可以用Netflix Turbine項目來累積並且將你的集群中運行的所有斷路器可視化。Hystrix可以把所有的服務器事件以數據流的形式發送出去,由Turbine消費掉。那Turbine又怎么知道哪些Pod里面有Hystrix呢?好問題,這個可以用Kubernetes的標簽解決。如果給所有有Hystrix的Pod全都打上個“hystrix.enabled=true”的標簽,Kubeflix Turbine引擎就可以自動發現每個Hystrix斷路器的SSE流,並且把它們展現在Turbine的網頁上。太感謝你了,Kubernetes!

上圖由Joseph Wilk繪制,謝謝。

配置管理怎么辦?

Netflix Archaius是用於處理雲上系統的分布式配置管理的。用法與Eureka和Ribbon一樣,搭起一個配置服務器,再用一個Java庫去查出配置項的值就可以了,它也支持動態更改配置等。請記住Netflix是為了在AWS上構建系統實現的這個功能,但是屬於Netflix的。作為CI/CD管道的一部分,他們要構建AMI並且部署,可構建AMI或任何VM鏡像都是非常耗時的,並且大多數時候都要有很多前置工作。有了Docker或Linux容器,事情就容易多了,接下來我將從配置的角度解釋一下。

還是先說95%的情況。我們希望把環境相關的配置信息保存在我們的業務程序之外,再在運行時從運行環境(開發、QA、生產等)中獲得它們。這里有個非常重要的區別,不是每個配置都和環境相關,要隨着運行環境來改的。而且我們也非常想要有與編程語言不相關的辦法來查找配置,避免強迫大家用Java,然后又要配一堆的Java庫和路徑等。

我們可以用Kubernetes來提供基於環境的配置管理:

我們可以把配置信息通過環境變量提供給Linux容器,這樣不管Java、Node.js、GO、Ruby還是Python等等,大多數編程語言都可以很容易獲取。也可以把配置信息保存在Git上,再把Git Repo和Pod捆綁起來,映射成Pod本地文件系統的文件,這樣任何編程框架都可以以獲取本地文件的方式來獲取配置信息了,這是個好方案。最后,也可以通過Kubernetes的ConfigMap來把版本化的配置信息保存在ConfigMap中,它也是做為文件系統加載到Pod上,這樣就可以將Git Repo解耦出去了。獲取配置信息的方法仍是從文件系統的配置文件中讀數據,你自己喜歡用什么編程語言或者框架都好。

另外5%的情況呢?

在剩下的5%的情況下你可能想要在程序運行時動態更改配置信息。這一點Kubernetes可以幫忙。你只需要在ConfigMap中更改配置文件,然后把那些改變動態的推送到加載了它的Pod上就好了。在這個方案里,你要使用客戶端的庫來幫助你感知到這些配置的改動,並且把它們提交到業務程序中。Netflix Archais就提供了有這樣功能的客戶端。Java版的Spring Cloud Kubernetes在用了ConfigMap時處理這樣的事情更容易。

Spring Cloud怎么樣?

使用Java的程序員們在Spring下開發微服務時常常把Spring Cloud和Netflix OSS等同起來,因為它很大一部分就是基於Netflix OSS實現的。fabric8.io社區中也有很多用Kubernetes運行Spring Cloud的好東西,請查看https://github.com/fabric8io/spring-cloud-kubernetes。包括配置、日志等在內的很多模式都可以用Kubernetes運行得非常好,不用借助服務發現引擎、配置管理引擎等額外的、復雜的框架。

小結

如果你正在構建自己的微服務,而且你也對Netflix OSS/Java/Spring/Spring Cloud等方案很感興趣,請一定提醒自己你們不是Netflix,所以不必直接調用AWS EC2的原語來把自己的程序搞得非常復雜。如果你在調查Docker的方案,那采用Kubernetes是個非常明智的選擇,它本身就自帶許多這樣的分布式系統功能。請在合適的時候將業務級程序庫分層,這樣可以從一開始就避免把你的服務搞的太復雜,因為Netflix在5年前就非常明智的開始這樣做了。事實上他們也是不得不這樣的,但想想如果5年前他們有Kubernetes又會是怎樣?他們的Netflix OSS棧會看起來完全不同的! :)

閱讀英文原文Netflix OSS, Spring Cloud, or Kubernetes? How About All of Them!



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM