在本文中,OpenAI 的工程師團隊分享了他們在 Kubernetes 集群擴展過程中遇到的各種挑戰和解決方案,以及他們取得的性能和效果。
我們已經將 Kubernetes 集群擴展到 7500 個節點,為大型模型(如 GPT-3、 CLIP 和 DALL·E)創建了可擴展的基礎設施,同時也為快速小規模迭代研究(如 神經語言模型的縮放定律)創建了可擴展的基礎設施。
將單個 Kubernetes 集群擴展到這種規模很少見,但好處是能夠提供一個簡單的基礎架構,使我們的機器學習研究團隊能夠更快地推進并擴展,而無需更改代碼。
自上次發布關于擴展到 2500 個節點的帖子以來,我們繼續擴大基礎設施以滿足研究人員的需求,在此過程中學到了許多的經驗教訓。本文總結了這些經驗教訓,以便 Kubernetes 社區里的其他人也能從中受益,并最后會介紹下我們仍然面臨的問題,我們也將繼續解決這些問題。
我們的工作負載
在深入探討之前,我們著重描述一下我們的工作負載。我們在 Kubernetes 上運行的應用程序和硬件與大家在普通公司遇到的可能相當不同。因此,我們的問題及解決方案可能與你自己的設置匹配,也可能不匹配!
一個大型的機器學習作業跨越許多節點,當它可以訪問每個節點上的所有硬件資源時,運行效率最高。這允許 GPU 直接使用 NVLink 進行交叉通信,或者 GPU 使用 GPUDirect 直接與 NIC 進行通信。因此,對于我們的許多工作負載,單個 Pod 占用整個節點。任何 NUMA、CPU 或 PCIE 資源爭用都不是調度的因素。裝箱或碎片化不是常見的問題。我們目前的集群具有完全的二分帶寬,因此我們也不考慮機架或網絡拓撲。所有這些都意味著,雖然我們有許多節點,但調度程序的負載相對較低。
話雖如此,kube-scheduler 的負載是有波動的。一個新的作業可能由許多數百個 Pod 同時創建組成,然后返回到相對較低的流失率。
我們最大的作業運行 MPI,作業中的所有 Pod 都參與一個單一的 MPI 通信器。如果任何一個參與的 Pod 掛掉,整個作業就會停止,需要重新啟動。作業會定期進行檢查點,當重新啟動時,它會從上一個檢查點恢復。因此,我們認為 Pod 是半有狀態的——被刪掉的 Pod 可以被替換并且工作可以繼續,但這樣做會造成干擾,應該盡量減少發生。
我們并不太依賴 Kubernetes 的負載均衡。我們的 HTTPS 流量非常少,不需要進行 A/B 測試、藍 / 綠或金絲雀部署。Pod 使用 SSH 直接通過 Pod IP 地址與 MPI 進行通信,而不是通過服務端點。服務“發現”是有限的;我們只在作業啟動時進行一次查找,查找哪些 Pod 參與 MPI。
大多數作業與某種形式的 Blob 存儲進行交互。它們通常會直接從 Blob 存儲流式傳輸一些數據集的分片或檢查點,或將其緩存到快速的本地臨時磁盤中。我們有一些 PersistentVolumes,用于那些需要 POSIX 語義的情況,但 Blob 存儲更具可擴展性,而且不需要緩慢的分離 / 附加操作。
最后,我們的工作性質本質上是研究,這意味著工作負載本身是不斷變化的。雖然超級計算團隊努力提供我們認為達到“生產”質量水平的計算基礎架構,但在該集群上運行的應用程序壽命很短,它們的開發人員會快速迭代。因此,隨時可能出現新的使用模式,這些模式會挑戰我們對趨勢和適當權衡的設定。我們需要一個可持續的系統,同時也能讓我們在事情發生變化時快速做出響應。
網 絡
隨著集群內節點和 Pod 數量的增加,我們發現 Flannel 難以滿足所需的吞吐量。因此,我們轉而使用 Azure VMSS 的本地 Pod 網絡技術和相關 CNI 插件來配置 IP。這使我們的 Pod 能夠獲得主機級別的網絡吞吐量。
我們轉而使用別名 IP 地址的另一個原因是,在我們最大的集群中,可能會同時使用約 20 萬個 IP 地址。在測試了基于路由的 Pod 網絡后,我們發現能夠使用的路由數明顯存在限制。
避免封裝會增加底層 SDN 或路由引擎的需求,雖然這使我們的網絡設置變得簡單。添加 VPN 或隧道可以在不需要任何其他適配器的情況下完成。我們不需要擔心由于某部分網絡具有較低的 MTU 而導致的分組分段。網絡策略和流量監控很簡單;沒有關于數據包源和目的地的歧義。
我們使用主機上的 iptables 標記來跟蹤每個 Namespace 和 Pod 的網絡資源使用情況,這使研究人員可以可視化他們的網絡使用模式。特別是,由于我們的許多實驗具有不同的 Internet 和 Pod 內通信模式,因此能夠調查任何瓶頸發生的位置通常是非常有意義的。
可以使用 iptables mangle 規則任意標記符合特定條件的數據包。以下是我們用來檢測流量是內部流量還是 Internet 流量的規則。FORWARD 規則涵蓋了來自 Pod 的流量,而 INPUT 和 OUTPUT 規則涵蓋了主機上的流量:
iptables -t mangle -A INPUT ! -s 10.0.0.0/8 -m comment --comment "iptables-exporter openai traffic=internet-in" iptables -t mangle -A FORWARD ! -s 10.0.0.0/8 -m comment --comment "iptables-exporter openai traffic=internet-in" iptables -t mangle -A OUTPUT ! -d 10.0.0.0/8 -m comment --comment "iptables-exporter openai traffic=internet-out" iptables -t mangle -A FORWARD ! -d 10.0.0.0/8 -m comment --comment "iptables-exporter openai traffic=internet-out"
一旦標記,iptables 將開始計數以跟蹤匹配該規則的字節數和數據包數。你可以使用 iptables 本身來查看這些計數器:
% iptables -t mangle -L -v Chain FORWARD (policy ACCEPT 50M packets, 334G bytes) pkts bytes target prot opt in out source destination .... 1253K 555M all -- any any anywhere !10.0.0.0/8 /* iptables-exporter openai traffic=internet-out */ 1161K 7937M all -- any any !10.0.0.0/8 anywhere /* iptables-exporter openai traffic=internet-in */
我們使用名為 iptables-exporter 的開源 Prometheus 導出器將這些數據追蹤到我們的監控系統中。這是一種簡單的方法,可以跟蹤與各種不同類型的條件匹配的數據包。
我們網絡模型中比較獨特的一點是,我們完全向研究人員公開節點、Pod 和 Service 網絡 CIDR 范圍。我們采用集線器和分支的網絡模型,并使用本機節點和 Pod CIDR 范圍路由該流量。研究人員連接到中心樞紐,然后可以訪問任何一個單獨的集群(分支)。但是這些集群本身無法相互通信。這確保了集群保持隔離、沒有跨集群依賴,可以防止故障隔離中的故障傳播。
我們使用一個“NAT”主機來翻譯從集群外部傳入的服務網絡 CIDR 范圍的流量。這種設置為我們的研究人員提供了很大的靈活性,他們可以選擇各種不同類型的網絡配置進行實驗。
API 服務器
Kubernetes 的 API Server 和 etcd 是保持集群健康運行的關鍵組件,因此我們特別關注這些系統的壓力。我們使用 kube-prometheus 提供的 Grafana 儀表板以及額外的內部儀表板。我們發現,將 HTTP 狀態碼 429(請求太多)和 5xx(服務器錯誤)的速率作為高級信號警報是有用的。
雖然有些人在 kube 內部運行 API 服務器,但我們一直在集群外運行它們。etcd 和 API 服務器都在它們自己的專用節點上運行。我們的最大集群運行 5 個 API 服務器和 5 個 etcd 節點,以分散負載并盡可能減少發生故障后帶來的影響。自從我們在 上一篇博文 中提到的將 Kubernetes 事件拆分到它們自己的 etcd 集群中以來,我們沒有遇到 etcd 的任何值得注意的問題。API 服務器是無狀態的,通常很容易在自我修復的實例組或擴展集中運行。我們尚未嘗試構建任何自我修復 etcd 集群的自動化,因為發生事故非常罕見。
API 服務器可能會占用相當多的內存,并且往往會與集群中的節點數量成線性比例。對于我們有 7500 個節點的集群,我們觀察到每個 API 服務器使用高達 70GB 的堆內存,因此幸運地是,未來這應該仍然在硬件能力范圍之內。
API Servers 受到壓力的主要來源之一就是對 Endpoints 的 WATCH。在整個集群中有一些服務,如“kubelet”和“node-exporter”,其中每個節點都是成員。當一個節點被添加或從集群中刪除時,這個 WATCH 將被觸發。通常,由于每個節點本身都通過 kube-proxy 監視 kubelet 服務,因此這些響應中所需的數量和帶寬將是N2非常大,有時會達到 1GB/s 或更高。Kubernetes 1.17 中推出的 EndpointSlices 大大降低了這種負載,減少達 1000 倍。
總的來說,我們會非常注意隨著集群規模增大而增加的 API Server 請求。我們盡量避免任何 DaemonSets 與 API Server 交互。在需要每個節點監視更改的情況下,引入緩存服務(例如 Datadog Cluster Agent)作為中介,似乎是避免集群范圍瓶頸的良好模式。
隨著集群的增長,我們對集群的實際自動伸縮越來越少。但當一次性自動擴展太多時,我們偶爾會遇到問題。當新節點加入集群時會生成大量請求,一次性添加數百個節點可能會超過 API 服務器容量的負荷。稍微平滑一下這個過程,即使只有幾秒鐘也有助于避免宕機。
時間序列度量與 Prometheus 和 Grafana
我們使用 Prometheus 收集時間序列度量數據,并使用 Grafana 進行圖形、儀表板和警報。我們從 kube-prometheus 部署開始收集了各種各樣的度量數據,并使用了一些良好的儀表板進行可視化。隨著節點數量的不斷增加,我們開始遇到 Prometheus 收集的度量數據數量過多的問題。盡管 kube-prometheus 公開了許多有用的數據,但我們實際上并沒有查看所有的度量數據,一些數據也過于細化,無法有效地進行收集、存儲和查詢。因此,我們使用 Prometheus 規則從被攝入的度量數據中“刪掉”一些數據。
有一段時間,我們遇到了 Prometheus 消耗越來越多的內存問題,最終導致容器崩潰并出現 Out-Of-Memory 錯誤(OOM)。即使為應用程序分配了大量的內存容量,這種情況似乎仍然會發生。更糟糕的是,它在崩潰時會花費很多時間在啟動時回放預寫日志文件,直到它再次可用。最終,我們發現了這些 OOM 的來源是 Grafana 和 Prometheus 之間的交互,其中 Grafana 使用 /api/v1/series API 查詢 {le!=""}(基本上是“給我所有的直方圖度量”)。/api/v1/series 的實現在時間和空間上沒有限制,對于具有大量結果的查詢,這將不斷消耗更多的內存和時間。即使請求者已經放棄并關閉了連接,它也會繼續增長。對于我們來說,內存永遠不夠,而 Prometheus 最終會崩潰。因此,我們修補了 Prometheus,將此 API 包含在上下文中以強制執行超時,從而完全解決了問題。
雖然 Prometheus 崩潰的次數大大減少,但在我們需要重新啟動它的時候,WAL 回放仍然是一個問題。通常需要多個小時來回放所有 WAL 日志,直到 Prometheus 開始收集新的度量數據并提供服務。在 Robust Perception 的幫助下,我們發現將 GOMAXPROCS=24 應用于服務器可以顯著提高性能。在 WAL 回放期間,Prometheus 嘗試使用所有核心,并且對于具有大量核心的服務器,爭用會降低所有性能。
我們正在探索新的選項來增加我們的監控能力,下面“未解決的問題”部分將對此進行描述。
健康檢查
對于如此龐大的集群,我們當然依賴自動化來檢測并從集群中移除行為不當的節點。隨著時間的推移,我們建立了許多健康檢查系統。
被動健康檢查
某些健康檢查是被動的,總是在所有節點上運行。這些檢查監視基本的系統資源,例如網絡可達性、壞盤或滿盤,或者 GPU 錯誤。GPU 以許多不同的方式出現問題,但一個容易出現的常見問題是“不可糾正的 ECC 錯誤”。Nvidia 的數據中心 GPU 管理器(DCGM)工具使查詢這個問題和許多其他“Xid”錯誤變得容易。我們跟蹤這些錯誤的一種方式是通過 dcgm-exporter 將指標收集到我們的監控系統 Prometheus 中。這將出現為 DCGM_FI_DEV_XID_ERRORS 指標,并設置為最近發生的錯誤代碼。此外,NVML 設備查詢 API 公開了有關 GPU 的健康和操作的更詳細信息。
一旦檢測到錯誤,它們通常可以通過重置 GPU 或系統來修復,但在某些情況下確實需要更換基礎 GPU。
另一種健康檢查是跟蹤來自上游云提供商的維護事件。每個主要的云提供商都公開了一種方式來了解當前 VM 是否需要進行會最終導致中斷的、即將發生的維護事件。VM 可能需要重新啟動以應用底層的超級管理程序補丁,或者將物理節點替換為其他硬件。
這些被動健康檢查在所有節點上不斷運行。如果健康檢查開始失敗,節點將自動劃分,因此不會在節點上安排新的 Pod。對于更嚴重的健康檢查失敗,我們還將嘗試 Pod 驅逐,以要求當前運行的所有 Pod 立即退出。這仍然取決于 Pod 本身,可通過 Pod 故障預算進行配置來決定是否允許此驅逐發生。最終,無論是在所有 Pod 終止之后,還是在 7 天過去之后(我們的服務級別協議的一部分),我們都將強制終止 VM。
活動 GPU 測試
不幸的是,并非所有 GPU 問題都會通過 DCGM 可見的錯誤代碼表現出來。我們建立了自己的測試庫,通過對 GPU 進行測試來捕捉其他問題,并確保硬件和驅動程序的行為符合預期。這些測試無法在后臺運行 - 它們需要獨占 GPU 運行數秒鐘或數分鐘。
我們首先在節點啟動時運行這些測試,使用我們稱之為“預檢(preflight)”的系統。所有節點都會附帶一個“預檢”污點和標簽加入集群。這個污點會阻止普通 Pod 被調度到節點上。我們配置了一個 DaemonSet,在所有帶有此標簽的節點上運行預檢測試 Pod。測試成功完成后,測試本身將刪除污點和標簽,然后該節點就可供一般使用。
我們還定期在節點的生命周期中運行這些測試。我們將其作為 CronJob 運行,允許它著陸在集群中的任何可用節點上。哪些節點會被測試到可能有些隨機和不受控制,但我們發現隨著時間的推移,它提供了足夠的覆蓋率,并且最小化了協調或干擾。
配額和資源使用
隨著集群規模的擴大,研究人員開始發現他們難以獲取分配給他們的全部容量。傳統的作業調度系統有許多不同的功能,可以公平地在競爭團隊之間運行工作,而 Kubernetes 沒有這些功能。隨著時間的推移,我們從這些作業調度系統中汲取靈感,并以 Kubernetes 原生的方式構建了幾個功能。
團隊污點
我們在每個集群中都有一個服務,稱為“team-resource-manager”,具有多個功能。它的數據源是一個 ConfigMap,為在給定集群中具有容量的所有研究團隊指定了 (節點選擇器、應用的團隊標簽、分配數量) 元組。它會將當前節點與這些元組進行對比,并使用 openai.com/team=teamname:NoSchedule 的污點對適當數量的節點進行標記。
“team-resource-manager”還有一個入站的 webhook 服務,因此在提交每個作業時會根據提交者的團隊成員身份應用相應的容忍度。使用污點使我們能夠靈活地限制 Kubernetes Pod 調度程序,例如允許較低優先級的 Pod 具有 "any" 容忍度,這樣團隊可以借用彼此的容量,而無需進行大量協調。
CPU 和 GPU balloons
除了使用集群自動縮放器動態擴展我們基于虛擬機的集群之外,我們還使用它來糾正(刪除和重新添加)集群中的不健康成員。我們通過將集群的 "最小值" 設置為零、"最大值" 設置為可用容量來實現這一點。然而,如果 cluster-autoscaler 發現有空閑節點,它將嘗試縮小到只需要的容量。由于多種原因(VM 啟動延遲、預分配成本、上面提到的 API 服務器影響),這種空閑縮放并不理想。
因此,我們為 CPU 和 GPU 主機都引入了“球形”部署。這個部署包含一個具有 "最大值" 數量的低優先級 Pod 副本集。這些 Pod 占用節點內的資源,因此自動縮放器不會將它們視為空閑。但由于它們是低優先級的,調度程序可以立即將它們驅逐出去,以騰出空間進行實際工作。(我們選擇使用 Deployment 而不是 DaemonSet,以避免將 DaemonSet 視為節點上的空閑工作負載。)
需要注意的是,我們使用 pod 反親和性(anti-affinity)來確保 pod 在節點之間均勻分布。Kubernetes 調度器的早期版本存在一個 O(N^2) 的性能問題,與 pod 反親和性有關。自 Kubernetes 1.18 版本以后,這個問題已經得到了糾正。
Gang 調度
我們的實驗通常涉及一個或多個 StatefulSets,每個 StatefulSet 操作不同部分的訓練任務。對于優化器,研究人員需要在進行任何訓練之前調度 StatefulSet 的所有成員(因為我們通常使用 MPI 在優化器成員之間協調,而 MPI 對組成員變化很敏感)。
然而再默認情況下,Kubernetes 不一定會優先滿足某個 StatefulSet 的所有請求。例如,如果兩個實驗都請求 100%的集群容量,那么 Kubernetes 可能只會調度給每個實驗需要的一半 Pod,這會導致死鎖,使兩個實驗都無法進行。
我們嘗試了一些需要自定義調度程序的方法,但遇到了一些與正常 Pod 調度方式沖突的邊緣情況。Kubernetes 1.18 引入了核心 Kubernetes 調度程序的插件體系結構,使本地添加此類功能變得更加容易。我們最近選擇了 Coscheduling 插件作為解決此問題的方法。
未解決的問題
隨著 Kubernetes 集群規模的擴大,我們仍有許多問題需要解決。其中一些問題包括:
指標
在如今的規模下,Prometheus 內置的 TSDB 存儲引擎很難壓縮,并且每次重新啟動時需要長時間回放 WAL(預寫式日志)。查詢還往往會導致“查詢處理會加載過多樣本”的錯誤。我們正在遷移到不同的、與 Prometheus 兼容的存儲和查詢引擎。大家可以期待下我們未來的博客文章,看看它的表現如何!
Pod 網絡流量整形
隨著集群規模的擴大,每個 Pod 的互聯網帶寬量被計算了出來。每個人的聚合互聯網帶寬需求變得非常大,我們的研究人員現在有能力會意外地對互聯網上的其他位置施加重大資源壓力,例如要下載的數據集和要安裝的軟件包。
結 論
Kubernetes 是一個非常靈活的平臺,可以滿足我們的研究需求。它具有滿足我們所面臨的最苛刻工作負載的能力。盡管它仍有許多需要改進的地方,但 OpenAI 的超級計算團隊將繼續探索 Kubernetes 的可擴展性。
審核編輯:湯梓紅
-
服務器
+關注
關注
12文章
9123瀏覽量
85324 -
機器學習
+關注
關注
66文章
8406瀏覽量
132558 -
kubernetes
+關注
關注
0文章
224瀏覽量
8712 -
OpenAI
+關注
關注
9文章
1079瀏覽量
6481 -
ChatGPT
+關注
關注
29文章
1558瀏覽量
7595
原文標題:ChatGPT 團隊是如何使用Kubernetes的
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論