Project Eru

豆瓣時期

我在豆瓣工作的時候,主要是寫 Douban App Engine 。大體上它和 GAE 類似,有自己的 SDK 和服務端的 Runtime。因為是對內使用,所以在 SDK 和 Runtime 實現細節上,我們并沒有像 GAE 那樣做太多的 Mock 來屏蔽一些系統層面的 API(比如重寫 OS庫等)。對于一家大部分都是使用 Python 的公司而言,我們只做了 Python 的 SDK 和 Runtime,我們基于 Virtualenv 這個工具做了運行時的隔離,使得 App 之間是獨立分割的。但是在使用過程中,我們發現有些運行時的隔離做得并不是很干凈,比如說我們自己在 Runtime 使用了 werkzeug 這個庫來實現一些控制邏輯,然后疊加應用自身 Runtime 的時候可能因為依賴 Flask 因此也安裝了另一個 werkzeug 這個庫,那么到底用哪個版本的就成了我們頭疼的一個問題了。

一開始我們考慮修改 CPython 來做這件事,包括一些 sys.path 的黑魔法,但是發現成本太高,同時要小心翼翼的處理依賴和路徑關系,后面就放棄使用這種方法了,采用分割依賴來最小化影響,盡量使得 Runtime 層疊交集最小。

Runtime 演進

進入 2013 年之后,Docker 在 3 月默默的發布了第一個版本,我們開始關注起來。緊接著我就離職出發去橫穿亞洲大陸了,一路上我一直對 Docker 持續關注并思考如何通過它來做一個或者改造一個類似 DAE 一樣的 PaaS,直到我回國。機緣巧合之下,我加入到芒果TV隸屬于平臺部門,有了環境便開始嘗試我在路上時產生的這些想法。

芒果TV 的 Nebulium Engine

加入芒果TV之后,一開始我實現了類似于 DAE 架構的一個新的 PaaS —— Nebulium Engine(a.k.a NBE),只不過運行時完全用 Docker 來隔離,控制層移到了 Container 之外。除此之外,整體架構上和 DAE 并未有太多的差別。另外由于這邊并沒有一個大一統的強勢語言,所以我們把 Runtime 的控制權完全交給了業務方。綜合大半年線上運行結果來看,在資源管理和工作流整合上面,其實 NBE 做得并不是很好。原因有很多,一方面是公司基礎設施沒有豆瓣那么完善,另外一方面還是因為語言五花八門,完全放開 Runtime 控制層在對資源競爭和預估這兩方面平臺層面就完全沒法做了,而恰恰這兩方面對于業務而言非常重要。

第一代 NBE 架構,Dot(Master) 是單點,這也是問題所在

于是在 2014 年年底重新回顧關于 Borg 和 Omega 的文章之后,我們開始了第二代目 NBE —— Project Eru 的開發。這一次我們基本完全拋棄了以前的設計思路,不再是實現一個 PaaS,而是一個類似于 Borg 一樣的服務器編排和調度的平臺。

Project Eru

有了第一代 NBE 的開發經驗,我們明顯開發速度快了很多,第二周的時候就已經有了一個大體上面用的 demo。到目前為止,Eru 平臺可以混編 Offline 和 Online 的服務(binary/script),對于資源尤其是 CPU 資源實現了自由維度(0.1,0.01,0.001等)的彈性分配,使用 Redis 作為數據總線對外進行消息發布,動態感知集群所有的 Containers 狀態并監控其各項數據等?;?Docker 的 Images 特性我們很好的跟 Git 給結合起來,實現了 Github Workflow 的自動化 Build/Test 流程,統一了線上操作,同時也使得線上的各語言運行時都不會有“污染”的問題,并且能很快速的進行部署/擴容/縮容。

Eru 架構,可以看到 Eru-Core 已經實現無狀態化了

業務層方面在邏輯上我們使用了類似于 Kubernetes 的 Pod 來描述一組資源,使得 Eru 有了 Container 的組資源控制的能力。但是和 Kubernetes 不同的是,我們 Pod 僅僅是邏輯上的隔離,主要用于業務的區分,而實際的隔離則基于我們的網絡層。通過標準化的 App.yaml 我們統一 Dockerfile 的生成,通用化的 Entrypoint 則滿足了業務一份代碼多個角色的復用和切換,使得任何業務幾乎都可以完全無痛的遷移上來。

同時這個項目上我們放棄了以前考慮的完整閉環設計。之前實現的 NBE 第一代打通了項目整個生命周期的每一個環節,但實際上落地起來困難重重,并且使得 Dot(Master)的狀態太重沒法 Scale Out,因此是它是單點部署,可靠性上會糟糕一些。所以 Eru 中每一個 Core 都是一個完整的無狀態的邏輯核心,使得其可以 Scale Out 的同時可靠性上也比 NBE 第一代要好健壯得多。任何業務可以根據自身業務特性,通過監控自身數據,訂閱 Eru 廣播,調用 Eru-Core 的 API ,實現復雜的自定義的部署擴容等操作。

細節

Eru 主要分 Core 和 Agent 兩個部分。Agent 和 Core 并沒有很強的耦合,通過 Redis 來交互信息(依賴于我們自己的 Redis Cluster 集群技術),主要用來匯報本機 Containers 情況和做一些系統層面的操作(比如增加減少 veth)。Core 則是剛才所說無狀態的邏輯核心,控制所有的 Docker Daemon 并且和 Agent 進行控制上的交互。

容器內存儲上,我們目前大部分使用了 Devicemapper 小部分是 Overlay,因此我們有的 Docker Host 上使用了內核 3.19 的內核,并外掛了一個 MooseFS 作為容器間數據共享的卷??紤]到 Docker 本身大部分時間是版本越新越靠譜(1.4是個悲劇),因此基本上我們使用的都是最新版的 Docker。網絡方面對比了若干個解決方案之后,在隧道類(Weave/OVS等)和路由類(MacVLAN/Calico等)中我們選擇了后者中的 MacVLAN。

網絡方案對比

網絡

相比于 Route 方案,Tunnel 方案靈活度會更高,但是會帶來兩個問題:

  1. 性能,比如 Weave,通過 UDP 封裝數據包然后廣播到其他跑著 Weaver 的 Host,封包解包的過程就會帶來一些開銷。另外大多數 OVS 方案性能其實都不太樂觀,之前和某公司工程師交流過,大體上會影響 20%~30% 左右的吞吐性能。
  2. Debug 困難。Tunnel 的靈活是構建在 Host 間隧道上的,物理網絡的影響其實還沒那么大,但是帶來一個弊端就是如果現在出了問題,我怎樣才能快速的定位是物理鏈路還是隧道本身自己的問題。

而 Route 方案也會有自己的問題:

  1. Hook,Route 方案需要 Container Host 上有高權限進程去 Hook 系統 API 做一些事情。
  2. 依賴于物理鏈路,因此在公有云上開辟新子網做 Private SDN 使得同類 Containers 二層隔離就不可能了。
  3. 如果是基于 BGP 的 Calico,那么生效時間差也可能帶來 Container 應用同步上的一些問題。

網絡之所以選擇 MacVLAN 主要是考慮到我們組人少事多,隧道類方案規模大了之后 Debug 始終是一件比較麻煩的事情,路由方案中 MacVLAN 從理解上和邏輯上是最簡單的一個方案。

使用這種方案后,我們可以很容易在二層做 QoS,按照 IP 控流等,這樣避免了使用 tc(或者修改內核加強 tc)去做這么一件事件,畢竟改了內核你總得維護對吧。因為是完全獨立的網絡棧,性能上也比 Weave 等方案表現得好太多,當然還有二層隔離帶來的安全性。

某種意義上 MacVLAN 對 Container 耦合最小,但是同時對物理鏈路耦合最大。在混合云上,無論是 AWS 也好還是青云亦或者微軟的 Azure,對二層隔離的親和度不高,主要表現在不支持自定義子網上。因此選取這個方案后,在混合云上是沒法用的。所以目前我們也支持使用 Host 模式,使得容器可以直接在云上部署,不過這樣一來在云上靈活度就沒那么高了。

存儲

容器內存儲我們目前對這個需求不是太高,小部分選取 Overlay 主要是為了我們 Redis Cluster 集群方案上 Eru 之后 Redis 的 AOF 模式需要,目前來看情況良好。在 Devicemapper 和 Overlay 的性能對比上,大量小文件持續寫 Overlay 的性能要高不少。

對于我們現有的 Redis Cluster 集群,我們采用內存分割的方式部署 Container。一個 Container 內部的 Redis 限制在 Host 總內存數/ Container 數這么大。舉個栗子,我們給 Redis 的 CPU 分配為 0.5 個,一臺機器 24 Core 可以部署 48 個 Container,而我們的 Host 申請下來的一般只有 64G,因此基本上就是 1G 左右一個 Redis Container 了。

這樣會有2個好處:

  1. AOF 卡頓問題得到緩解。
  2. 數據量或者文件碎片量遠遠達不到容器內存儲的性能上限,意外情況可控。
20G 碎片文件測試

Scale

擴容和縮容,我們更加希望是業務方去定制這么組件去做。我們所有的容器基礎監控數據均存儲在了 influxdb 上面,雖然現在來看它不是蠻靠譜(研究 Open-falcon 中)。業務方也可以寫入其自定義數據到任何其他服務中,所以實現上業務方可以通過讀取這些數據(基礎容器監控數據和業務方自己數據),判斷并決策,然后調取 Core 的 API 去做相應的事情即可,因此平臺這邊并不需要熟悉業務特性,對業務擴容縮容也更加彈性一些。

誰關心,誰做

資源分配和集群調度

資源分配我們采取的是 CPU 為主,MEM 半人工審核機制。磁盤 IO 暫時沒有加入到 Eru 豪華午間套餐,而流量控制交給了二層控制器。之所以這么選擇主要是因為考慮到一個機房建設成本的時候,CPU 的成本是比較高的,因此以 CPU 為主要調度維度。在和騰訊的講師聊過之后,關于 CPU 的利用率上我們也實現了掰開幾分用這么個需求,當然,這也是可以設置的,因此我們不會局限于 0.1 這個粒度,0.01或者 0.5 都是可以的,并且是 Pod 的自身屬性。內存方面的話,我們并沒有實現 softlimit subsystem 在內核中,主要還是通過數據判斷 Host 內存余量和在上面 Container 內存使用量/申明量對比來做旁路 OOM Kill。

以 CPU 為主維度的來調度上,我們把應用申請 CPU 的數目計算為兩類,一類稱之為獨占核,一類為碎片核。一個 Container 有且僅有一個碎片核,比如申請為 3.2 個 CPU 的話(假定一個 Cpu 分為 10 份),我們會通過 CpuSet 參數設定 4 個核給這個 Container,然后統一設定 CpuShare 為 1024*2。其中一個核會跟其他 5個 Container 共享,實現 Cpu 資源的彈性。

目前我們暫不考慮容器均勻部署這么個需求,因為我們對應的都是一次調度幾臺甚至幾十臺機器的情況,單點問題并不嚴重。

所以在應用上線時,會經過這些步驟:

  1. 申報內存最大使用量和單個 Container CPU 需求。比如 1G 1.5個CPU。
  2. 請求在哪個 Pod 上進行上線。
  3. Eru 計算 Pod 中剩余 CPU 資源是否足夠這一次上線的請求數量。
  4. 足夠的話開始鎖定 CPU 資源,調度 Host 上的 Docker Daemon 開始部署。

另外我們加入了一個 Public Server 的機制,不對機器的 CPU 等資源做綁定,只從宏觀的 Host 資源方面做監控和限制。使得 Eru 本身可以對服務進行降級操作,實際上我們用這種 Host 現在跑了一部分單元測試的工作。

服務發現和安全

對于服務發現和安全控制,我們把控制權交給了業務方和運維部門。一般情況下同類 Container 將會在同一個子網之中(就是依靠 SDN 的網絡二層隔離,一組 Container 理論上都會在一個或者多個同類子網中),調用者接入子網即可調用這些 Container。同時我們也把防火墻策略放到了二層上,保證其入口流量安全。因而整體上,對于業務部門而言,服務基本上說一個完整的黑箱(組件),他們并不需要關心服務的部署細節和分布情況,他們看到的是一組 IP (當然使用內網 DNS 的話會更加透明),同一子網內才有訪問權限,直接調用就完了。我們認為一個自建子網內部是安全的。

另外我們基于 Dnscache 和 Skydns 構建了可以實時生效的內網 DNS 體系,分別部署在了我們現有的三個機房里面。業務方可以自行定義域名用來描述這個服務(其實是一組 Container),完全不需要關心服務背后的物理鏈路物理機器等,實現了線上的大和諧。

舉個栗子

目前我們 Redis Cluster 有 400個 instance,10個集群,按照傳統方式部署。每一次業務需求到我們這邊之后我們需要針對業務需求調配服務器,初始化安裝環境,并做 instance 部署的操作。在我們完成 Redis instance Dockerize 之后,Redis Cluster Administrator 只需要調取 API biu 一個最小集群,交付子網入口 IP 即可(或者內網域名)。遇到容量不足則會有對應的 Redis Monitor 來自動調用 Eru API 擴容,如果過于清閑也能非常方便的去縮容?,F在已經實現了秒級可靠的 Redis 服務響應和支撐。

此外我們打算上線的另一個服務也打算基于這一套平臺來解決自動擴容問題。通過 Eru 的 Broadcasting 機制結合 Openresty 的 lua 腳本動態的更新服務的 Upstream 列表,從而使得我們這樣平時 500 QPS 峰值 150K QPS 的業務不再需要預熱和準備工作,實現了無人值守。

總結

我們整個 Eru 項目設計思路都是以組合為主,依托于我們現有的 Redis 解決方案,我們通過“消息”把各個組件串了起來,從而使得整個平臺的擴展性和自由度達到我們的需求。除了一些特定的方法,比如構建 Image,其他的諸如構建 Dockerfile,如何啟動應用等,我們均不做強一致性的范式去規范業務方/服務方怎么去做,當然這和我們公司本身體系架構有關,主要還是為了減少落地成本。畢竟不是每個公司業務線都有能力和眼界能接受和跟上。

最后我們現在主要在搞 Redis instance Dockerize 這么一件事,又在嘗試把大數據組 Yarn task executor docker 化。在這個過程中我們搞定了 sysctl 的參數生效,容器內權限管理等問題,那又是另外一個故事了……我們計劃今年年末之前,業務/服務/離線計算 3個方向,都會開始通過 Eru 這個項目構建的平臺開始 Docker 化調度和部署,并對基于此實現一個 PaaS。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容