將 Kubernetes 擴展至 2,500 個節點
OpenAI 在 Azure 上結合使用 D15v2 和 NC24 VM,將其 Kubernetes 基礎設施擴展到了 2,500 個以上的節點,以支援深度學習研究。這次擴展工作需要克服狀態儲存、Pod 調度、容器鏡像分發和網路配置方面的重大瓶頸。
優化 etcd 效能與穩定性
隨著集群規模超過 500 和 1,000 個節點,Kubernetes 狀態儲存出現了嚴重的延遲和容量問題。
降低寫入延遲
當集群超過 500 個節點時,由於網路連接 SSD 的寫入延遲過高,會導致 kubectl 超時。OpenAI 透過將 etcd 目錄移至本地臨時磁碟(直接連接到實例的 SSD)解決了這個問題,這將寫入延遲從 2ms 降低到了 200us。
管理 API Server 負載
在 1,000 個節點時,由於 kube-apiservers 從 etcd 讀取的數據量超過 500MB/s,導致提交延遲再次升高。這是由於監控程序(Fluentd 和 Datadog)從每個節點對 API Server 進行激進輪詢所致。透過降低這些程序的輪詢頻率,恢復了系統穩定性。
隔離事件與儲存配額
為了防止事件(Events)產生的突發流量影響主集群狀態,OpenAI 使用 --etcd-servers-overrides 標誌將 Kubernetes Events 儲存在另一個獨立的 etcd 集群中。此外,他們使用 --quota-backend-bytes 標誌增加了預設的 2GB etcd 儲存限制,以防止發生連鎖故障(即 etcd 停止接受寫入後,自動擴展器會終止工作節點)。
Kube Master 與調度調整
OpenAI 將 Kubernetes 作為批次調度系統使用,並採用自定義的自動擴展器來最小化閒置節點的成本。
Bin-Packing 調度策略
為了確保閒置節點可以被終止,並讓大型 Pod 可以快速被調度,OpenAI 將預設的負載分散策略替換為 "MostRequestedPriority" 策略。這鼓勵將 Pod 進行「裝箱」(bin-packing)到較少的節點上,而不是平均分散。
KubeDNS 可靠性
裝箱策略造成了熱點問題,某些機器運行了 10 個以上的 KubeDNS 副本,超過了 Azure VM 的外部網域查詢限制(約 200 QPS)。OpenAI 透過實施 Pod 反親和性(pod anti-affinity)規則來解決此問題,確保 KubeDNS Pod 分散在不同的主機名稱上。
加速 Docker 鏡像拉取
大型容器鏡像(例如用於 Dota 專案的 17GB 鏡像)會導致 Pod 長時間處於 Pending 狀態。
並行化拉取與超時調整
預設情況下,kubelet 會序列化鏡像拉取(--serialize-image-pulls=true),這意味著一個大型鏡像可能會阻塞所有其他鏡像。OpenAI 將此設置為 false 並將 Docker 切換到 overlay2 儲存驅動。他們還將 Docker root 移至實例連接的 SSD 上。
為了防止因大型鏡像解壓縮緩慢而導致 rpc error: code = 2 desc = net/http: request canceled 錯誤,他們將 --image-pull-progress-deadline 增加到 30 分鐘,並在 Docker 守護進程中將 max-concurrent-downloads 設置為 10。
預載常用鏡像
為了避免節點透過 NAT 存取 Google Container Registry (gcr.io) 時觸發單一 IP 配額限制,OpenAI 使用 docker image save 和 docker image load 直接將 pod-infra-container-image 和其他常用內部鏡像預載到工作機鏡像中。
網路與內核調優
大規模分散式實驗顯示,與網路堆疊相關的吞吐量下降和連線問題非常顯著。
克服 Flannel 開銷
OpenAI 觀察到,使用 Flannel 的 Pod 吞吐量被限制在約 2Gbit/s,而裸機之間的吞吐量可達 10-15Gbit/s。為了為高性能工作負載繞過此開銷,使用者可以為其 Pod 設置 hostNetwork: true 和 dnsPolicy: ClusterFirstWithHostNet。
解決 ARP 快取溢出
間歇性的 DNS 解析失敗和連線掛起是由於內核的 ARP 快取空間不足引起的,因為大型集群中的每個 Pod 都會消耗一個 ARP 項目。這可以透過 dmesg 中的 neighbor table overflow! 訊息識別出來。OpenAI 透過在 /etc/sysctl.conf 中增加 ARP 快取閾值解決了此問題:
net.ipv4.neigh.default.gc_thresh1 = 80000
net.ipv4.neigh.default.gc_thresh2 = 90000
net.ipv4.neigh.default.gc_thresh3 = 100000
Sources
相關
- Dispatch
- Dispatch
- Dispatch
- Dispatch
- Dispatch