Cloud Foundry 技術全貌及核心組件分析
歷經一年多的發展,Cloud Foundry的架構設計和實現有了眾多改進和優化。為了便于大家了解和深入研究首個開源PaaS平臺——Cloud Foundry,《程序員》雜志攜手Cloud Foundry社區開設了“深入Cloud Foundry”專欄,旨在從架構組成、核心模塊功能、源代碼分析等角度來全面剖析Cloud Foundry,同時會結合各行業的典型案例來講解Cloud Foudry在具體應用場景中的表現。
架構設計及核心組件
從總體上看,Cloud Foundry的架構如圖1所示。

圖1 Cloud Foundry架構圖
經過一年多的發展,Cloud Foundry的組件增加了很多。但核心組件并沒有變化,增加的組件是原架構基礎上的細化和專門化。Stager組件解決了打包(Stage)過程需要操 作大量文件且操作時間長的問題,所以它作為獨立進程,使打包工作異步進行,不阻塞作為核心組件的Cloud Controller。
下面是對Cloud Foundry核心組件的描述。
Router。顧名思義,Router組件在Cloud Foundry中是對所有進來的請求進行路由。進入Router的請求主要有兩類。
- 第一類是來自VMC Client或者STS的,由Cloud Foundry使用者發出,叫做管理請求。這類請求會被路由到Cloud Controller組件處理。
- 第二類是對所部署的App的訪問請求。這部分請求會被路由到App execution,即DEA組件中。簡單地說,所有進入Cloud Foundry系統的請求都會經過Router組件。Router組件是可擴展的,由多個 Router共同處理進來的請求。但如何對Router做負載均衡不屬于Cloud Foundry的實現范圍。Cloud Foundry只須保證所有Router都可以處理任何請求,而管理員可用DNS實現負載均衡,也可部署專用硬件來實現,或者簡單點,弄個Nginx做負 載均衡。
在第一個版本中,Router工作由router.rb來做,所有請求都必須經過Ruby代碼處理轉發。這個設計簡單直接,只是容易引起性能問題,新版中做了如下改進,如圖2所示(左側為第一版本,右側為新版)。

圖2 Router工作過程(新舊版對比)
- 使用Nginx的Lua擴展,在Lua中加入URL查詢和統計的邏輯。
- 如果Lua不知道當前的URL應該路由給哪一個DEA,則會發一個查詢請求到router_uls_server.rb(也就是圖2中的“Upstream Locator SVC”)。
- router_uls_server.rb是一個簡單的Sinatra應用,它存儲了所有URL與DEA IP:Port對應關系。另外,它也管理了請求的Session數據。
這樣一來,大量的業務請求在Lua查詢過并保存位置后,都由Nginx直接轉發,不再經過Router,性能和穩定性都大幅提高。
Router的設計中有個難點:我們知道HTTP請求是有上下文的,那如何保證請求的上下文完整呢?簡單來說,就是如何保證有上下文的請求每次都可 以找到同一個DEA處理?Cloud Foundry是支持Session的,當Router發現用戶請求中帶了Cookie信息,它會在Cookie里暗藏一個應用實例的id。當有新請求 時,Router通過解析Cookie得到上次的應用實例,然后轉發到同一臺DEA上。這信息與上面的查詢類似,會先存在于Upstream Locator SVC中,當Lua知道后會保存在Nginx內部提高效率。
DEA (Droplet Execution Agency)。首先要解釋下什么叫做Droplet。在 Cloud Foundry中,Droplet指把提交的源代碼及Cloud Foundry配置好的運行環境(如Java Web就是一個Tomcat),再加一些控制腳本,如start/stop等,全部打包在一起的tar文件。Staging App是指制作Droplet,然后把它存儲起來的過程。Cloud Foundry會保存這個Droplet,直到啟動(start)一個App時,一臺部署了DEA模塊的服務器會來拿這個Droplet的副本去運行。因 此,如果將App擴展到10個實例(instance),那么這個Droplet就會被復制10份,供10臺DEA服務器運行。
圖3是DEA模塊的架構圖(左側為第一版本,右側為新版)。

圖3 DEA模塊架構圖(新舊版對比)
Cloud Foundry剛推出時,用戶部署的應用可以在內網暢通無阻,跑滿CPU,占盡內存,寫滿磁盤。因此,Cloud Foundry開發出了Warden,用這個程序運行容器解決這一問題。這個容器提供了一個隔絕環境,Droplet只可以獲得受限的CPU、內存、磁盤 訪問權限和網絡權限。
Warden在Linux上的實現是將Linux 內核的資源分成若干個namespace加以區分,底層的機制是CGROUP。這樣的設計比虛擬機性能好,啟動更快,也能夠獲得足夠的安全性。
DEA的運行原理沒有發生根本改變:Cloud Controller模塊會發送start/stop等基本的App管理請求給DEA,dea.rb接收這些請求,然后從blobstore下載合適的 Droplet。前面說到Droplet是一個帶有運行腳本和運行環境的tar包,DEA只需要把它拿過來解壓,并執行里面的start腳本,就可讓應用 運行起來,App也就可以被訪問了。換句話說,就是這臺服務器的某一個端口已經在待命,只要有request從這個端口進來,這個App就可以接收并返回 正確的信息。
接著,dea.rb要做以下一些善后的工作。
把這個信息告訴Router模塊(前面說到,所有進入Cloud Foundry的請求都是由Router模塊處理并轉發的,包括用戶對App的訪問請求。一個App運行起來后,需要告訴Router,讓它根據負載均衡 等原則把合適的請求轉進來,使這個App的實例能夠干活)。
- 一些統計性的工作。例如要把這個用戶又新部署了一個App告訴Cloud Controller,以作quota控制等。
- 把運行信息告訴Health Manager模塊,實時報告該App的實例運行情況。
另外,DEA還要負責部分對Droplet的查詢工作。例如,如果用戶想通過Cloud Controller查詢一個App的log信息,那么DEA需要從該Droplet里取到log返回等。
Cloud Controller。Cloud Foundry的管理模塊。簡單來說,就是與VMC和STS交互的服務器端,它收到指令后發消息到各模快,管理整個云的運行,相當于Cloud Foundry的大腦。
以部署一個App到Cloud Foundry為例。在輸入push命令后,VMC開始工作。在做完一輪用戶鑒權、查看所部署的App數量是否超過預定數額、問了一堆相關App的問題后,需要發4個指令。
- 發一個POST到“apps”,創建一個App;
- 發一個PUT到“apps/:name/application”,上傳App;
- 發一個GET到“apps/:name/”,取得App狀態,查看是否已啟動;
- 如果沒有啟動,發一個PUT到“apps/:name/”,使其啟動。
第一版的Cloud Controller是基于Ruby on Rails的,新版的Cloud Controller用Sinatra進行了重寫,并把部分工作獨立成組件, 使Cloud Controller變得更輕。另一個重要的改進是,第一個版本的Droplet是通過NFS共享的,這樣會帶來安全、性能等方面的問題,新版中采用了自 己開發的blobstore存放Droplet。
隨著Cloud Foundry逐漸成熟,權限管理功能在新版本中逐漸完善。在原有的用戶模型基礎上,加入了組織和用戶空間等概念,細化了管理模型。用戶模型的認證是由 UAA模塊實現的。在企業環境中,如果用Cloud Foundry的開源代碼搭建私有云,那么它可以與企業已有的認證系統進行整合,例如LDAP、CAS等。權限控制是由ACM模塊實現的。圖4給出了用戶 訪問Cloud Controller某個API的過程。

圖4 用戶訪問Cloud Controller某個API的過程
Health Manager。它做的事情不復雜,簡單地說,是從各個DEA獲得運行信息,然后進行統計分析、報告、發出告警等。
Services。服務應屬于PaaS的第三層。Cloud Foundry把Service模塊設計成一個獨立的、插件式的模塊,便于第三方方便地把自己的服務整合成Cloud Foundry服務。在GitHub上有以下兩個相關的子項目值得關注。
- vcap-services-base:顧名思義,它包括Cloud Foundry服務的框架及核心類庫。如果開發自定義服務,需要引用到里面的類。
- vcap-services:目前Cloud Foundry支持的,包括官方及大部分第三方貢獻的服務。這個項目的根文件目錄是根據服務名稱劃分的,可以選擇其中自己感興趣的來研究。
由此可見,Service模塊十分方便為第三方提供自定義服務。從架構來說, Cloud Foundry服務部分使用了模板方法設計模式,可通過重寫鉤子方法來實現自己的服務。如果不需要特別邏輯則可以使用默認方法。
現實情況中,種種原因使有些系統服務難以或不愿意遷移到云端,為此Cloud Foundry 引入了Service Broker模塊。
Service Broker可以使部署在Cloud Foundry上的應用能訪問本地服務。Service Broker的使用方法如下。
- 準備被訪問的服務。以PostgreSQL為例,配置好程序和防火墻,讓其可以通過類似 postgres://xyzhr:secret@db.xyzcorp.com:5432/xyz_hr_db的URI訪問。
- 注冊以上URI到Service Broker。
使用Service Broker暴露的服務與使用Cloud Foundry的系統服務無異,準備被訪問的服務中的訪問服務的URI通過環境變量傳給App。App通過URI訪問暴露出來的服務,這過程不必通過 Service Broker。這個過程如圖5所示,與使用系統服務類似,此處不再贅述。

圖5 使用Service Broker所暴露的服務的過程
NATS (Message bus)。 Cloud Foundry的架構是基于消息發布和訂閱的。聯系各模塊的是一個叫NATS的組件。NATS是由Cloud Foundry開發的一個基于事件驅動的、輕量級的消息系統。它基于EventMachine實現。第一版本Cloud Foundry被人詬病的一個問題就是NATS服務器是單節點的,讓人不大放心。新版NATS能支持多服務器節點,NATS服務器間通過THIN來做通 信。NATS的GitHub開源地址是:https://github.com/derekcollison/nats。代碼量不多但設計很精妙,推薦研究它的源代碼。
Cloud Foundry各種優秀特性均源于消息通信架構。每臺服務器上的各模塊會根據當前的行為,向對應主題發布消息,同時也按照需要監聽多個主題,彼此以消息進行通信。
可以說,Cloud Foundry的核心是一套消息系統,如果想了解Cloud Foundry的來龍去脈,跟蹤它里面復雜的消息機制是非常好的方法。舉個最簡單的例子,一個裝有DEA組件的服務器為加強云的計算能力,被加入到 Cloud Foundry集群中。它首先需要表明已準備好隨時提供服務,Cloud Controller可將App部署到它這里,Router也可將相關的請求交給它處理;Health Manger可定時為它體檢等,它會發布一條消息到主題“dea.start”:
NATS.publish(‘dea.start’, @hello_message_json)
@hello_message_json包括DEA的UUID、ip、 port、版本信息等內容。Cloud Controller、Router、Health Manger及其他模塊會監聽這個主題,得到通知,各自干活。
理解Cloud Foundry的最好方法其實是選定某一操作,如部署一個App、創建服務等,以消息為線索,跟蹤到各模塊,看其如何處理。這樣就可以觀察到整個 Cloud Foundry的工作流程。本專欄第2篇文章將專門介紹如何以NATS為主線理解Cloud Foundry原理,這里就不做過多敘述了。
總結
在過去的一年中,Cloud Foundry發生了很多改變,足可看出Cloud Foundry社區的活躍。非常希望本文已把Cloud Foundry的原理講得足夠明白,但請不要把本文作為參考手冊使用,在VMware中國開發者關系團隊的努力下,Cloud Foundry的文檔相當完善,強烈推薦以其作為參考(網址:www.cloudfoundry.cn)。
作者彭麟,EMC中國研究院云計算平臺與應用實驗室高級研究員,主要關注分布式計算、大型系統架構及云計算平臺設計領域的相關研究工作。加入EMC 之前,曾就職于阿爾卡特朗訊,參與多個運營商級別的應用產品及系統的架構設計。