從多租戶隔離到高可用,談DaoShip微服務架構演進
本文根據DCOS聯盟第3期線上分享整理而成
講師介紹

薑衝
DaoCloud高級軟件工程師
-
Docker Contributor,負責公有雲構建服務、DaoShip的設計與研發。
-
對微服務架構設計與實現有著豐富的理論與實踐經驗。
大綱:
-
正確構建鏡像的目標和所需資源,以及如何規劃和構建服務;
-
基於優良的微服務架構設計及網絡層優化,為數十萬用戶的服務使用提供穩定高速的構建能力;
-
不同運營需求下的技術架構演進;
-
微服務帶給客戶的價值。
DaoShip 作為 DaoCloud Service 的一個基礎組件,提供了利用 Docker 技術實現的代碼構建、代碼測試和持續集成功能,是基於雲端的的 DevOps 工具,因為多租戶安全隔離和高可用性的需要,從早期就采取了今天看來成為潮流的微服務設計。
這裏應該很多人用過 jenkins 或者 travis ci 等持續集成服務。
無論何種構建服務或者集成服務,源代碼是必不可少的,都會對接各種源碼托管服務。針對國內用戶,我們需要支持國外的 GitHub、Bitbucket、國內的 Coding 和企業內部自建的 GitLab 的源代碼托管服務。我們會在這些代碼倉庫上設置 webhook,這樣用戶一推送代碼,我們就能立刻開始自動化構建。
另外,對於每一次構建,用戶最關心的莫過於結果,是成功還是失敗,以及如果失敗原因是什麼。所以我們還得提供清晰的構建日誌,方便用戶查錯。
內部實現上,由於我們麵對的是眾多不同的用戶,還必須能多租戶隔離,讓不同用戶的任務不會相互影響。容器技術出現前,我們需要起一個個虛擬機,來跑不同的任務,隔離程度最好。但是這樣做,消耗資源大,而且啟動時間長,在主機資源少的情況下,任務會排期長隊,每個任務都需要等待很長時間。
Docker 的出現,使得我們可以秒級啟動,同時資源消耗大幅下降,一台主機可以同時啟動多個構建任務,所有任務全部容器化。
針對以上 3 點,我們總結下構建服務的基本目標:
-
集成代碼托管服務;
-
提供清晰的構建日誌;
-
隔離構建任務。
在初期我們的構建服務就是這樣的:
有如下 3 個特點:
-
單機部署模式,單體應用。
應用太複雜,降低開發速度。因為所有模塊都運行在一個進程中,任何一個模塊中的一個 bug,比如空指針引用,將會弄垮整個進程,有單點故障。並且無法擴展,隻有有限的服務能力。DaoShip API 中使用 docker daemon 做構建的模塊(builder)更新頻繁,但是每次更新,必須把 DaoShip API 整個更新。
-
調用本地 Docker Daemon。
這不是一件好事,構建全在本地,用戶進程會影響 API 的性能。比如用戶構建一個 node 應用,npm install 會分分鍾把內存和 CPU 吃滿。
Docker 的每次更新,都會帶來很多新功能,解決大量 bug。通常我們會評估下,然後將新版本 Docker 引入到我們的構建服務裏,為用戶帶來新的 Docker 功能。然而 DaoShip 內部與 docker daemon 的深度耦合,我們需要引用新的 docker api,修改這部分代碼,然後再經曆完整而漫長的測試,最後才能提心吊膽地上線。整個過程複雜而冗長,還無法保證質量。
-
日誌存本地文件,可能因為主機故障而丟失,或者磁盤爆滿,導致服務中斷。
針對上麵 3 點,我們做了兩件事。
首先日誌使用單獨的分布式文件存儲。其次分離處理構建的模塊 builder,每個 builder 都部署在不同的主機上,直接接受 API 下發的任務。
圖上展示的各個模塊都支持獨立橫向擴展,似乎不會有單點故障。但是由於 API 是使用 Golang Channel 管理構建任務的,沒有健壯的調度器,存在盲目調度的問題,一個 builder 很空閑,而另一個 builder 接受了很多任務可能很忙,導致宕機無響應。而 builder 一停機上麵的任務會全失敗,任務不會被重新調度,這問題嚴重影響用戶體驗。另外我們無法方便有效的更新 builder,由於 builder 幾乎時刻都有任務在跑,我們必須等到夜深人靜的時候,才能去更新,這樣做非常麻煩。
所以為了解決盲調度和錯誤率高的問題,我們加了一個單獨的調度器,做任務的調度。
調度器會把任務扔到任務隊列裏,builder 則根據自身的任務壓力,去從任務隊列中取任務。如果壓力大,會隔很久才會取一個任務,保證發揮機器最大的性能。builder 還會發送心跳,如果某台 builder 失聯或者宕機,其上的任務會被標識為需要重新調度,調度器識別出就會將任務重新扔入任務隊列,等待新的 builder 來取。這樣我們可以頻繁更新,不用擔心 builder 失聯,同時構建錯誤率大幅下降,用戶體驗大幅提升。
隨著產品迭代和體驗提升,用戶量急劇上升。用戶量上來後,我們發現構建集中某幾個時間段,比如下午1點到晚上 8點,都是構建的高峰期,而其它時間段構建很少。如果我們按高峰期來分配 builder 的數量,到低峰期時,資源會過剩,浪費嚴重。相反按低峰期分配 builder 數量,到高峰期,我們的任務會大量阻塞。所以這裏我們還實現了根據 builder 的平均壓力來自動擴展彈性伸縮,按需創建 builder。
另外根據收費計劃,不同的用戶有不同的套餐,比如專業版使用更好的構建機。同時我們的構建也分區為北京 BGP 和國外執行環境。所以我們還得能夠根據用戶的套餐和配置,把任務分配到不同的集群中。我們在調度器上建了一個任務派發器,把不同類型的任務派發到不同的構建集群。
加入健壯的任務派發器和彈性伸縮構建集群後,隨著更快的功能迭代,逐漸形成了今天的架構,如上圖所示。在運營增長和功能迭代的要求下,我們始終堅持微服務化,不斷迭代升級 DaoShip,各個服務之間有明確的界限,職責也非常清晰。在整個架構演進中,微服務化給我們帶來如下 4 點顯而易見的益處:
-
通過分解巨大單體式應用為多個服務方法解決了複雜性問題,服務邊界清晰,組件之間通過 rest api 和消息隊列進行通信。
-
這種架構使得我們的每個服務都可以有專門開發團隊或者人員來開發。即使某個服務的同事離職,也不會影響其他服務的開發,同時由於服務足夠簡單,新的同事可以快速上手掌握。
-
微服務架構模式下每個服務可以獨立部署,每個組件都能單獨快速測試發布。小步迭代,敏捷開發下,現在我們可以做到時刻無感知上線,一個小 bug 修複可以在 10 分鍾內做到從編碼到測試到上線。
-
微服務架構模式使得每個服務可以獨立擴展。在偶爾的構建壓力暴增情況下,我們可以快速擴展 builder,以符合服務需求。
今天我主要分享了 DaoShip的微服務架構是如何演進的,其中的技術細節下次我們再深入探討。
Q&A
Q1:你們微服務分解有什麼經驗嗎?或是有什麼方法嗎?你們是怎麼分解的?
A1:這個問題非常好,相信很多工程師都非常關心。說實話,我們公有雲構建服務這一塊,您已經看到了,由於我們沒有很多的曆史包袱,所以涉及到的服務拆分比較少,屬於服務演進的範疇,少量的服務拆分也是在一開始的架構設計鬆耦合的方式下,成功演進。
Q2:宿主的Docker更新你們有做嗎,如果做,要怎麼在不停機的狀況下做呢?
A2:Docker更新更是一個非常切實際的話題。我們目前的策略是:我們的 builder 是自動去取任務的,所以更新時,會讓builder 停止取任務,然後上麵的任務完成後,我們再更新Docker 。
Q3:你們的微服務主要存在哪個環節?
A3:關於微服務存在於哪些方麵,這個問題。我們的認識是這樣的。我們在鏡像構建這邊,盡管沒有涉及到市麵上的dubbo,zuul,APIgateway等內容,主要是因為本身不是一個複雜的係統,職責較為聚焦,但是整個構建的演進可以認為是具備微服務演進的基本特性,這裏5個服務:API服務,日誌服務,構建服務,調度服務,分發服務,它們的設計於演進都是結合場景,在需求之下,謀變,求突破,達到預期的效果。我個人的觀點是:微服務與代碼行數方麵沒有直接關係。
Q4:Python和node的鏡像基本都要安裝大量的包,IO的占用很大,你們在docker這邊有什麼優化嗎?
A4:“Python和node的鏡像基本都要安裝大量的包,IO的占用很大,你們在docker這邊有什麼優化嗎”。這又是一個實際過程中經常會遇到的問題。首先第一點,我們完完全全尊重用戶編寫的Dockerfile,其次在這基礎上滿足用戶對構建高效,快速的需求。網絡IO方麵,我們采用兩條線,一條國內,一條國外,一經發現用戶構建涉及國外源,即分發至國外的構建機器。磁盤IO方麵,我們全部采用的是SSD。因此,您可以發現,我們首先會從硬件基礎設施層滿足用戶的需求。
Q5:目前這個架構中,還有沒有可改進的地方?
A5:很好的話題。首先第一點,我們的架構肯定會跟著客戶的需求來走,目前,我們這套可以服務20w用戶的構建任務,日構建量達到15k,當前運行非常穩定。第二點,架構改進方麵,我們後續計劃在構建的自學習方麵引進一類新的服務,即如何更好的幫助用戶識別出Dockerfile的軟件源,並實現更加高效快速構建。
原文發布時間為:2016-12-12
本文來自雲棲社區合作夥伴DBAplus
最後更新:2017-05-11 14:54:40