如何在細粒度的架構中更好的微服務。這里會從持續集成和持續交付說起。
1.持續集成簡介
CI(Continuous Integration , 持續集成)
CI能夠保證新提交的代碼與已有的代碼進行集成,從而保證所有人保持同步。CI服務器會檢測到
代碼已提交並簽出,然后花些時間來驗證代碼是否通過編譯以及測試能否通過。
作為這個流程的一部分,我們經常會生成一些構建物(artifact)以供后續驗證使用。
理想情況下,這些構建物應該只生成一次,然后在本次提交所對應的所有部署環節中使用。
CI的好處很多。通過它,我們能夠得到關於代碼質量的某種程度的快速反饋。
CI可以自動化生成二進制文件。用於生成這些構建物的所有代碼都在版本的控制之下,
所以如果需要的話,可以重新生成這個版本的構建物。通過CI我們能夠從已部署的構建物回溯到
相應的代碼,有些CI工具,還可以使這些代碼和構建物上運行過的測試可視化。
持續集成允許我們更快速,更容易的修改代碼。
有人任務使用了CI工具就算采用了CI這個實踐,事實上,只有工具是遠遠不夠的。
測試別人是否真正理解CI的三個問題?
- 你是否每天簽入代碼到主線?
你應該保證代碼能夠與已有代碼進行集成
- 你是否有一組測試來驗證修改?
如果沒有測試,我們只能知道集成后沒有語法錯誤,但無法知道系統的行為是否已經被破壞。
沒有對代碼行為進行驗證的CI不是真正的CI。
- 當構建失敗后,團隊是否把修復CI當做第一優先級的事情來做?
綠色的構建意味着,我們的修改已經安全地和已有代碼集成在了一起。紅色的構建意味着,最后一次修改很可能有問題,
這時只能提交修復構建的代碼。
2.把持續集成映射到微服務
前面已經提到過,每個微服務應該能夠獨立於其他服務進行部署。
所以如何在微服務、CI構建及源代碼三者之間,建立起合適的映射呢?
最簡單的做法,如下
圖 6-1 把所有微服務放在同一個代碼庫中,並且只有一個構建
這種方法從表面上看比其他方法要簡單的多:因為你需要關心的代碼庫比較少,
而且從概念上來講,這種構建也比較簡單。開發者的工作也得到了簡化:
我們只需要提交代碼即可,如果需要同時在多個服務上工作的話,一個提交就能搞定。
在同步發布(lock - step release)中,你需要一次性部署多個服務。
一般來講,我們絕對應該避免這個模式,但是在項目初期是個例外。
當僅有一個團隊在所有的服務上工作時,這種模式在短時間內是可接受的。
這種模式存在很多明顯的缺點。
如果我僅修改了圖6-1中用戶服務中的一行代碼,所有其他服務都需要進行驗證和構建,
而事實上它們或許並不需要重新進行驗證和構建,所以這里我們花費了不必要的時間。
更糟糕的是,我不知道那些構建物應該被重新部署,哪些不應該。
使用這種方式的組織,往往都會退回到同時部署所有代碼的模式,而這也正是我們非常不想看到的。
很不幸,如果這一行的修改導致構建失敗,那么在構建得到修復之前,其他服務相關的代碼也無法提交。
這種方法的一個變體是保留一個代碼庫,但是存在多個CI會分別映射到代碼庫的不同部分。
如圖 6-2
這種模式是個雙刃劍。
一方面它會簡化檢出/檢入的流程,但是另一方面,它會讓你覺得同時提交對多個服務的修改
是一件簡單的事情,從而做出將多個服務耦合在一起的修改。
但是相對於只有一個構建的多個服務來說,這個方式已經好很多了。
還有一種比較好的方式,每個微服務都有自己的CI,這樣就可以將該微服務部署到生產環境之前做一個快速的驗證。
如圖6-3
這里的每個微服務都有自己的代碼庫,分別於相應的CI綁定。
當對代碼庫進行修改時,可以只運行相關的構建以及其中的測試。
每個微服務都有自己的代碼庫和構建流程。
我們也會使用CI構建流程,全自動話的創建出用於部署的構建物。
3. 構建流水線和持續交付
把一個構建分成多個階段是很有價值的。
因為有的測試運行快,涉及范圍小,有的測試運行耗時,涉及范圍廣,
如果放一起,快速如果失敗,還會接着運行耗時的測試,這樣就不合理。
解決這個問題的一個方案是,將構建分解成為多個階段,從而得到我們熟知的構建流水線。
在第一個階段運行快速測試,在第二個階段運行耗時測試。
構建流水線可以很好的跟蹤軟件構建進度:每完成一個階段,就離終點更近一步。
流水線也能夠可視化本次構建物的軟件質量。構建物會在整個構建的第一個環節生成,
然后會被用在整個流水線中。
CD(Continuous Delivery, 持續交付)基於上述概念,並在此之上有所發展。
CD能夠檢查每次提交是否達到了部署生成環境的要求,並持續地把這些信息反饋給我們,
它會把每次提交當成候選發布版本來對待。
為了更好的理解這些概念,我們需要對從代碼提交及部署到生產環境這個過程中,所需要經歷的流程進行建模,
並知道哪些版本的軟件時可發布的。
UAT(User Acceptance Testing, 用戶驗收測試)流程。
通過對整個軟件上線過程進行建模,軟件質量的可視化得到了極大改善,這可以大大減少發布之間的間隔,
因為可以在一個集中的地方看到構建和發布流程,這也是可以引入改進的一個焦點。
在微服務的世界,我們想要保證服務之間可以獨立於彼此進行部署,所以每個服務都有自己獨立的CI.
不可避免的例外
所有好的規則都需要考慮例外。
當一個團隊剛開始啟動一個新項目時,尤其是什么都沒有的情況下,你可能會花很多時間來識別出服務的邊界。
所以在你識別出穩定的領域之前,可以把初始服務都放在一起。
在最開始的階段,經常會發生跨服務邊界的修改,所以時常會有些內容移入或者移出某個服務。
在這個階段,把所有的服務都放在一個單獨的構建中,可以減輕跨服務修改所帶來的代價。
當然,在這個階段你必須把所有服務打包發布,但這應該是一個過渡步驟。
4.平台特定的構建物
大多數技術棧都有相應的構建物類型,同時也有相關的工具來創建和安裝這些構建物。
Ruby中有gem,Java中有JAR包和WAR包,Python中有egg。
但是,從微服務部署的角度來看,在有些技術棧中只有構建物本身是不夠的。
所以為了部署和啟動這些構建物,需要安裝和配置一些其他軟件,再啟動這些構建物。
自動化可以對不同構建物的底層部署機制進行屏蔽。
5.操作系統構建物
有一種方法可以避免多種技術棧下的構建物所帶來的問題,那就是使用操作系統支持的構建物。
舉個例子,對基於RedHat或者CentOS的系統來說,可以使用RPM;對於Ubuntu來說,可以使用deb包;
對於Windows來說,可以使用MSI。
使用OS特定構建物的好處是,在做部署時不需要考慮底層使用的是什么技術。只需要簡單使用內置的工具就可以完成軟件的安裝。
OS包管理工具,可以幫你完成很多原本需要使用Chef或者Puppet來完成的工作。
其缺點是,剛開始編寫構建腳本的過程可能會比較困難。
當然,還有另一個缺點,即如果你需要部署到多個操作系統的話,維護不同版本構建物的開銷就會很大。
但如果軟件時部署在你可控的機器上,那么建議,盡量減少需要維護的操作系統的數量,最好只維護一種。
它可以大大減少不同機器之間可能存在的不同之處,並減小部署和維護的工作量。
特別是如果你在linux上工作,而且采用多種技術棧來部署微服務,那么這種方法就很合適。
6.定制化鏡像
使用類似Puppet、Chef及Ansible這些自動化配置管理工具的一個問題是,需要花費大量時間在機器上運行這些腳本。
什么是藍綠部署?
藍綠部署允許我們在老版本服務不下線的同時,去部署新版本的服務。可以減少在部署時,服務停止的時間增加。
一種減少啟動時間的方法是創建一個虛擬機鏡像,其中包含一些常用的依賴。
現在你可以把公共的工具安裝在鏡像上,然后在部署軟件時,只需要根據該鏡像創建一個實例,
之后在其上安裝最新的服務版本即可。
你只需要構建一次鏡像,然后根據這些鏡像啟動虛擬機,不需要再花費時間來安裝相應的依賴,
因為它們已經在鏡像中安裝好了,這樣就可以節省很多時間。如果你的核心依賴沒有改變,
那么新版本的服務就可以繼續使用相同的基礎鏡像。
這個方法也有些缺點。
首先,構建鏡像會花費大量的時間。
其次,產生的鏡像可能會很大。例如,當你創建VMWare鏡像時,在網絡上傳送一個20GB的鏡像文件會怎么樣。
由於歷史原因,構建不同平台上的鏡像所需的工具是不一樣的。
6.1 將鏡像作為構建物
我們可以把服務本身也包含在鏡像中,這樣就把鏡像變成了構建物。
就像使用OS特定軟件包那樣,可以認為這些VM鏡像時對不同技術棧的一層抽象。
我們不需要關心運行在鏡像中的服務,所使用的語言是Ruby還是Java,最終構建物是gem還是JAR包,
我們唯一需要關心的就是它能否工作。
這個簡潔的方法有助於我們實現另一個部署概念:不可變服務器。
6.2 不可變服務器
通過把配置都存到版本控制中,我們可以自動化重建服務,甚至重建整個環境。
但是如果部署完成后,有人登陸到機器上修改了一些東西呢?
這就會導致機器上的實際配置和源代碼管理中的配置不再一致,這個問題叫做配置漂移。
為了避免這個問題,可以禁止對任何運行的服務器做手動修改。
相反,無論修改多么小,都需要經過構建流水線來創建新的機器。
7.環境