持續部署,并不簡單!

作者: 常新居士  來源: 酷殼  發布時間: 2015-05-15 15:03  閱讀: 5545 次  推薦: 4   原文鏈接   [收藏]  

  (原文發布于2012年6月25日)

  這幾年,持續集成隨著敏捷在國內的推廣而持續走熱,與之相伴的持續部署也一直備受關注。自前兩年,持續交付這個延續性概念又闖進了國內 IT 圈,慢慢開始在社區和會議中展露頭角。許多不明真相的群眾跟風哭著喊著要“上”,而許多前 CI 的半吊子玩家換件衣服就接著干,有的甚至衣服都來不及換……。國內的這些土財主如果不巧請了某些所謂的戰略家,除了建了一堆持續集成環境,以及每天嚷嚷著要這個要那個,混亂的狀況在根本上沒有得到改善。本文無意費力探討持續集成和持續交付的概念,而是打算談談對于大型軟件企業,以持續集成為基礎實現持續部署(交付)時,所要面對的問題以及可行的解決方案。地主老財們,夜黑風正猛,山高路又遠,注意腳下……

  And God Said, Let there be light: and there wa — GENSIS, Charpter 1, King James

  一、起步

  先來講個故事……

  幾年前,一對留美的夫婦通過朋友找到我,讓我幫忙在國內組建一個開發團隊,該團隊負責為其開發一款基于社交網絡的客戶關系管理軟件,(暫且稱之為項目A)。這個項目除了尚不清晰的需求范圍和很緊的期限外,作為業內人士的老公 Richard 根據眼下流行的軟件開發過程還提了諸多額外的要求:

  • 功能要及早交付(以便拿去和潛在的投資人洽談)
  • 功能在部署到生產環境前要先部署到一個測試環境(Richard 要試用后給予反饋)
  • 功能必須經過測試(長期作為軟件外包的甲方,對質量要求嚴格)
  • 要減少后期維護的工作(美國人精貴,少雇一個是一個)
  • 支持協同開發(以便維護人員及早介入)
  • ……

  這正是持續集成所要解決的典型場景。針對 Richard 的要求,我們只要建立一個基于 Hudson(現在叫 Jenkins)+ Maven + SVN 的持續集成環境(再加上持續集成所要求的測試和過程)就可以很好地滿足上述要求,此方案的結構如下:

  對于上述方案,讓我們近距離看看各個服務器的內部情況,以及人員在這種方案下的分工協作:

  我們先談談上面的圖中涉及的一些概念性問題:

  1. 1)編譯時依賴運行時依賴

  從字面上不難理解這兩種依賴的類型。但要注意雖然編譯時依賴常常也是運行時依賴,但并不能推斷出一方必然是另一方。比如,在開發的過程中需要某些提供 API 的 Jar 包,而運行時可能是具體 API 實現的 Jar 包。再者,被依賴的包會有其自身的依賴,因此,項目對這些包產生間接依賴(運行時依賴),依此類推,最終形成一個依賴樹。當項目運行時,這些依賴樹上的包必須全部就位。

  Maven 在 POM 中通 scope 來界定依賴的類型,從而幫助開發和運維人員擺脫手動處理依賴樹的工作,然而運行時所依賴包最終是要安裝到生產環境的,這部分工作 Maven 并不能自動完成。因此,一個常用方式是將運行時所依賴的包拷貝到項目文件中,比如 Java Web 應用的 WEB-INF/lib,然后將項目總的打一個包。在安裝項目包后,修改環境變量,將這些包所在的路徑加入相應的環境變量中,如 ClassPath

  再看個例子,現代的操作系統和其它系統框架都考慮到了運行時依賴樹的處理問題,比如 Ubuntu 的 apt-get,CentOS 的 yum,Ruby 的 RubyGem,Node 的 npm 等等。

  1. 2)依賴時的復雜度

  項目除了對程序包的依賴,對于運行環境也有些具體的要求,比如,Web 應用需要安裝和配置 Web 服務器,應用服務器,數據庫服務器等,企業應用中可能需要消息隊列,緩存,定時作業,或是對其它系統以 Web Service 方式暴露的服務。這些可以看做項目在系統層面對外部的依賴。這些依賴有些可以由項目自行處理,而有些則是項目無法處理的,比如運行容器,操作系統等,這些是項目的運行環境。

  總之,依賴的復雜度主要有兩個:

  1. 依賴包間的版本兼容性問題。兼容性問題是軟件開發的惡夢。
  2. 間接依賴,或多重依賴問題。這個問題可以類比想像一下 C++ 中的多重繼承中出現的很多問題。

  比如:A 依賴于 python 2.7,A還依賴于B,但是B卻依賴于 python 3,而 Python 2.7 和 Python 3 不兼容。這是依賴中最惡心的事。

  1. 3)任務分工

  由于項目簡單,因此并不需要專門的運維人員。以一個 100 人左右以交付為主業(恩,就是做外包)的公司為例,由于沒有任何歷史項目和代碼的拖累,且各個項目間也沒有任何關聯,故而只需要配備一個 IT 支持人員進行資源方面的管理:分配機器,報修,初始化系統,分配 IP 地址等。各個項目的運行環境、數據庫、開發環境等都由具體項目的開發人員手動完成。 環境出問題怎么辦?很簡單,涼拌——重裝系統。實際的運行效果不錯。

  1. 4)自動化部署

  由于 Hudson 這樣的持續集成環境提供了自動編譯(定時或觸發式)的功能,而且可以在編譯過程中提供了一些擴展點,因此通過提供一個部署用的腳本,就可以非常容易實現簡單的自動化部署。

  毫無疑問,持續集成就是敏捷的魔法藥,它見效快、副作用小、業界的爭論少。每每運用在混亂的項目中時,幾周內項目就開始持續的產出經過測試的功能。對于獨立項目,以持續集成為中心的持續部署絕對是不二選擇。

  但是,我們有沒有想過,這會是一個自動化部署的通用解決方案嗎?持續集成應該位于持續交付的中心嗎?

  二、困境

  回到我們的故事:項目A上線兩年后,運營業績不錯,投資人第一輪注資后,Richard 的公司進行了擴張,他們對項目進行了重構,而且隨著用戶數量的增長,公司分別在美國、英國和日本等地建立了運營中心,并且對亞洲市場進行的定制功能開發(項目A+),接下來,公司又投入開發了團購系統(項目B)。在獲得了新一輪投資后,各條本來比較簡單的業務和功能線上越來越復雜,需要不斷地細分,于是公司再度擴張(開發人員達到了 300 人,國內 200 多人,而運維團隊主要在美國),隨后又為項目 A/A+ 的高級用戶開發了問答系統(項目C)。目前,他們正準備開發手機系統。 看看下面的圖,公司增長的過程中,整個項目環境也變得復雜。(注意,這里是一種邏輯結構,而在物理層面項目B和項目A的生產環境可能部署在相同的機器上)。

  同時,原本單一的項目軟件結構隨著業務系統的增加也不再簡單:

  而軟件間的版本依賴使這個問題變得更為復雜:

  現在,Richard 的公司已經不再是一條快樂的小魚,而是漸漸成為一直龐大的巨獸。雖然只有四個產品,但公司卻要支持幾百臺開發機,幾十臺生產服務器,還有對應的測試環境,數據庫服務器,以及幾十個開發小組,和一大堆的內部項目。我們盡可以使用持續集成來為我們完成自動化部署。但,當我們為各個項目建立起持續集成環境后,它能滿足我們對于持續部署的要求嗎?我們前期的工作可以簡化我們今后項目的持續交付的工作的難度嗎?它需要我們為之建立一個龐大的運維團隊,還是可以讓我們能節省下每一毛錢來投入到真正的業務價值中去?

  讓我們先來看看復雜的項目環境中的幾個場景

  場景1:環境升級

  項目A和項目B都依賴于 Web 容器,公司決定升級 Web 容器版本,而公司要升級的機器有上百臺,依賴人肉升級已不現實,維護團隊因此針對各種軟件開發了相應的自動化腳本,但當新的軟件出現時,必須要開發新的腳本。而且當同時升級若干環境軟件時,則難度隨之增大,手工調度的方式極易出錯,當升級失敗時仍需要大量人工處理。由于存在大量升級腳本,有一定的維護成本。

  場景2:依賴于環境的軟件升級與回滾

  針對環境升級,公司為項目A和項目B開發了新的版本。但環境的升級和軟件的升級不是同步進行,出錯的可能性非常大(想一想間接依賴和多重依賴的情況)。當新版本部署到生產系統時,發現問題,需要回滾到之前的版本——所有運行時版本都需要回滾,而且環境也需要同步回滾。幾百臺機器……

  場景3:運行時依賴

  在第一節的方案中,我們將所有的運行時依賴都打包到一起。當項目依賴關系復雜時,這樣產生的包將非常臃腫,潛在地延長了部署的時間(想一想全世界有幾百臺服務器,一個部署計劃需要部署幾百兆文件的情況),而且產生沖突的可能性非常大,而且對于不同類型的項目(Java 和 Ruby 項目)缺乏通用性。2006年左右,Nortel 可是拿 Excel 統計過運行時依賴的,牽涉若干項目組,反復多次,沒有個把月真搞不定。

  場景4:泛濫的部署

  每個項目相關的持續集成環境都需要開發自己的部署腳本,重復投入大,而且各個項目的部署過程不一致,并且對于同一個項目無法同時滿足不同目的部署要求,例如,環境或系統配置參數改變后,無需安裝包,只需做清理和激活的工作。最后,持續集成只是支持了和代碼修改有關的部署。

  場景5:不一致的環境

  簡單項目中,開發環境和運行環境都由開發人員搭建,當公司變大時,系統的運行環境將由運維人員搭建,而開發環境如果由運維人員搭建則工作量太大,由開發人員自己搭建則操作復雜又容易產生不一致的情況。

  場景6:熱切換

  對于某些部署,需要盡量減少服務的停止時間,需要在服務的同時進行部署。

  這些場景只是以持續集成為中心的持續部署在面對大型企業時所遇到的部分問題。大型企業,人多,項目多,機器多,項目環境復雜,部署維護工作繁多。以持續集成為基礎的部署可以解決各個項目的集成問題,卻無法幫助企業應對復雜的項目環境和各種不同的部署要求。究其根本,大型企業中的部署不再是一個簡單的問題,而是一個交付生態圈,基礎設施和環境管理必須要納入考慮之中。要實現真正意義上的持續部署,我們就必須把環境和項目同等對待,通通納入管理之中。同時,部署本身要得到統一。一個好的部署機制,應該是易于建立,易于使用,易于維護。

  三、任脈——環境管理

  什么是環境?

  系統運行所依賴和包含的一切就是其環境:硬件、操作系統,網絡資源(IP地址、域名),服務容器,服務器軟件配置,環境亦是,運行時依賴的命令和包,項目本身的包和配置都是環境的一部分。對于部署而言,廣義上,這些通通應該納入環境管理的范疇,但狹義上,從軟件系統的角度看,一個環境就是其運行需要的軟件及其配置(我們先把操作系統和網絡資源當做基礎設施,其在部署時已處于就位的情況)。因此:

  項目A的生產環境 = 項目A本身的軟件包 + 項目A運行時依賴的軟件包 + 項目A運行時依賴的其它軟件 + 項目A的配置信息

  由于,項目本身的軟件包、項目運行時依賴的軟件包,以及項目運行時依賴的其它軟件在本質上沒有區別——都是軟件,上面的定義可以進一步抽象為:

  環境 = 軟件包 + 配置信息

  在這個定義下,我們就必須將運行環境的軟件解構,并以包的形式導入到公司的整個項目資源庫中,比如 Apache 將作為一個包被導入,而 Apache 依賴的其它包也將依次被導入,并建立起正確的依賴關系。而且,在導入的過程中還必須做些相應的調整,如,環境變量的讀取和設置,必須來自于環境配置模塊,而不要修改系統的環境變量,防止不同環境在系統環境配置上相互影響和依賴。

  再回頭審視我們的示例,項目A的生產環境可以部署在不同的區域,對于各個區域可能有定制化的設定。這就像面向對象中的類,可以通過繼承使子類重用父類的公有屬性和行為并添加自己特有的信息。因此,環境的概念模型如圖:

  通過這樣的關系,我們很容易為示例的復雜環境建立一種簡單的結構,對于項目A:

  這里,環境依然是處于知識層面(Knowledge Level),它并未與具體的基礎設施相關聯。當我們將一個環境“具現化”成一個運行系統時,我們就產生了一個真正的環境實例。在這兩者之間,我們還必須要考慮環境實例的使用目的(開發?測試?……)以及安裝所依賴的其它信息(如機器),因此,我們需要增加一個環境目標來集中這些信息,而且由于不同目標的環境可能會有所差別,因此,環境目標也需要配置的能力。概念模型如圖:

  圖中的環境實例是如何產生的呢?部署一次部署可能會產生一個環境實例。一系列部署將產生對應于環境目標的多個環境實例,除去當前起作用的環境實例外(最新的),其它的是歷史環境實例。通過在歷史環境實例中切換,我們自然而然的就可以使整個環境回滾,因為項目所依賴的一切都已經成為環境中的軟件包,而且環境依賴的包的版本會隨著部署具體確定下來。如此一來,我們便可以給每個環境實例分配一個版本號,再通過環境實例的版本號與軟件包的版本對應起來,從而得知一次部署時應用的具體軟件包,如圖:

  目前的環境管理結構,已經可以解決場景1、2和 5 的問題。那么對于場景2,運行時依賴,環境管理應該如何解決呢?

  細心的朋友,可能已經發現,在環境層面上我們確定了環境依賴的軟件包,這里有兩個隱藏的含義:

  • 環境定義的是對軟件包的運行時依賴
  • 由于環境是一個邏輯上的概念,因此其所用的軟件包也是一個邏輯上的概念(相對于版本控制系統中的軟件包)

  我們也已經知道,在部署時,一個環境實例將具體的確定其依賴的軟件包的版本。某個版本的軟件包最終與代碼庫中的物理的軟件包相關聯。但軟件包是運行時的安裝包,因此,它應該是代碼庫中包編譯的結果。在對代碼庫的包編譯時,既要將結果打上版本保存起來,也好在兩者的版本間建立關系,最后,編譯結果應該是某種既定的安裝包目錄文件結構。

  另外,當環境包含的包比較多時,運行時版本樹會非常大,手動的指定全部的包的版本將是一個非常大的體力勞動,這部分工作也要得到簡化。由此,我們必須

  • 建立邏輯軟件包版本和版本庫中軟件包版本間的關系
  • 為相互依賴的包編譯并打上統一的標簽
  • 簡化運行時包依賴關系的生產
  • 簡化運行時包依賴的指定(可參考 apt-get 和 RubyGem,環境只需指定直接依賴的包,間接依賴的包從運行時依賴樹中自動導入)

  一個可能的簡單結構如下:

  上述討論還沒有涉及操作系統,如果我們的運行機器要支持多個系統,我們又該怎么辦???

  配置信息也是個大問題,大家可以思考

  • 環境配置和應用配置如何區分?
  • 如何簡化環境配置工作?
  • 如何使環境配置的效果只對具體環境有效,而不會泄露到環境外部?

  再者,

  • 如何使應用支持多運行目標?
  • 環境管理如何能方便開發環境的調試?
  • 要如何簡化版本的選擇?
  • 在多個包有編譯和運行時依賴時,編譯時如何檢查以減少引入兼容性問題的風險?

  這些都留待大家思考。

  四、督脈——部署系統

  《持續集成》和《持續交付》中都對部署有詳細的討論,不在贅述。在我看來,部署其就是按照其目的執行一系列步驟將環境置于其目的所指向的狀態中。我們一會再回過頭來看這段文縐縐的話,先看看第一部分持續集成的環境下,我們部署的步驟可能會是下面這個樣子:

  1. 登陸目標機(ssh)
  2. 停止服務
  3. 清理環境
  4. 準備安裝環境(創建文件夾等)
  5. 安裝項目包(rsync,解壓,權限設置等)
  6. 配置環境變量
  7. 啟動服務
  8. ……

  而在第二部分的情景4中,我們看到如果對不同的持續集成環境建立不同的部署腳本和環境維護腳本,這部署過程的維護會非常繁瑣。基于第三部分的環境管理,我們可以將部署過程抽象為:

  現在回到開頭那個文縐縐的描述:部署其就是按照其目的執行一系列步驟將環境置于其目的所指向的狀態中

  由于我們已經將部署作為環境管理的一部分,而環境又是對外提供服務的最小實體,因此,對環境的部署就是要根據部署的類型,在環境上按一定的步驟執行一系列操作,從而使環境置于部署類型所要的狀態,這個過程中可能會生成對應的環境實例。舉例來說,我們可能會修改環境相關的一些配置,然后重啟環境,顯然,這種情況下不需要下載安裝軟件包(沒有改變),因此也就不需要生成環境實例。

  對于標準的部署——安裝軟件包并啟動環境,可能的步驟將會是:

  1. 選擇將要部署的軟件包的版本
  2. 生成新的環境實例(確定環境實例的版本和其依賴包的版本,確定環境配置等)
  3. 清理和準備目標機環境
  4. 下載包
  5. 設置環境配置
  6. 環境實例切換
  7. 生成部署報告
  8. ……

  好,部署系統和環境管理各就各位,我們可以將各個項目環境納入我們的環境管理之中,甚至是持續集成環境本身。再補充一句,要讓部署系統和環境管理能很好的發揮作用,我們即需要一個簡單一致的 UI 界面(為開發人員),也需要提供一個清晰明了的服務接口(供外部系統調用,如持續部署系統)。對于與環境管理相關的機器狀態管理,網絡資源的配置等等,本文不再涉及,大家可以自己思考。環境管理的實現、編譯系統的改造以及持續部署的具體實現,另作文章探討。

  就技術而言(不考慮圍繞持續部署的過程實踐),環境管理、部署系統以及我們沒有提及的編譯系統改造才是生產線的真正引擎,持續部署不過是水到渠成的傳送帶而已。

  五、沒完

  打通了任督二脈后,事還還沒有完,還有很多細節上的問題。你想,這個工具實在是太好用了,于是公司里成百上千的工程師們都在使用這個自動化部署系統,我們又會面對很多很多問題:

  • 部署系統的性能問題。幾百號人不停地在把他們的軟件部署到自己的機器上,部署到測試環境,部署到生產環境,一天之內一個人可能會要部署N次,回滾N次,不但有大量部署請求,還有大量的文件在網絡上傳輸。你得想想這套部署系統如何解決這些性能問題,還得考慮未來更大規模的性能水平擴展問題。
  • 目標機環境的管理。在目標運行機上需要解決幾個問題:1)兩個環境間如果有一些一樣的包,那就沒有必要再下載了,這樣可以節約時間。2)每次部署都需要把老的部署環境給保留下來,這樣方便在新舊環境下的切換。這兩點對于在生產環境下部署非常關鍵。(這需要環境內所有軟件的綠色安裝才能更容易達到這個目標,因些,Unix/Linux 會比 Windows 更容易做到這點)
  • 部署一致性事務問題。有時候,我們需要同時部署若干臺服務器,比如:包A到機器MA,包B到機器MB,包C到機器MC,……(Web Service 的 SOA 架構),這些包之間有運行依賴性和兼容性問題,要么一次性全部完成,要么就全部失敗。回滾也是一樣的,這是一個部署事務或部署一致性的問題。如何解決呢?
  • 部署環境的版本控制問題。前面說過,我們的一個環境就會和若干個包的版本耦合,環境必須管理要部署的包的版本。于是,當你的部署越來越多的時候,各個環境的包的版本開始出現混亂,各種依賴間的版本也會出現不統一的情況,也就是說,就算你有這樣的一個工具,在一個高速開發的環境下,我們的部署環境的管理還是會出現很多混亂的情況,需要你不斷地統一大家的開發、測試環境。
  • 部署計劃。我們可能會有很多部署計劃,比如:設定定時部署,提升或降低部署優先級,部署事務定義,部署策略(如:先部署 10% 的機器,如果沒有問題,再把剩下的系統部署了),熱切計劃和策略…… 等等 ,等等 。
  • 部署的監控和維護。任何軟件和系統都會有這樣的問題,當規模上去了以后,我們的自動化部署系統的監控和維護的復雜度并不亞于一個大型的互聯網應用。

  這樣的問題會有很多,基本上來說,這樣一個持續集成持續部署的自動化系統并不是那么簡單的事,其開發工作量和一個標準的大型互聯網業務系統沒什么兩樣

  六、總結

  這里只談一點自己的看法,從傳統的持續集成到面向大型軟件的持續部署,我們將系統所依賴的軟件環境和軟件包抽象為一致的實體納入到管理之中,并將運維人員的工作真正的分攤到開發人員身上。而云計算的出現,使得計算機本身也可以自動化的創建和回收,這樣環境管理的范疇將進一步擴充。相應的,部署的能力和靈活性也是一次質的飛躍,將再一次減輕運維人員的工作壓力。

  說了這么多廢話,總結一下自己的觀點,對于向大型軟件企業推銷基于持續集成的持續部署(交付)的哥們:

  • 你就是在耍流氓,如果你不解決環境管理!!!
  • 你就是在耍流氓,如果你不建立部署系統!!!
  • 你就是在耍流氓,如果你不擴展編譯系統!!!
  • 你就是在耍流氓,如果你只是推銷小團隊的實踐而不考慮改造大環境!!!
  • 你就是個流氓,如果你只是不斷地告訴別人怎么做,自己卻從來不動手寫一個測試或建立一個持續集成環境!!!

  最后,用 Linus 最經典的話來結束本文——“ Talk is Cheap, Show me the Code!”

  (注:本文由@常新居士完成初稿,@馬基雅弗利 做了一些編輯,主要寫了第五節“沒完”

4
0
 
標簽:部署
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()