本文根據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