持續集成之「自動化部署」

前文《依賴管理》中,咱們討論瞭如何在代碼變得龐大,組件增多的狀況下,作好外部庫和內部組件依賴管理,從而提升構建效率。能夠應用的實踐包括:一次生成,屢次複用;創建統一依賴庫,外部依賴庫可使用像Maven或Ivy這樣的工具進行統一管理;對架構進行調整,使一個大的代碼庫分紅多個組件;每一個組件有本身的持續集成體系;對多個組件作持續集成。然而,解決一個問題後,總會有另外一個問題等在那裏,須要你來解決。此次Joe的團隊遇到了部署問題。數據庫

星期一早上,Alice一進辦公室,就看到一臉倦意的Joe坐在椅子上,喝着咖啡。瀏覽器

「今天怎麼來得這麼早?看樣子,你沒睡好啊?」Alice問道。服務器

「固然啦,昨天晚上我就來了。」Joe無精打彩地回答道。網絡

「怎麼啦?」架構

「還不是由於新版本上線出了點兒問題」,Joe說道。「看來咱們要把部署這件事好好討論一下,再這樣下去,不僅我要來,大家也要和我同樣啦!呵呵!」app

當天下午,Joe邀請了運維團隊的主要負責人Tom和Steven,召開了一個關於部署問題的討論會。運維

Joe說道:「先請運維部門的Tom介紹一下上週末的新版本上線過程和發現的問題吧。」ide

Tom描述了上線部署全過程。svn

不可重複且不可靠、易出錯的手工部署過程

  1. 當新版本開發測試完成後,由開發團隊的成員在瀏覽器上登陸運維平臺,填寫上線申請單。申請單的內容包括新版本的上線部署步驟。
  2. 測試人員爲了保證可以升級部署成功,首先要複製生產環境中的程序和數據到本地的測試環境中,而後根據上線申請單中所描述的上線部署步驟進行操做,對上線步驟進行驗證。
  3. 運維人員登陸到運維平臺,收到上線申請單後,確認「已收到」。
  4. 運維人員發現上線部署步驟有問題,生產環境的路徑與上線部署步驟中描述的不一致。因而與開發人員進行溝通,讓開發人員修改上線部署步驟。
  5. 開發人員修改後,再次通知測試人員和運維人員查看並確認。
  6. 確認無誤後,運維人員根據部署計劃,登陸到生產環境中,依照上線部署步驟,手工操做完成。

「上週末上線部署時出現的狀況是:在本次部署以前,咱們的集羣中,有兩臺機器因HotFix,其程序配置被修改過,與其它機器不一致。所以,該機器上的部署失敗,致使部分服務不可用。運維人員查了很長時間沒有發現問題,星期日打電話把Joe叫來幫助咱們查問題時,Joe纔回憶起有那麼一次HotFix,但當時負責的運維人員已經離職,沒人其它運維人員知道這件事情。」Tom說道,「咱們對問題進行了分析,認爲應該增強咱們的上線流程管理,對於那種HotFix也應該發起一個審批流程,而且在該流程中不但要主要負責人審批,並且要對相關人發出周知通報。另外,咱們的運維人員應該對上線單進行嚴格審覈,並對部署中所涉及的機器進行更詳細的驗證,對生產環境中的任何修改都要進行登記。即便很是緊急,也要在過後補充記錄一下。」工具

「這些方法當然很好,但其實咱們能夠採用更好的辦法來解決。」Joe接着說到,「假如咱們在部署運維工做也可以借鑑持續集成的作法,利用一些最佳實踐,那麼此次部署事故根本就不會發生。好比(1)將部署操做腳本化;(2)進行持續部署驗證測試;(3)部署腳本通用化,環境變量等使用配置方式傳入;(4)讓測試環境儘量與生產環境一致,至少在成本條件容許的狀況下儘可能保持類似;(5)對環境配置進行版本控制;(6)任何人不得直接對生產環境進行直接的手工操做,等等。」

將部署操做腳本化,並進行部署驗證測試

Bob說道:「嗯,其實那些上線步驟中所描述的內容均可以進行腳本化,以前也討論過這一問題。目前上線步驟中的內容基本均可以寫成自動化腳本,即便如今不行,也能夠經過少許改造,使其能夠自動化。但問題是... ...」Bob猶豫了一下,接着說道,「如何來驗證這些腳本是正確的呢?」

Joe 說道:「保證運維人員是如何驗證上線申請單上的上線步驟是正確的呢?一樣,咱們也能夠作一些部署驗證就好了。這些部署的驗證也能夠經過腳本方式來進行,好比在安裝以前驗證程序所用端口沒有被佔用,安裝以後驗證該端口已被該程序所使用;好比安裝以前驗證程序日誌中記錄了該程序已中止運行,在安裝以後驗證程序日誌中刻錄該程序已從新啓動;等等」。

Alice問道:「那咱們還要調試這些部署腳本呀?沒有線上生產環境,咱們怎麼調試呢?」

各種環境儘量類似,並使部署腳本通用化

Joe 回答道:「首先咱們應該增強基礎設施這方面的投入。在力所能及的狀況下,讓測試環境與生產環境類似。好比,生產環境可能有100臺機器的集羣,那咱們至少要找兩臺機器的集羣作測試環境。生產環境中使用Tomcat,咱們的測試環境和開發環境中也應該使用相同的Tomcat,而不用Jetty。」

Joe 停下來,喝了一口咖啡,接着說道:「這樣一來,咱們的部署腳本就能夠在開發環境、測試環境進行測試了。當開發人員進行本地測試時,可使用這個腳本進行單機的部署。當測試人員進行集成測試時,可使用一樣的腳本進行多機部署。與機器數量無關的配置能夠統一放在某配置文件中。而與機器數量等相關的配置能夠放在另外的配置文件中。因爲在真正上線部署以前,開發人員和測試人員已經使用同一個腳本進行屢次部署,就是對該腳本進行的測試。當咱們上線部署時,只有與機器相關的配置文件會有變化,其它配置基本相同,因此上線部署時腳本出錯的概率已經比較小了。並且,這種自動化沒有人工干預,也不會發生手工誤操做。」

Tom問道:「那這些腳本由誰來寫?由誰維護呢?」

Joe回答道:「誰最瞭解狀況,就由誰來寫。其實,咱們也應該像對待產品代碼同樣,來對待這些腳本和配置文件,把它們放在咱們的代碼庫裏,進行版本控制。不管是運維人員仍是開發人員,或者測試人員,對這些腳本的修改都應該提交到版本控制庫中,除非他所作的修改只是爲了測試他本身在本地的程序,那就不用提交了。這樣一來,‘誰在何時對什麼進行了修改,爲何作修改?’這個審計問題就能夠直接由版本控制系統來回答,也就作到了全部內容可追蹤了。」

對環境管理進行版本控制,杜絕對生產環境的手工直接修改

「聽上去,對於配置文件、腳本等進行版本管理的確是解決了運維部署的不少問題。但如何對環境管理進行版本控制呢?」Tom問道。

Joe想了想,說道:「環境管理比較複雜。通常來講,環境包括幾個層次,包括硬件及網絡配置、操做系統、咱們的應用程序所依賴的軟件堆棧及其配置、以及咱們的應用程序運行時所需的數據及其配置。目前對咱們來講,對於硬件及網絡配置、操做系統這兩層來講,有兩種方式進行管理。一種是利用一些專用軟件進行自動化的遠程配置,即只要給機器加電,就能夠經過一些技術對一臺機器進行系統的安裝與配置。另外一種是使用虛擬化技術來進行系統配置管理。對咱們如今的遊戲平臺來講, 使用後者便可。只要將基本的環境作成虛擬機鏡像文件,並將其做爲環境基線進行版本管理。固然,因爲鏡像一般較大,因此最好不要使用常見的版本控制工具(如subversion,Git等)進行,而使用某種簡單的機制便可。」

Joe停了一下,看看你們沒有提問的意思,因而接着說道:「至於基於其上的軟件堆棧及堆棧中各軟件的配置管理徹底能夠利用相似於CfEngine,Puppet或Chef的工具進行。這些軟件環境管理工具 都提供某種領域專屬語言來描述軟件堆棧配置,並保存在文本文件中。這些工具通常經過服務器/客戶端的工做方式運行,客戶端向服務器發送請求,驗證本機器節點的軟件配置是否與服務器中的設置相符,若是不符,就會自動更新。尤爲重要的是,這些更新操做都是冪等的,即不管這些配置在該客戶機上執行多少遍,每次的結果狀態都是相同的。另外,它們一般能與版本控制工具集成。因此,只要將咱們的軟件堆棧配置管理信息放到版本控制庫中,就能夠同時管理數臺機器。」

「oh, 對不起,Joe,我想打斷一下,」Tom問道:「你能畫一個圖來解釋一下你剛纔所說的這種軟件環境配置管理工具嗎?」

「固然沒問題。」Joe拿起筆在白板上畫了一個Puppet的工做示意圖,以下圖所示。

「看上去清楚多啦。」Tom笑道,「經過這種方式,咱們就只須要將版本控制庫中保存的配置信息檢出到本地,進行相應的修改,再提交到版本控制庫中,這種工具就會自動幫咱們完成必要的配置更新了。是這樣的嗎?」

「對,」Joe點了點頭,說道,「若是咱們的部署腳本也是經過這種方式來作的,那麼咱們就根本沒有必要登陸到生產環境的機器上,進行手工操做了。並且,Puppet還提供一種Try Run功能,能夠進行配置變動的模擬,讓你可以對比一下變動先後的不一樣之處。」

Tom說道:「你說的這些聽上去都不錯。但並非全部人都可以修改生產環境的配置信息的。因此咱們仍是須要一個軟件平臺來管理上線的申請審批流程。」

「在任何企業中,這種申請審批流程和生產環境變動的受權都是必要的,但這僅僅是審覈流程的操做。而真正與軟件部署相同的具體操做都不該該在這種審批流程當中。」Joe回答道。

Tom接過話來,說道:「嗯,這樣的話,咱們仍舊可以作到:有權限的人才能真正修改生產環境的配置文件,同時達到了無人真正直接操做生產環境的目的,避免了手工誤操做帶來的問題。」

參加本次會議的測試人員和運維人員對這種作法產生了濃厚的興趣,並要求開發人員給予配合,將目前遊戲平臺的部署自動化。Tom說道:「這就是咱們運維工做的一個方向。讓枯燥易出錯的重複性手工操做變成受控的自動化,從而解放運維人員,讓咱們能夠關注於更加有價值的運行監控等工做中。」

Alice說道:「這看上去仍是有必定的工做量啊。」

「固然,咱們可能須要作一些工做,但我想這些投入是值得的。」Joe回答道。「同時,還須要各類角色之間更緊密的配合,而不是像以前那樣,經過一個表明上個世紀八十年代先進技術的辦公自動化平臺來描述部署上線步驟這類關鍵的業務操做信息。」

Tom也點了點頭,說:「嗯,應該使用版本控制方式。但咱們仍是須要一個上線審批的流程,只不過,這個流程中再也不保存上線步驟這類與實際部署相關的業務信息,而只是爲了部署人員的資格審覈與信息周知的目標。」

通過一番討論,開發、測試和運維團隊在這件事情上達成了一致,並按計劃開始實施了。

持續集成之「軟件自我識別」

http://www.infoq.com/cn/news/2011/08/ci-software-self-recognition

前文《自動化部署》中,咱們討論了自動化部署。經過對部署操做腳本化、部署驗證自動化、部署環境版本控制、生產部署全自動化等諸多實踐,可讓部署徹底處於受控狀態。然而,做爲運維人員,是否曾經有人走過來問你這樣的問題:「當前生產環境上部署的是哪一個軟件版本?」你是否遇到過這樣的情形,即便手裏拿着一個jar文件或dll文件,也沒法知道它究竟是哪一個版本。也許你可能認爲,這算不了什麼,到某個管理平臺上查一查部署記錄就好了。但是,若是發如今生產環境的集羣服務器上,不一樣機器上部署的同一個程序文件(好比.war文件)的大小卻不相同,哪個的大小是正確的呢?做爲運維人員,你當時的心情會是什麼樣呢?

地點:運維人員辦公室

下午五點鐘,運維人員Steven習慣性地打開線上運維監控平臺,一頁一頁地快速查看着監控數據。這已經成了他的一個工做習慣,只要有新版本上線,他老是常常打開監控頁面看看。「好像沒什麼問題。」他暗自慶幸,「能夠正點下班了」。

突然,他的目光停在了一個流量曲線上。「爲何波動會這麼大呢?」他又查看了其它幾個相關曲線,沒有什麼問題,但直覺告訴它,可能新版本部署有問題。因而,他開始了更詳細的跟蹤分析。

時間隨着電子錶的跳動,一分鐘一分鐘的過去了。終於,Steven站了起來,深深地吸了一口氣。此時,時鐘已指向晚上八點啦。他發現,在生產環境的集羣中,有五臺機器的應用程序文件包與其它機器上的文件包相比,文件大小不同。到底哪一個是正確的呢?

Steven首先查了一下部署管理日誌系統,文件名爲abc,正確版本是1.21。但是這兩種文件的文件名都是abc.war。從文件名上沒有什麼區別。無奈,Steven操起電話,把開發人員Bob從家中叫了回來,一塊兒解決這個問題。

又通過三個多小時的忙活,兩我的終於找到了正確的版本。原來,只有那五臺機器上的二進制包是正確的,其它機器上的都不對。這兩個版本都屬於1.21,只不過,其中一個是快上線前修復bug的一個測試版本,而另外一個是正式上線版本。它們在版本庫中的revision只差一個。多是誰部署時不當心搞錯了。

地點:開發團隊工做室

次日一大早,Steven在站會上把這個Case通報了一下,引發了Joe的注意。Joe說道:「咱們站會以後討論一下,怎麼避免這類問題的發生吧。」

因而,站會後,Joe、Bob、Alice和Steven在一個角落裏坐了下來,並叫來了運維主管Tom。

「咱們都使用了自動化部署,怎麼還會出現這個問題呢?」Tom不解的問道。

Steven答道:「我查看了一下腳本,這部分沒有作驗證,我今天把它加上。不過,這兩個版本的文件名稱是同樣的,只能在部署前拿到它們的MD5,進行比對驗證。」

Alice說道:「咱們還能夠對文件名進行規範,在文件名上加入版本號,好比appname-xxx_xxx。」

「這也是解決問題的一個辦法。可是,你知道,文件是很容易被重命名的。」Joe說道。

Tom又說道:「咱們能夠把MD5和一些元數據信息,好比revision等放到一個無數據描述文件中,並打包在應用程序中。好比,在Java領域,全部的.jar, .war 和.ear文件都容許將這些信息放在 META-INF/MANIFEST.MF文件中。」

「嗯,這也是種好辦法。可是,若是能讓應用程序自我識別,不是更加直接嗎?」Joe說道。

你們一臉迷惑地看着Joe,不明白他在說什麼。

「咱們讓應用程序告訴咱們它是什麼版本,不就是自我識別嘛!」Joe笑道,「其實,這也不是什麼新鮮技術,大家一看就明白了。」

Joe打開筆記本,接上了21寸的顯示器,把他們使用的持續集成和發佈管理工具Cruise的界面打開了,如圖1所示。

圖1 軟件自我識別版本信息

接着說道:「這是咱們用的Cruise服務器的信息頁面。從這裏,咱們能夠很清楚地看到這個應用程序的運行環境信息,好比Java虛擬機的版本、操做系統類型與版本、服務器存儲空間信息、應用程序的數據庫版本、license信息等等。更重要的是第一行的服務器版本信息。(1)表示其對外發布的版本號;(2) 和(3)可能對應着其revision號。」

「我歷來沒有看過這個頁面。」Bob和Alice同時說道。

「這裏面的信息不少啊。」Steven有點兒興奮的說道,「若是咱們的應用程序也能夠這麼作的話,我在這方面的運維工做會輕鬆不少,由於我能夠把自動化部署腳本和自動化部署驗證作得更強大一些。」

Alice說道:「咱們的不少組件都以庫文件方式存在,沒有界面,那怎麼辦?」

「沒有關係,」Steven說道:「界面對自動化運維來講是次重點,最重要的是能夠經過一些API以命令行的方式來執行。」

Joe點頭說道:「讓咱們來總結一下吧,看看接下來作什麼。」

應用程序自識別的應用場景與實現方式

    • 能夠直接拿到應用程序安裝文件或二進制包時

從它的元描述文件(metaData)中查看它來自於哪裏。大多數現代二進制格式支持以某種方式作到這一點。好比,你使用Java時,全部的.jar, .war 和.ear文件都容許將這些信息放在 META-INF/MANIFEST.MF文件中。若是你是在Windows上,創造性地使用versioninfo resources也能達到一樣的效果。注意:並非說把這些信息放在文件名中,由於修改文件名的可能性很大。

    • 當你沒法拿到正在提供服務的二進制文件時(好比應用程序是一個遠端服務,沒法直接登陸到服務器上)

最容易的方法是可以訪問一個已知的URL或者服務調用API,它可以告訴你任何你想知道的信息。究竟是什麼樣的UR或服務調用並不重要,只要須要知道這些信息的人知道怎麼調用它就好了。

固然,API方式還有更多的用途。

好比,當你使用持續部署實踐時,你在部署以前能夠驗證一下將要部署的二進制是否是正確的版本。

而後,你能夠在部署以後,使用這個服務調用去驗證應用程序的正確版本是否是啓動並運行了。

若是有一個動態更新的系統信息顯示板,你就能夠快速且方便地看到哪一個軟件安裝的是哪一個版本,而不用去更新文檔,由於文檔很容易忘記更新。

最後,Steven和開發團隊一塊兒,商定了一些細節。

  1. 每一個組件的文件名按照以下格式生成:組件名+對外版本號+版本庫revision號。
  2. 每次構建中生成該文件的MD5碼。
  3. 在打包時,將這些元數據信息寫入元數據描述文件。因爲使用subversion版本控制庫,並且,各組件的代碼庫會作遷移,因此元數據中,至少包含該構建版本對應的源代碼svn庫的URL和revision。
  4. 每一個組件都提供統一的API調用whoami,要求返回形如NAME0-PUBLIC VERSION:svn URL@revision的自識別信息。
  5. Steven根據上述信息,更新部署腳本,以及自動化部署驗證測試。
相關文章
相關標籤/搜索