當前IaaS軟件更像雲控制器軟件,要成爲一個完整的雲解決方案還缺乏不少特性(features)。做爲一個正在發展中的技術,預測一個完整的解決方案的必備的全部特性是很是困難的,因此一個IaaS軟件不可能在一開始就完成它全部的特性。基於以上事實,一個IaaS軟件的架構必須有能力,在添加新特性的同時保持核心結構穩定。ZStack的通用插件系統,使得特性能夠像插件同樣實現(在線程內或在線程外),這樣不僅能使ZStack的功能獲得了拓展,也能夠注入業務邏輯內部去改變默認的行爲。java
eBay管理OpenStack私有云的首席工程師,Subbu Allamaraju曾說過:spring
然而,OpenStack是一個雲控制器軟件。儘管社區爲OpenStack的組建作出了巨大貢獻,可是安裝好的一個OpenStack實例並不能算一個雲。做爲一個操縱者你必須處理許許多多的用戶不必定知道的附加的操做。這些包括基礎的員工培訓、初始化、維護、配置管理、補丁、打包、升級、高可用性、監控、度量、用戶支持、容量預測和管理、計費或退款、資源回收、安全、防火牆、DNS、與其餘內部的基礎設施和工具的集成,等等,等等。這些活動將花費大量的時間和精力。OpenStack給出了一些建立雲必備的成分,但並無把雲打包好。數據庫
這表達當前IaaS軟件的處境,除了一些構建的很是好的IaaS(像AWS),大多數IaaS(包括咱們的ZStack)仍然不是一個完整的雲解決方案。因爲像Amazon這樣的已經探索多年的先驅,公有云已經有一個更成熟的模型,對於公共雲解決方案應該是什麼樣子而言。因爲仍然處在開發階段,私有云目前尚未通過驗證的完整的解決方案。不像專用的公有云軟件,能夠專門爲製造商的基礎設施和服務定製;開源的IaaS軟件必須同時考慮公有云和私有云的需求,使得建立一個完整的解決方案變得更加困難。由於咱們沒有辦法預測一個完整的解決方案應該是什麼樣,咱們惟一的辦法是提供一個插件式的架構,它能在添加插件的同時,不影響核心業務穩定性。瀏覽器
許多軟件聲稱本身是插件式的,可是不少並非真的插件式的,或至少不是徹底插件式的。在解釋緣由以前,讓咱們看兩種主要的插件式架構的形式。雖然有不少文章討論過這個話題,以咱們的經驗來看,咱們把全部插件概括成兩種結構,能夠被準確的描述爲GoF design patterns一書中的策略模式和觀察者模式。安全
源自策略者模式的插件網絡
這種形式的插件一般是經過提供不一樣的實現,拓展軟件特定的功能;或者經過添加插件APIs去添加新的功能。咱們熟悉的不少軟件都是經過這種模式搭建的,好比,操做系統的驅動,網頁瀏覽器的插件。這種插件組成的工做方式是,容許應用程序經過定義良好的協議去訪問插件 。數據結構
源自觀察者模式的插件架構
這種形式的插件一般注入應用程序的業務邏輯,針對特定的事件。一旦一個事件發生,掛在上面的插件將被調用,以執行一段甚至可能改變執行流的代碼,好比,當事件知足某些條件,拋出異常去中止執行流。基於這種模式的插件一般對最終用戶是透明的、純內部實現的,例如,一個監聽器監聽數據庫插入事件。這種插件的工做方式是,容許插件經過定義良好的擴展點去訪問應用程序。eclipse
大多數軟件聲稱它們是插件式的,要麼實現了這些組成方式中的一種,要麼有一部分代碼實現這些組成方式。爲了變得徹底插件化,軟件必須設想到這麼一個想法,即全部的業務邏輯都使用這兩種方式實現。這意味着整個軟件是由大量的小插件組成的,就像樂高玩具同樣。ide
一個重要的設計原則貫穿了全部ZStack的組件:每個組件都應該被這麼設計,信息最少、自包含、無關其餘組件。 好比,爲了建立一個虛擬機,分配磁盤、提供DHCP、創建SNAT都是很是必要的步驟,管理建立VM的組件應該很是清楚。可是它真的須要知道這麼多嗎?爲何這個組件不能簡化爲,分配VM的CPU/內存,而後給主機發送啓動請求,讓其餘組件,像存儲、網絡來關心它們本身的事情。你可能已經猜到了這個答案:不,在ZStack中,組件並不須要知道那麼多,沒錯!能夠是那麼簡單。咱們充分意識到這麼一個事實,你的組件知道的信息越多,你的應用程序耦合越緊密,最終你獲得一個複雜的難以修改的軟件。 因此咱們提供如下插件形式來保證咱們的架構是鬆耦合的,而且使咱們容易添加新特性,最終造成一個完整的雲解決方案。
一般IaaS軟件中的插件是整合不一樣物理資源的驅動。例如,NFS主存儲,ISCSI主存儲,基於VLAN的L2網絡,基於Open vSwitch的L2網絡;這些插件都是咱們剛剛提到的策略模式的形式。ZStack已經將雲資源抽象成:虛擬機管理器、主存儲、備份存儲、L2網絡、L3網絡等等。每一個資源都有一個相關的驅動程序,做爲一個單獨的插件。要添加一個新的驅動程序,開發人員只須要實現三個組件:一個類型,一個工廠,和一個具體的資源實現,這些所有都被封裝在單一的插件中,一般被構建成一個jar文件。引用Open vSwitch舉一個例子,讓咱們假定咱們將建立一個使用Open vSwitch做爲後臺的新L2網絡,而後開發者須要:
public static L2NetworkType type = new L2NetworkType("Openvswitch"); /* once the type is declared as above, there will be a new L2 network type called 'Openvswitch' that can be retrieved by API */(一旦類型被聲明,一個新的叫作「Openvswitch」的l2網絡類型能夠被API檢索)
public class OpenvswitchL2NetworkFactory implements L2NetworkFactory { [@Override](https://my.oschina.net/u/1162528) public L2NetworkType getType() { /* return type defined in 1.1 */ return type; } [@Override](https://my.oschina.net/u/1162528) public L2NetworkInventory createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg) { /* * new resource will normally have own creational API APICreateOpenvswitchL2NetworkMsg that * usually inherits APICreateL2NetworkMsg, and own database object OpenvswitchL2NetworkVO that * usually inherits L2NetworkVO, and a java bean OpenvswitchL2NetworkInventory that usually inherits * L2NetworkInventory representing all properties of Openvswitch L2 network. */(新的資源將一般有一個建立的API APICreateOpenvswitchL2NetworkMsg,一般繼承自APICreateL2NetworkMsg,還將有本身的數據庫對象,OpenvswitchL2NetworkVO,一般繼承自L2NetworkVO,和一個java bean OpenvswitchL2NetworkInventory,一般在L2NetworkInventory中表示Openvswitch L2網絡的全部屬性) APICreateOpenvswitchL2NetworkMsg cmsg = (APICreateOpenvswitchL2NetworkMsg)APICreateL2NetworkMsg; OpenvswitchL2NetworkVO cvo = new OpenvswitchL2NetworkVO(vo); evaluate_OpenvswitchL2NetworkVO_with_parameters_in_API(cvo, cmsg); save_to_database(cvo); return OpenvswitchL2NetworkInventory.valueOf(cvo); } [@Override](https://my.oschina.net/u/1162528) public L2Network getL2Network(L2NetworkVO vo) { /* return the concrete implementation defined in 1.3 */ return new OpenvswitchL2Network(vo); } }
public class OpenvswitchL2Network extends L2NoVlanNetwork { public OpenvswitchL2Network(L2NetworkVO self) { super(self); } [@Override](https://my.oschina.net/u/1162528) public void handleMessage(Message msg) { /* handle Openvswitch L2 network specific messages(both API and non API) and delegate * others to the base class L2NoVlanNetwork; so the implementation can focus on own business * logic and let the base class handle things like attaching cluster, detaching cluster; * of course, the implementation can override any message handler if it wants, for example, * override L2NetworkDeletionMsg to do some cleanup work before being deleted. (處理和Openvswitch L2網絡相關的特定消息(API消息或不是API的消息),並把其餘消息交給L2NoVlanNetwork的基礎類處理;因此它的實現能夠關注它自身的業務邏輯,並讓基礎類處理一些如綁定集羣,解綁集羣,的事情。固然,實現方法也能夠覆蓋任何消息處理器,例如,覆蓋L2NetworkDeletionMsg在刪除前作一些清理工做。) */ if (msg instanceof OpenvswitchL2NetworkSpecificMsg1) { handle((OpenvswitchL2NetworkSpecificMsg1)msg); } else if (msg instanceof OpenvswitchL2NetworkSpecificMsg2) { handle((OpenvswitchL2NetworkSpecificMsg2)msg); } else { super.handleMessage(msg); } } }
讓三個組件一塊兒放到一個Maven模塊中,添加一些必須的Spring配置文件,並編譯爲JAR文件,你就在ZStack中建立了一個新的L2網絡類型。全部的Zstack資源驅動程序都是經過以上步驟實現的(類型,工廠,具體實現)。一旦你已經學會了怎麼爲一個資源建立驅動程序,你就學會了怎麼爲全部的資源這麼作。正如咱們在「ZStack--進程內的微服務架構」中提到的同樣,驅動能夠有本身的API和配置方法。
策略模式的插件(驅動)容許你擴展示有的ZStack的功能;然而,爲了使架構鬆耦合,插件必須能注入應用程序的業務邏輯,甚至是其餘插件的業務邏輯;觀察模式插件的關鍵是拓展點,拓展點容許一段插件的代碼在一個代碼流運行的時候被調用。目前Zstack定義了大約100個擴展點,暴露了大量讓插件去接收事件或改變代碼流行爲的場景。建立一個新的擴展點就是定義一個java接口,組件能夠很容易地建立擴展點,以容許其餘組件注入本身的業務邏輯。爲了瞭解它是如何工做的,讓咱們繼續咱們的Open vSwitch的例子;假設Open vSwitch L2網絡須要鉤入建立VM的過程,以在VM建立以前準備好GRE隧道,該插件實現以下:
PreVmInstantiateResourceExtensionPoint: public class OpenvswitchL2NetworkCreateGRETunnel implements PreVmInstantiateResourceExtensionPoint { [@Override](https://my.oschina.net/u/1162528) public void preBeforeInstantiateVmResource(VmInstanceSpec spec) throws VmInstantiateResourceException { /* * you can do some check here; if any condition makes you think the VM should not be created/started, * you can throw VmInstantiateResourceException to stop it */ } @Override public void preInstantiateVmResource(VmInstanceSpec spec, Completion completion) { /* create the GRE tunnel, you can get all necessary information about the VM from VmInstanceSpec */ completion.success(); } @Override public void preReleaseVmResource(VmInstanceSpec spec, Completion completion) { /* *in case VM fails to create/start for some reason, for cleanup, you can delete the prior created GRE tunnel here */ completion.success(); } }
當ZStack鏈接到KVM主機,Open vSwitch L2網絡想要在主機上檢查並啓動Open vSwitch的守護進程,那麼它實現KVMHostConnectExtensionPoint:
public class OpenvswitchL2NetworkKVMHostConnectedExtension implements KVMHostConnectExtensionPoint { @Override public void kvmHostConnected(KVMHostConnectedContext context) throws KVMHostConnectException { /* * you can use various methods like SSH login, HTTP call to KVM agent to check the Openvswitch daemon你可使用不少方式(如SSH登陸,KVM代理的HTTP調用)經過使用在KVMHostConnectedContext上的信息,去檢查在主機上的Openvswitch的守護進程。若是任意狀態讓你認爲主機不能提供Openvswitch L2網絡方法,你能夠拋出KVMHostConnectExtensionPoint去阻止主機鏈接。 * on the host, using information in KVMHostConnectedContext. If any condition makes you think the * host cannot provide Openvswitch L2 network function, you can throw KVMHostConnectExtensionPoint to * stop the host from being connected. */ } }
最後,你須要宣傳你有兩個組件實現了這些擴展點,ZStack的插件系統將確保全部者在一個適當的時間調用你的組件。該通知是在插件的Spring配置文件中完成的:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:zstack="http://zstack.org/schema/zstack" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://zstack.org/schema/zstack http://zstack.org/schema/zstack/plugin.xsd" default-init-method="init" default-destroy-method="destroy"> <bean id="OpenvswitchL2NetworkCreateGRETunnel" class="org.zstack.network.l2.ovs.OpenvswitchL2NetworkCreateGRETunnel"> <zstack:plugin> <zstack:extension interface="org.zstack.header.vm.PreVmInstantiateResourceExtensionPoint" /> </zstack:plugin> </bean> <bean id="OpenvswitchL2NetworkKVMHostConnectedExtension" class="org.zstack.network.l2.ovs.OpenvswitchL2NetworkKVMHostConnectedExtension"> <zstack:plugin> <zstack:extension interface="org.zstack.kvm.KVMHostConnectExtensionPoint" /> </zstack:plugin> </bean> </beans>
這就是你所須要作的一切。建立一個新類型的L2網絡,卻不須要更改其餘任意一個ZStack組件的甚至一行代碼。這是ZStack保持其核心業務流程穩定的基礎。
不要OSGI:熟悉Eclipse和OSGI的人可能已經注意到,咱們的插件系統和eclipse、OSGI的很是類似。可能有人會問,爲何咱們不直接使用OSGI,它但是爲Java應用程序建立插件系統而專門設計的。實際上,咱們花費了至關多的時間嘗試OSGI;然而,咱們感受它是用力過猛。咱們不喜歡在咱們的應用程序有另外一個容器,不喜歡單獨的類裝載器,不喜歡它建立插件的複雜性。看起來OSGI正付出大量努力使插件相互隔離,但ZStack想讓插件扁平化。咱們已經注意到,許多項目在代碼中引入了沒必要要的限制,以使總體架構明顯是分層的、隔離的,但因爲設計糟糕的接口,插件必須寫不少醜陋的代碼來克服這些限制,反而打亂了真正的架構。ZStack將全部的插件做爲本身核心的一部分來考慮,針對核心業務流程擁有同樣的特權。咱們不是構建一個相似瀏覽器的消費級應用程序,用戶可能會錯誤地安裝惡意插件;咱們是在構建一個企業級軟件,每個角落都須要通過嚴格的測試。一個扁平的插件系統使咱們的代碼變得簡單和健壯。
除了以上兩種方式外,開發人員確實有第三種方式擴展ZStack--進程外服務。雖然ZStack把全部的編排服務包裝成一個單一的進程,獨立於業務流程服務的功能能夠被實現爲獨立的服務,這些服務運行在不一樣的進程甚至不一樣的機器上。ZStack Web UI,一個經過RabbitMQ和ZStack編排服務進行交互的Python應用程序,是一個很好的例子。ZStack有一個定義良好的消息規範,進程外的服務能夠用任何語言編寫,只要它們能經過RabbitMQ進行交互。ZStack也有稱爲canonical event的機制,用於暴露一些內部事件給總線,好比VM建立,VM中止,磁盤建立。諸如計費系統的軟件徹底能夠經過監聽這些事件,創建一個進程外的服務。若是一個服務想要在進程外,但仍須要訪問一些尚未暴露的核心業務流程的數據結構,或須要訪問數據庫,它可使用一種混合的方式,即在管理節點上的一塊小插件負責採集數據並將它們發送給消息代理,在進程外的服務接受這些數據並完成本身的事情。
在這篇文章中,咱們展現了ZStack的插件架構。雖然ZStack並無成爲一個完整的雲解決方案,可是它提供了一個架構,能夠將任何將來所須要的特性構建成插件(進程內或進程外),在保持核心業務流程穩定的同時,使得快速發展成爲一個成熟的、完整的雲解決方案變得可能。