Docker如何做資源隔離,還是不做?怎樣做才能最大化利用服務器資源,避免浪費?
我們看看Netflix是如何做到的。
吵鬧的鄰居(Noisy Neighbors)
我們都曾有過吵鬧的鄰居。無論是在咖啡館,還是穿過公寓的墻壁,它總是具有破壞性。事實證明,在共享空間中保持良好的禮儀不僅對人很重要,對Docker容器也很重要。
當你在云中運行時,你的containers在一個共享空間中;特別是它們共享主機實例的CPU內存層次結構。
由于微處理器速度如此之快,計算機體系結構設計已經發展到在計算單元和主內存之間添加不同級別的緩存,以隱藏將bits帶到CPU的延遲。然而,這里的關鍵觀點是,這些Cache在cpu之間部分共享,這意味著不可能對共同承載的containers進行完美的性能隔離。如果在containers旁邊的核心上運行的containers突然決定從RAM中獲取大量數據,這將不可避免地導致更多的緩存丟失(從而導致潛在的性能下降)。
linux拯救世界(Linux to the rescue)?
傳統上,減輕性能隔離問題一直是操作系統任務調度程序的職責。在Linux中,當前的主流解決方案是CFS(Completely Fair Scheduler)( https://en.wikipedia.org/wiki/Completely_Fair_Scheduler)。它的目標是以"公平"的方式將正在運行的進程分配給CPU的時間片。
CFS得到了廣泛的應用,因此經過了良好的測試,世界各地的Linux機器運行起來都具有合理的性能。那么,為什么要攪亂它呢?事實證明,對于Netflix的大多數用例來說,它的性能遠遠不是最優的。Titus(https://netflix.github.io/titus/)是Netflix的集裝箱平臺。每個月,我們在Titus上的數千臺機器上運行數百萬個容器,為數百個內部應用程序和客戶提供服務。這些應用程序的范圍從支持面向客戶的視頻流服務的關鍵低延遲服務,到用于編碼或機器學習的批處理作業。維護這些不同應用程序之間的性能隔離對于確保內部和外部客戶的良好體驗至關重要。
通過將一些CPU隔離責任從操作系統轉移到包含組合優化和機器學習的數據驅動解決方案,我們能夠顯著提高這些容器的可預測性和性能。
這個想法(The idea)
CFS非常頻繁地(每隔幾微秒)應用一組啟發式操作,這些啟發式操作封裝了圍繞CPU硬件使用的最佳實踐的一般概念。
相反,如果我們減少干預的頻率(每隔幾秒鐘),但在分配計算資源的過程方面做出更好的數據驅動決策,以最小化配置噪音,結果會怎樣?
減輕CFS性能問題的一種傳統方法是讓應用程序所有者通過使用核心固定或nice值手動協作。然而,通過基于實際使用信息檢測搭配機會,我們可以自動做出更好的全局決策。例如,如果我們預測容器A將很快變得非常CPU密集型,那么也許我們應該在一個不同的NUMA(https://en.wikipedia.org/wiki/Non-uniform_memory_access)套接字上運行它,而容器B對延遲非常敏感。這避免了過多的抖動緩存為B和平衡的壓力,對L3(https://en.wikipedia.org/wiki/Memory_hierarchy)緩存的機器。
通過組合優化優化布局(Optimizing placements through combinatorial optimization)
OS任務調度程序所做的實際上是解決一個資源分配問題:我有X個線程要運行,但只有Y個cpu可用,我如何將線程分配給cpu,以產生并發的假象?
作為一個演示示例,讓我們考慮一個包含16(https://en.wikipedia.org/wiki/Hyper-threading)個超線程的玩具實例。它有8個物理超線程核心,分裂在2個NUMA套接字上。每個超線程與其鄰居共享其L1和L2緩存,并與套接字上的其他7個超線程共享其L3緩存:
如果我們想在4個線程上運行容器A,在這個實例上在2個線程上運行容器B,我們可以看看"壞"和"好"的布局決策是什么樣子的:
第一個位置在直覺上是不好的,因為我們可能通過L1/L2緩存在前2個核心上創建A和B之間的并置噪聲(collocation noise),而通過L3緩存在套接字上創建套接字,同時保留整個套接字為空。第二個位置看起來更好,因為每個CPU都有自己的L1/L2緩存,我們更好地利用了兩個可用的L3緩存。
資源分配問題可以通過數學的一個分支組合優化有效地解決,例如用于航空公司調度或物流問題。
我們將問題表示為一個混合整數程序(MIP)。給定一組K個容器,每個容器在擁有d個線程的實例上請求特定數量的cpu,目標是找到一個大小為M (d, K)的二進制賦值矩陣,以便每個容器獲得它請求的cpu數量。損失函數和約束包含了表示先驗的良好配置決策的各種術語,例如:
· 避免將容器分散到多個NUMA套接字(避免潛在的緩慢的跨套接字內存訪問或頁面遷移)
· 除非需要,否則不要使用超線程(以減少L1/L2抖動)
· 嘗試平衡L3緩存上的壓力(基于對容器硬件使用的潛在測量)
· 不要在不同的位置決策之間做太多的調整
考慮到系統的低延遲和低計算需求(我們當然不希望花費太多的CPU周期來弄清楚容器應該如何使用CPU周期!),我們實際上能在實踐中實現這一點嗎?
實現(Implementation)
我們決定通過Linux cgroups(http://man7.org/linux/man-pages/man7/cgroups.7.html)實現該策略,因為CFS完全支持它們,方法是根據容器到超線程的所需映射修改每個容器的cpuset cgroup。通過這種方式,用戶空間進程定義了一個"圍欄",CFS在其中對每個容器進行操作。實際上,我們消除了CFS啟發式算法對性能隔離的影響,同時保留了它的核心調度功能。
這個用戶空間進程是一個名為Titus - isolation的Titus(https://github.com/Netflix-Skunkworks/titus-isolate)子系統,其工作原理如下。在每個實例上,我們定義三個觸發布局優化的事件:
· add: Titus調度程序為這個實例分配了一個新的容器,需要運行它
· remove:一個正在運行的容器剛剛完成
· rebalance:容器中的CPU使用量可能發生了變化,因此我們應該重新評估我們的位置決策
當最近沒有其他事件觸發位置決策時,我們會定期對重新平衡事件進行排隊。
每次觸發一個放置事件時,Titus -隔離都會查詢一個遠程優化服務(https://en.wikipedia.org/wiki/Turtles_all_the_way_down),從而解決容器到線程的放置問題。
然后,該服務查詢一個本地GBRT(https://en.wikipedia.org/wiki/Gradient_boosting)模型(每隔幾小時對從整個Titus平臺收集的數據進行重新培訓),該模型預測未來10分鐘內每個容器的P95 CPU使用情況(條件分位數回歸)。該模型既包含上下文特性(與容器關聯的元數據:誰啟動了它、圖像、內存和網絡配置、應用程序名稱……),也包含從主機定期從內核CPU會計控制器(https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt)收集的容器的歷史CPU使用量的最后一個小時中提取的時間序列特性。
然后,這些預測被輸入到一個MIP中,該MIP將被動態求解。我們使用cvxpy(https://www.cvxpy.org/)作為一個很好的通用符號前端來表示問題,然后可以將其輸入各種開源或專有的MIP解決程序后端。由于MIPs是np困難的,因此需要采取一些謹慎的措施。我們對求解程序施加了一個艱難的時間預算,以將分支和削減策略驅動到低延遲狀態,并在MIP缺口周圍設置護欄,以控制找到的解決方案的總體質量。
然后,服務將位置決策返回給主機,主機通過修改容器的cpuset來執行該決策。
例如,在任何時候,一個包含64個邏輯CPU的r4.16xlarge可能看起來是這樣的(顏色比例表示CPU使用量):
結果(Results)
該系統的第一個版本帶來了令人驚訝的好結果。我們平均將批處理作業的總體運行時減少了幾個百分點,同時最重要的是減少了作業運行時差異(隔離的一個合理代理),如下所示。在這里,我們看到一個實際的批處理作業運行時分布,有和沒有改進的隔離:
請注意,我們主要是如何使長時間運行的異常值問題消失的。不幸的吵鬧鄰居的右尾巴現在不見了。
在服務業方面,增長更為顯著。一個專門為Netflix流媒體服務的Titus中間件服務在高峰流量時減少了13%的容量(減少了1000多個容器),以滿足相同負載所需的P99延遲SLA!我們還注意到,由于內核在緩存失效邏輯上花費的時間要少得多,因此計算機上的CPU使用率也有了大幅下降。我們的容器現在更容易預測,速度更快,機器的使用也更少了!魚與熊掌不可兼得的情況并不常見。
下一個步驟(Next Steps)
我們對該領域迄今取得的進展感到興奮。我們正從多個方面著手擴展本文提出的解決方案。
我們希望擴展系統以支持CPU超訂閱。我們的大多數用戶都不知道如何正確設置應用程序所需的cpu數量。事實上,這個數字在容器的生命周期內是變化的。由于我們已經預測了容器未來的CPU使用情況,所以我們希望自動檢測和回收未使用的資源。例如,如果我們能夠沿著下圖的各個軸檢測用戶的靈敏度閾值,則可以決定將特定容器自動分配給未充分利用cpu的共享cgroup,從而更好地提高總體隔離和機器利用率。
我們還希望利用內核PMC事件()更直接地優化緩存噪聲。一種可能的方法是使用Amazon最近引入的基于Intel的bare metal實例(https://phoenixnap.com/blog/what-is-bare-metal-hypervisor),該實例允許對性能分析工具進行深度訪問。然后,我們可以將這些信息直接輸入到優化引擎中,從而轉向一種更有監督的學習方法。這需要一個合適的連續隨機化的位置收集無偏反設事實,所以我們可以建立某種干擾模型("什么將容器的性能在下一分鐘,如果我把它的一個線程在同一核心容器B,知道還有C運行在同一個插座嗎?")。