Yelp天天要運行數百萬個測試,確保開發人員提交的代碼不會對已有的功能形成破壞。如此巨大規模的測試,他們是怎麼作到的呢?如下內容翻譯自 Yelp 的技術博客,並已得到翻譯受權,查看原文 How Yelp Runs Millions of Tests Every Day 。html
開發速度對於一個公司的成敗來講是相當重要的。咱們老是經過減小測試、部署和監控變動的時間來提高開發效率。爲了讓開發者可以安全地提交代碼,咱們天天經過內部的分佈式系統 Seagull 運行了超過數百萬個測試。python
Seagull 是什麼?git
Seagull 是一個具有容錯能力和彈性的分佈式系統,咱們用它來並行執行咱們的測試套件。咱們使用了以下技術來構建 Seagull 。算法
挑戰docker
在將咱們的單體 Web 應用 yelp-main 的新代碼部署到生產環境以前,Yelp 的開發人員針對 yelp-main 的特定版本運行了整個測試套件。開發人員經過觸發 seagull-run 做業來運行測試,這個做業將會在咱們的集羣上安排調度以便運行測試用例。這裏須要考慮兩方面的因素。apache
咱們所面臨的挑戰是如何在分鐘級別運行每個 seagull-run 做業,而不是按天來運行,同時還能保持較低的成本。編程
Seagull 的工做原理api
首先,開發人員在控制檯觸發 seagull-run,它會啓動一個 Jenkins 做業,用於編譯代碼,並生成測試清單。這些測試清單被組合在一塊兒,傳遞給一個調度器,調度器將會在 Seagull 集羣上執行測試。最後,測試結果被保存到 Elasticsearch 和 S3 上。安全
1.一個開發人員爲某個版本的代碼(基於 git 某個分支的 SHA 值)觸發了一個 seagull-job,咱們假設 git 分支的名字叫做 test_branch。服務器
2.爲 test_branch 生成代碼包和測試清單,並上傳到 S3 上。
3.Bin Packer 獲取測試清單和測試歷史時間元數據,用於建立多個包含了測試用例的 bundle。如何進行有效的 bundle 實際上是一個裝箱問題,我麼使用了以下兩種算法來解決這個問題。至於使用哪種算法,由開發人員傳給 Seagull 的參數來決定。
線性編程(Linear Programming):若是出現了測試依賴,一個測試須要與同一個 bundle 裏的另外一個測試一塊兒運行。對於這種狀況,咱們將會使用線性編程。線性編程方程式的目標函數和約束定義以下。
主要的約束:
咱們使用了 Pulp 來解開這個方程式。
# 目標函數: problem = LpProblem('Minimize bundles', LpMinimize) problem += lpSum([bundle[i] for i in range(max_bundles)]), 'Objective: Minimize bundles' # 其中的一個約束: for i in range(max_bundles): sum_of_test_durations = 0 for test in all_tests: sum_of_test_durations += test_bundle[test, i] * test_durations[test] problem += (sum_of_test_durations) <= bundle_max_duration * bundle[i], ''
在這裏,bundle 和 test_bundle 是 LpVariable 類型,max_bundles 和 bundle_max_duration 是整數。
通常狀況下,咱們會在線性編程約束裏考慮測試用例的 setup 和 teardown 時長,不過爲了簡單起見,咱們在這裏把它們忽略了。
4.在 Jenkins 服務器上啓動一個調度器進程,它將會獲取 bundle,而後啓動一個 mesos 框架。咱們爲每個 seagull-run 建立一個新的調度器。每次運行會生成300多個 bundle,每一個 bundle 大概須要 10 分鐘的運行時間。調度器爲每個 bundle 建立了一個 mesos 執行器,並被安排在 Seagull 集羣上執行,只要 Mesos Master 可以提供可用的資源。
5.在執行器被安排到集羣上以後,執行器內部將執行以下幾個步驟。
每一個執行器啓動一個沙箱,並從 S3 上下載軟件包(它們是在第二步時上傳到 S3 上的)。測試服務所依賴的 Docker 鏡像被下載下來,用於啓動 docker 容器(也就是服務)。在全部的容器都運行起來以後,開始執行測試。最後,測試結果和元數據被保存到 Elasticsearch(ES)和 S3 上。咱們使用了內部的代理服務 Apollo 將數據寫到 ES 上。
若是你處在一個分佈式環境裏,那麼遭遇主機崩潰是一件不可避免的事情。不過,Seagull 具備容錯能力。
例如,假設一個調度器須要調度兩個 bundle。Mesos 將代理(A1)的資源分配給調度器。假設調度器認爲已經分配到足夠的資源,那麼兩個 bundle 就會被安排在 A1 上。A1 由於某些緣由發生崩潰,那麼 Mesos 會讓調度器知道 A1 已經崩潰了。調度器的任務管理器決定進行重試,或者直接取消任務。若是進行了重試,當 Mesos 提供了足夠的資源時(好比 A2),那麼 bundle 就會從新被安排執行。若是任務被取消,調度器會將這些 bundle 的測試用例標記爲未執行。
6.Seagull UI 經過 Apollo 從 ES 上獲取測試結果,並將它們加載到一個 UI 上,讓開發人員能夠看到結果。若是測試經過,就能夠進行部署!
咱們所談論的規模是多大?
咱們天天有 300 多個 seagull-run,高峯期每小時有 30 到 40 個。它們爲此天天啓動超過 200 萬個 Docker 鏡像。爲了應付這些場景,咱們的 Seagull 集羣在高峯期須要差很少 1 萬個 CPU 核心。
這種規模所帶來的挑戰
爲了保持測試套件的及時性,特別是在高峯時期,咱們須要確保 Seagull 集羣裏有數百個可用的實例。咱們曾經使用過AWS ASG 和 AWS On-Demand 實例,不過它們對於咱們來講太昂貴了。
爲了下降成本,咱們開始使用一個叫做 FleetMiser 的內部工具來維護 Seagull 集羣。FleetMiser 是一個自動擴展引擎,咱們用它基於一些信號來擴展集羣,好比當前集羣的使用狀況、管道里運行的工做負荷數量,等等。它有兩個主要的組件。
自動伸縮:幾周前的容量數據
FleetMiser 爲咱們節省了 80% 的集羣成本。而在那以前,咱們的集羣部署在 AWS On-Demand 實例上,沒法進行自動伸縮。
咱們已經達成了什麼樣的目標?
Seagull 將測試結果的時間從 2 天下降到 30 分鐘,並且減小了大量的運行成本。咱們的開發人員可以自信地提交代碼,無需等待數個小時甚至數天來驗證他們提交的變動沒有形成任何破壞。