當前位置:
首頁 > 科技 > 如何用 Kubernetes 管理超過 2500 個節點的集群

如何用 Kubernetes 管理超過 2500 個節點的集群

接收程序員的 8 點技術早餐

譯者|夏天

校對|郭維

出處丨 K8sMeetup 中國社區

兩年來,我們一直在使用 Kubernetes 進行深度學習方面的研究。雖然我們大量的 workloads 可以直接運行在雲虛擬機上,但是因為 Kubernetes 目前仍在快速迭代,而且擁有比較好的擴展性,也沒有過多的約束,因此 Kubernetes 成為了我們最理想的集群管理工具。目前,我們已經有好幾個正在運行的 Kubernetes 集群了(有些運行在雲虛擬機上,有些則直接運行在物理機上)。其中最大的一個集群運行在 Azure 上,由 D15v2 和 NC24 兩種類型的虛擬機組成,目前已經超過 2500 個節點。

在達到 2500 節點規模的過程中,許多的系統組件都出現了問題,包括 etcd,Kube Master,Docker 拉取鏡像,網路,Kube DNS,機器上的 ARP 緩存。接下來將分享我們所遇到的問題和解決方案,希望對大家有所幫助。

etcd

在集群中添加了超過 500 個節點的時候,我們的研究人員發現使用 kubectl 操作集群的時候會發生超時的情況。於是我們嘗試添加更多的 Kube Master 節點(運行 kube-apiserver 的節點)。這樣做臨時解決了這個問題,但當我們添加了 10 個 Master 節點後,我們發現這只是在表面上解決問題,並沒有找到這個問題的根本原因(作為對比,GKE 只使用了一台 32 核的虛擬機作為 Master 就能管理 500 個節點。

etcd 存儲了 Kubernetes 集群的所有狀態數據,因此我們強烈懷疑是 etcd 集群出現了問題。通過查看 Datadog,我們發現雖然我們的每台機器都使用了支持 5000 IOPS 的 P30 SSD,但是運行了 etcd 的 DS15v2 虛擬機的寫延遲在某些時間段猛增至數百毫秒。

這麼高的寫延遲阻塞了整個集群!

在使用 fio 進行性能測試之後,我們發現 etcd 的 I/O 性能只能達到 I/O 上限的 10% 左右 。這主要是因為單次寫延遲為 2ms,而 etcd 又使用了串列 I/O,導致 I/O 之間的延遲疊加(latency-bound)。

然後,我們將每個節點上的 etcd 的目錄從網路磁碟移到本地 SSD 磁碟上。移動之後,寫延遲降低到了 200 微秒,etcd 終於正常了。

我們的集群一直運行良好,直到我們增加到了 1000 個節點。這個時候我們再次發現 etcd 出現了很高的延遲。這一次,我們注意到 kube-apiservers 從 etcd 讀取數據的速度超過了 500MB/s。我們搭建了 Prometheus 用於監控 kube-apiservers,還設置了--audit-log-path和--audit-log-maxbackup選項來保存更多的日誌數據。通過日誌,我們發現了許多慢查詢請求和大量的對 Events 的 List API 的請求。

我們找到了問題的根本原因,我們發現部分程序會頻繁去 kube-apiservers 查詢各種信息(比如 Fluentd 和 Datdog 會去 kube-apiservers 查詢每個節點信息)。我們修改了這些程序的設置,降低它們對 kube-apiservers 的查詢頻率,然後 kube-apiservers 的負載就降下來了:

etcd 的出口速度從 500MB/s 左右降低到了接近 0MB/s(上圖中負值表示出口速度)

另一個有效的改動是把 Kubernetes Events 存儲在一個單獨的 etcd 集群里,這樣對 Events 的操作就不會影響到主 etcd 集群的正常工作。要想能做到這一點,我們只需要將這--etcd-servers-overrides設置為

另一個超過 1000 節點後的故障是觸發了 etcd 的硬碟存儲上限(默認是 2GB), 於是 etcd 不再接受寫請求。這直接導致了一連串的問題:所有 kube node 的健康檢查都失敗了,我們的 autoscaler 決定刪除所有的 workers。於是我們通過 etcd 的--quota-backend-bytes增大了存儲大小。另外還讓 autoscaler 支持基礎(sanity)檢查,在發現要終止集群里超過 50% 的 workers 的時候,放棄這個操作。

Kube Master

我們在同一台機器上安裝了 kube-apiserver,kube-controller-manager 和 kube-scheduler processes。為了高可用,我們一直有至少兩個 masters,並將--apiserver-count設置為我們正在運行的 apiservers 數量(否則 Prometheus 可能會混淆這些實例)。

我們主要使用 Kubernetes 作為批量調度系統,並依靠 autoscaler 動態地伸縮我們的集群。這使我們可以顯著地降低空閑節點的成本,在快速迭代的同時提供低延遲。默認的 kube-scheduler 策略是將負載均勻分散在節點之間,但這與我們的期望相悖,我們希望可以終止未使用的節點,同時也可以快速調度大的 pod。所以我們切換到下面的策略:

我們使用 KubeDNS 實現服務發現,但是在我們使用新的調度策略後,很快它就出現了一些可靠性問題。我們發現故障只在 KubeDNS 的 Pods 中出現。在新的調度規則影響下,有些機器上運行了十多個 KubeDNS 實例,這就導致了這些機器成為了熱點,它們收到的 DNS 查詢超過了 200 QPS(這是 Azure 虛擬機對 DNS 查詢的上限值)。

Docker 拉取鏡像

我們的 Dota 項目剛開始運行在 Kubernetes 上的時候,一旦它開始擴容,我們就注意到一些新的節點上有很多 Pods 處於 Pending 狀態,而且持續很長時間。Dota 的鏡像大小為 17GB,把這個鏡像拉取到一個新的節點上需要大約 30 分鐘的時間。因此我們知道了 Dota 的 Pod 處於 Pending 的原因,但是同一個節點上的其他比較小的 Pod 也出現了相同的情況。

隨著調查的深入,我們發現 kubelet 的--serialize-image-pulls默認為true,這意味著拉取 Dota 鏡像的時候,其他鏡像的拉取都會被阻塞住。把這個選項修改為false需要讓 Docker 使用 overlay2 文件系統而不是 AUFS。為了加快拉取速度,我們把 Docker 的根存儲目錄遷移到了機器的本地 SSD 上,就像 etcd 那樣。

在優化了拉取速度之後,我們仍然發現 Pods 出現了奇怪的錯誤:error message: rpc error: code = 2 desc = net/http: request canceled。由於進度不足,Kubelet 和 Docker 的日誌里也指出了鏡像拉取被取消了。我們究其根源,發現當有很多積壓的鏡像拉取任務,或者有些大鏡像花費很長的時間都沒有完成拉取和解壓工作的時候,就會出現這種錯誤。為了解決這個問題,我們將 Kubelet 的--image-pull-progress-deadline設置為 30 分鐘, 並且設置了 Docker 的max-concurrent-downloads為 10。(第二個選項不會加速大鏡像的解壓工作,但是可以讓拉取鏡像的工作並行執行 。)

我們最後的 Docker 鏡像拉取問題源自 Google Container Registry。默認情況下, Kubelet 會自動從gcr.io拉取一個特殊的鏡像(可由--pod-infra-container-image控制),這個鏡像用於啟動新的容器。如果拉取失敗,這個節點就不能啟動任何 Pod 。由於我們的節點沒有公網 IP,只能通過 NAT 訪問gcr.io,一旦超過了單個 IP 的配額上限,就會拉取失敗。為了解決這個問題,我們通過預載入鏡像直接將鏡像部署到機器上。通過docker image save -o /opt/preloaded_docker_images.tar和docker image load -i /opt/preloaded_docker_images.tar完成這個工作。為了提升性能,我們對其他公共鏡像比如 OpenAI-internal 和 Dota 鏡像也做了相同的事情。

聯 網

隨著我們的實驗越來越大,我們的系統也逐漸變成了重度依賴網路的複雜的分散式系統。當我們第一次開始分散式實驗的時候,立即就發現了我們的網路不是很好。機器之間的吞吐量大約在 10-15Gbit/s, 但是我們基於 Flannel 的 Pods 之間的最大吞吐量僅僅只有 2Gbit/s。

Machine Zone 的公開性能測試也顯示了類似的結果,這意味著這個問題未必是由配置不好導致的,很有可能是我們的環境中隱藏了一些問題(機器上的 Flannel 應該不會增加這麼大的開銷)。

為了解決這個問題,用戶可以使用兩個不同的設置來讓 Pods 繞過 Flannel 的網路:hostNetwork 設置為true,dnsPolicy 設置為ClusterFirstWithHostNet(在做這件事情之前請先閱讀 Kubernetes 中的警告)。

ARP 緩存

雖然我們優化了 DNS 的 Pods,但是 DNS 解析仍然時不時地出點問題。有一天,一位工程師報告說,他用 nc -v 連接 Redis 伺服器時,花費了超過 30 秒才成功建立連接。我們深入這個問題一直到內核的 ARP 棧。初步調查顯示 Redis Pod 所在節點的網路出現了嚴重錯誤:連接任意埠都會掛起好幾秒,而且本地的 dnsmasq 沒有任何 DNS 記錄,dig 只是列印了一個奇怪的錯誤信息:socket.c:1915: internal_send: 127.0.0.1#53: Invalid argument。dmesg 的日誌反而有用的多:neighbor table overflow!這表示 ARP 已經用完了緩存空間。ARP 是用來映射 IPv4 地址到一個物理地址的(比如 MAC 地址)。幸運的是,只需要在/etc/sysctl.conf中設置幾個選項就能輕鬆解決這個問題:

這個修改在 HPC 集群中非常常見,而此時在 Kubernetes 中這個選項也非常重要,因為每個 Pod 都有自己的 IP,而每個 IP 都需要佔用 ARP 緩存空間。

參考文獻

https://blog.openai.com/scaling-kubernetes-to-2500-nodes/?utm_source=digg


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 InfoQ 的精彩文章:

春節假期技術圈都發生了哪些大新聞?

TAG:InfoQ |