美好的五一假期結束啦,大家玩得怎麼樣?今天小數給大家帶來的是數人雲架構師周偉濤在CSDN做Mesos生态圈系列技術分享的第二期,讨論他在微服務架構方面的經驗心得以及一些具體問題的解決。
Mesos生态圈系列第一期傳送門: 暢談 Mesos 生态圈系列
微服務架構概述
今天是 Mesos 生态圈系列的第二次分享,這次分享的内容是微服務架構軟體的持續傳遞,我首先解釋下标題與 Mesos 生态圈的關系。微服務架構是近幾年讨論比較多或者說比較受歡迎的一種軟體架構風格,但是該架構有一些缺點,譬如運維複雜度提高,傳遞次數頻繁等,我們可以利用 Mesos 配合一些工具搭建持續傳遞平台來彌補這些缺點。這裡我将以具體實踐為例,讨論微服務架構的一些經驗心得,然後着重介紹如何利用 Docker,Jenkins 來解決一些具體問題,實作微服務軟體的持續傳遞。
首先介紹下微服務架構的優勢與劣勢。相較于單體應用來說,微服務架構有這麼幾個優點:
- 易于開發、了解。 由于每個服務隻負責單一功能,開發者可以聚焦于自己負責的幾個服務子產品,對于其他服務,隻需要了解接口即可。當然,單體應用經過良好設計也可以達到這個效果,但是,與單體應用的程序内通信或單機内的程序間通信不同的是,微服務的各服務之間一般采用 RESTfulAPI或者異步消息隊列進行通信,無論 RESTful接口還是異步消息隊列都是開發語言無關的,極易了解的通信方式。
- 全局穩定性提高。由于每個服務負責的功能單一,各服務的資源需求也相對更低。進而可以選擇将服務分散的部署到多台中低配的伺服器上,而不是一台高配的機器上。如果某個機器上的服務故障,譬如說記憶體洩漏,故障隻會影響該機器上的某一個或幾個服務,對全局影響不大。
- 不受限于任何技術棧,極大的提高團隊搭建的速度。這一點對初創公司尤為重要,組建開發團隊對初創公司來說本來就是個頭疼的問題,如何還要求團隊的技術棧一緻,招聘難度可想而知。但是,如果産品架構采用微服務架構,那麼我們可以允許不同的服務子產品采用不同的技術棧,隻需要定義好對外接口即可。
- 局部的修改很容易部署,進而大大的提高了功能的傳遞效率。
說完了微服務架構的優點,我們再來讨論下其缺點或者說複雜的地方:
- 如何确定軟體功能切分的粒度,邊界。太多的微服務子產品會導緻服務間通信成本和運維成本的增加,過猶不及;但是若粒度過大,又違背了微服務的初衷。
- 多種技術棧(譬如 C,Java,Python,Scala 等)我們需要為每種語言準備編譯環境,運作環境等,增加了維護成本。這個可以通過Docker 隔離來解決,我們後面會詳細展開。
- 微服務子產品多了,會導緻全局的上線次數變多,進而需要更複雜的版本管理和 Bug跟蹤等,間接導緻項目管理成本增加。
技術幹貨 | 微服務架構軟體的持續內建與傳遞
持續傳遞
持續內建和傳遞(CI/CD)是一種軟體開發實踐,使用得當,它會極大的提高軟體開發效率并保障軟體開發品質;持續內建和傳遞分為持續內建和持續傳遞兩部分,這裡我們不再具體探讨這兩者的差別,統一按持續傳遞來處理。Jenkins是一個開源項目,它提供了一種易于使用的持續內建系統;除 Jenkins 外,常見的持續內建系統還有:
Travis: https://travis-ci.com/
Codeship: https://codeship.com/
Stridercd: http://stridercd.com/
另外,常見的傳遞方式一般有:
- 源代碼傳遞: 源代碼傳遞需要将源代碼以 tar 包等方式 download 到伺服器,然後在伺服器上借助程式的建構腳本去建構可執行程式,顯然這種方式會經常因伺服器環境差異,建構環境初始化失敗等問題導緻無法建構可執行程式。嚴重依賴于建構腳本的完備程度。
- Linux 标準包傳遞: 将項目的依賴通過 Linux deb 或者 rpm 來管理,由于這種方式更符合 Linux 規範,間接的提高了項目在伺服器上部署的成功率,但是有些時候仍然需要解決包沖突問題。
- 虛拟鏡像傳遞: 虛拟鏡像傳遞指的是我們将項目在虛拟機裡測試成功後直接将該虛拟鏡像部署到伺服器上。顯然,這種方式部署成功率接近100%而且隔離性好。但是随之而來的問題就是虛拟鏡像本身對伺服器資源的消耗。
- docker image 傳遞: docker image 傳遞是虛拟鏡像傳遞的進一步演進,在保證系統隔離的同時,docker image 對伺服器的資源消耗更低。當然,docker 的隔離機制是程序級别的,可能不适合一些強隔離場景。我們團隊目前正在使用這種方式進行傳遞。
上圖(圖檔來自于網絡)展示了圍繞 Docker 鏡像倉庫的持續傳遞流程:
- 首先開發者将代碼推送到代碼倉庫,譬如github
- 代碼倉庫的更新會觸發新的代碼建構,生成新的 docker 鏡像并推送到 docker 鏡像倉庫
- 接下來會基于新的 docker 鏡像進行內建測試
- 測試通過後,docker 鏡像被傳遞到公有或者私有雲上
通過上述持續傳遞的方式傳遞微服務架構的軟體,能夠很好的解決前面提到的第二與第三個問題,即
- 結合 Docker 解決多技術棧的環境維護問題;
- 按微服務子產品傳遞來提高軟體的傳遞效率
- 引入“版本服務”來可視化各微服務的版本資訊
- 引入“ReleaseNote服務”來釋出各微服務的 feature 更新
實踐
微服務架構有多種,數人雲的微服務架構有如下特點:
- RESTAPI 與 消息隊列 結合使用。微服務與外部使用者通過 RESTAPI 通信,内部微服務之間通過消息隊列通信。
- 全部 Docker 傳遞,這解決了多技術棧的環境維護問題。
- 一個微服務對應于一個持續傳遞的 Job,這保證了各服務在傳遞環節無互相依賴,單獨觸發。同時可以利用不同的 Docker 鏡像為不同的 Job 提供相應環境。
- 版本資訊自動更新
- ReleaseNote 自動釋出
- 建構環境 Docker 化,與底層隔離,保證主控端環境的一緻性,降低運維成本
- 利用 Docker-compose 維護本地開發環境,進而實作開發環境與生産環境的邏輯一緻性
技術幹貨 | 微服務架構軟體的持續內建與傳遞
數人雲的架構設計模式如圖所示。使用者通過浏覽器或者直接通過 RESTAPI 與背景通信,背景是一個微服務集合,對外服務的接口一律采用 RESTAPI, 内部服務之間的接口采用消息隊列。各微服務負責維護自身的狀态集,有自己獨立的緩存和 DB (如有必要)。微服務本身盡量無狀态化,以保證橫向擴充能力。
上圖是我們目前采用的持續傳遞架構圖。A, B, C, D 四個 github 代碼庫分别存儲着四個微服務的源代碼,相應的我們為每一個代碼庫建立了獨立的建構作業,代碼更新觸發建構時,建構作業将執行如下的大緻步驟:
- 從 Docker 私有鏡像倉庫拉取相應的建構鏡像
- 從 github 源碼庫拉取相應代碼并挂載到建構容器裡,觸發特定腳本來執行建構
- 若建構成功,立刻從 Docker 私有鏡像倉庫拉取相應的 runtime 鏡像,将建構成功的可執行程式 Docker 化成新一版的微服務鏡像
- 将上述微服務鏡像推送到 Docker 私有鏡像倉庫
若已經設定了自動傳遞
- 則通過 Marathon 的 RESTful API 接口觸發線上微服務的更新
- 更新版本服務裡面對應微服務的版本資訊
- 自動更新 ReleaseNote 服務裡面對應微服務的 ReleaseNote。 開發者在實作了相應功能時已經将對應的 ReleaseNote 放在了代碼庫特定目錄下面(這個後面會詳細提到)
另外,從架構圖中我們可以看出,程式的建構環境和運作環境正在共享同一個 Mesos 資源池,提高了資源使用率。
把 Jenkins 運作在 Mesos 上有如下幾個考慮:
- 把Jenkins運作到 Apache Mesos上,或者說利用 Apache Mesos 向 Jenkins 提供 slave 資源,最主要的目的是利用 Mesos 的彈性資源配置設定來提高資源使用率。
- 術棧的軟體情況下尤其重要,可以極大降低運維成本。
- Marathon會對釋出到它之上的應用程式進行健康檢查,進而在應用程式由于某些原因意外崩潰後自動重新開機該應用。這樣,選擇利用Marathon管理 Jenkins Master 保證了該建構系統的全局高可用。而且,Jenkins Master 本身也通過 Marathon 部署運作在 Mesos 資源池内,進一步實作了資源共享,提高了資源使用率。
關于怎樣将 jenkins 運作在 mesos 上,大家可以參考我以前在csdn釋出的一篇文章http://www.csdn.net/article/2015-06-18/2824998
Docker 承擔了什麼角色
Docker 在整個體系中承擔了如下幾個角色:
各代碼庫的編譯載體: 我們已經提前将各代碼庫的編譯環境制作成了 docker 鏡像
傳遞媒體: 編譯成功的可執行程式将被打包成 docker 鏡像, 鏡像的 tag 對應于程式版本資訊
Runtime 環境: 運作環境已經被打包到 docker 鏡像中了,啟動的 docker 容器将作為微服務的 runtime 環境
資源隔離: docker 本身支援程序級别隔離,已經滿足内部應用需求
為了保證單機開發環境與線上環境的配置/架構一緻,我們在單機開發環境利用 docker-compose 來編排整個微服務環境,以便于調試
版本調試
在實踐微服務架構時,我們碰到了這麼一個問題:各微服務子產品頻繁傳遞,如何确認線上各微服務的版本, 即我們需要對各微服務進行版本控制。
目前團隊疊代出了如下解決方案:傳遞成功後,傳遞 job會截取 docker 鏡像裡相應的 tag(代表着版本資訊),并把該資訊推送到一個 NginxServer 的靜态檔案裡面, 前端頁面通路該靜态檔案來擷取相應微服務的版本資訊。另外,同一個微服務會部署到開發,測試和生産三個環境上,是以基于不同的環境,我們會維護三個不同的靜态檔案 。
ReleaseNote 服務
在多人協作的微服務項目開發中,由于多人頻繁的 merge 代碼,ReleaseNote 的管理也會成為團隊的負擔。我這裡推薦的做法是這樣:
文檔規約:團隊達成一個 agreement: 對重大 feature 或 bug-fix 的送出都需要在目錄 pending-release 裡面建立相應的 markdown 檔案,并将改動添加到裡面。這樣,我們可以控制傳遞 Job 在傳遞時掃描 pending-release 目錄并将其中的文本 merge 到一起生成這次傳遞的 ReleaseNote。
git pre-commit-hook:同時,為了避免團隊成員忘記這個agreement,我們還可以在本地的 git-precommit-hook 中添加相應的掃描提醒。
配置中心服務
目前網絡上關于配置管理的解決方案已經非常成熟,我這裡就不過多解釋了。唯一需要提到的就是, 我們的配置中心服務還沒有完全融入到整個傳遞過程中來,微服務的配置檔案仍然需要手工介入。
持續內建的消息通知
顯然,團隊希望 CI 伺服器在執行了持續內建後能夠及時的将內建結果通知團隊成員, Jenkins 本身是有 irc notification 插件的,但是國内開發者可能使用 IRC 的并不多;微信是小團隊使用比較多的溝通工具,我們可以使用微信公衆号進行消息通知;或者使用國内的 LessChart 等交流工具,它們本身支援 webhook 調用。
開發環境的搭建
微服務架構導緻軟體子產品增多,增加了開發環境搭建的難度,同時也導緻了團隊新成員上手門檻的提高。我們目前是利用 docker-compose 維護本地開發環境來解決這個問題的。它不僅實作了開發環境與生産環境的邏輯一緻性,同時也可以讓相應子產品的開發者聚焦于自己的業務,不必糾結于如何啟停其它微服務。下面是我們的 docker-compose 檔案的一部分。
問題及解決
在具體實踐中,還有如下幾個難題。
MQ 消息格式統一的困境
微服務間的消息傳遞需要根據業務變動而頻繁改動,尤其是前期,這帶來了極大的溝通成本,目前沒有好辦法解決。
微服務的功能粒度切分問題
如何界定各微服務承擔的功能,每個微服務的功能粒度切分,微服務間的邊界定義,這也是我們團隊一直在摸索的問題。
參考連結:
-
http://microservices.io/
2.http://www.infoq.com/cn/news/2015/04/micro-service-architecture