我們使用Kubernetes做深度學習的研究已經超過兩年。儘管我們最大的工作量直接管理裸雲VM,但是Kubernetes提供了快速的迭代週期、具有合理的擴充套件性以及不含樣板程式碼(a lack of boilerplate),所以在Kubernetes上進行的實驗大多都達到了我們預期。我們現在執行著幾個Kubernetes叢集(一些在雲上,一些在物理機機上),其中最大的已經超過2500節點。這些叢集執行在Azure提供的D15v2和NC24 組合虛擬機器上。
達到現在這個節點規模,我們也經歷過很多問題。比如許多系統元件引起的問題:包括etcd、Kubernetes的master節點、Docker映象獲取問題、Network問題、KubeDNS,甚至是我們機器上的ARP快取。我認為分享這些我們遇到的具體問題,以及我們是怎麼解決這些問題是很有意義的。
在我們叢集到500個節點之後,我們的researchers開始從kubectl命令列工具得到定時超時的警告。 我們嘗試新增更多Kubernetes master(執行kube-apiserver的VM)。 這似乎暫時解決了這個問題,但是一連經歷10次後,我們意識到這隻是在處理癥狀,而沒發現真正的原因(相比之下,GKE使用32位單核虛擬機器支撐500個節點)。
這讓我們非常懷疑是提供Kube master中央狀態儲存區的etcd叢集出了問題。 從Datadog看來,儘管每臺機器都使用能夠達到5,000 IOPS的P30 SSD,但是在執行我們的etcd副本的DS15v2機器上,我們看到寫入有幾百毫秒的延遲。
在fio[1]的基準測試中,我們發現etcd只能使用大約10%的IOPS,因為寫延遲是2ms,etcd是連續的I/O,因此引起一連串的延遲。
然後,我們將每個節點的etcd目錄移動到本地臨時磁碟,這是一個直接連線到實體的SSD,而不是網路連線。切換到本地磁碟帶後寫延遲達到200us,etcd恢復正常!
直到我們達到大約1,000個節點以前,我們的叢集執行良好。在1,000這一點上,我們再次看到了etcd的提交高延遲。這一次,我們註意到kube-apiservers從etcd上讀取了超過500MB/s。我們設定了Prometheus來監視apiservers,還設定了–audit-log-path和–audit-log-maxbackup標誌,以便在apiserver上啟用更多的日誌記錄。這就出現了一些緩慢的查詢和對事件串列API的過度呼叫。
根本原因:Fluentd和Datadog監控行程的預設設定是從叢集中的每個節點查詢apiservers(例如,現在已解決的問題[2])。 我們只是簡單地改變了這些呼叫的過程,使apiservers的負載變得穩定。
etcd出口從500MB / s 下降到幾乎為0(上圖中的負值表示出口)
另一個有用的調整是將Kubernetes事件儲存在一個單獨的etcd叢集中,以便事件建立中的峰值不會影響主要etcd實體的效能。 要做到這一點,我們只需將–etcd-servers-overrides標誌設定為如下所示:
-etcd-servers-overrides=/events#https://0.example.com:2381;https://1.example.com:2381;https://2.example.com:2381
另一個超過1,000後節點故障是超過了etcd的硬碟儲存限制(預設2GB),導致硬碟拒絕寫入。 這引發了一個級聯失敗:所有的Kube節點都健康檢查失敗,我們的autoscaler決定它需要終止所有的任務。 我們用–quota-backend-bytes標誌增加了max etc的大小,現在autoscaler有了一個智慧的檢查,如果它終止超過50%的叢集,不會採取行動。
我們在同一臺機器上對kube-apiserver、kube- controllermanager和kube-scheduler行程進行定位。對於高可用性,我們總是至少有2個master,並將apiserver-count標誌設定為我們正在執行的apiservers的數量(否則,Prometheus監視會在實體間混淆)。
我們主要使用Kubernetes作為批次排程系統,並依靠我們的自動調節器動態擴容和縮容我們的叢集——這使我們可以顯著降低空閑節點的成本,同時在快速迭代時仍然保證低延遲。 預設的kube-scheduler策略是在負載均勻分佈在節點之間。但是我們希望相反,這樣可以終止未使用的節點,也可以快速排程大的Pods。 所以我們切換到以下策略:
{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
{"name" : "GeneralPredicates"},
{"name" : "MatchInterPodAffinity"},
{"name" : "NoDiskConflict"},
{"name" : "NoVolumeZoneConflict"},
{"name" : "PodToleratesNodeTaints"}
],
"priorities" : [
{"name" : "MostRequestedPriority", "weight" : 1},
{"name" : "InterPodAffinityPriority", "weight" : 2}
]
}
我們的服務發現功能廣泛使用KubeDNS,但在推出新的排程策略後不久就開始出現可靠性問題。 我們發現,失敗只發生在KubeDNS的某些Pods上。 在新的排程策略下一些機器最終運行了10多個KubeDNS副本,建立了熱點,而且我們已經超過了每個Azure虛擬機器允許的查詢〜200QPS限制。
我們透過為KubeDNS Pod新增一個anti-affinity規則[3]來解決這個問題:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- weight: 100
labelSelector:
matchExpressions:
- key: k8s-app
operator: In
values:
- kube-dns
topologyKey: kubernetes.io/hostname
我們的Dota專案是在Kubernetes上起步的,隨著規模的擴大,我們註意到新增的Kubernetes節點經常有很長一段時間處在Pending。 遊戲映象大概是17GB,通常需要30分鐘才能拉上一個新的叢集節點,所以我們理解了Dota容器為什麼會暫停一段時間——但是其他容器也是如此。 進一步挖掘,我們發現kubelet有一個–serialize-image-pullsflag預設為true,這意味著Dota映象阻塞了所有其他映象。 更改為false需要將Docker切換到overlay2而不是AUFS。 為了進一步提高獲取映象的速度,我們也將Docker root 移到了實體連線的SSD上,就像我們為etcd機器所做的那樣。
即使在最佳化獲取映象速度之後,我們也看到Pod無法啟動一個詭異的錯誤資訊:rpc error: code = 2 desc = net/http: request canceled。 kubelet和Docker日誌訊息顯示“由於缺乏進度,映象的獲取已經取消”。 我們追蹤了問題的根源是需要花費太多時間來獲取/加壓提取的大映象,或者當我們很多積壓的映象要獲取的時候。 為瞭解決這個問題,我們將kubelet的-image-pull-progress-deadline標誌設定為30分鐘,並將Docker守護行程的最大併發下載選項設定為10。(第二個選項沒有加速獲取大映象,但允許映象佇列並行獲取。)
我們最後一個Docker問題是由於Google Container Registry造成的。 預設情況下,kubelet從gcr.io獲取特殊的映象(由–pod-infra-container-image標誌控制)gcr.io經常用於建立一個新的容器。 如果因為任何原因(例如超過配額)而導致失敗,該節點將無法啟動任何容器。 由於我們的節點透過NAT到達gcr.io而不是擁有自己的公有IP,所以我們很可能會達到這個每IP配額的限制。 為瞭解決這個問題,我們透過使用docker image save -o /opt/preloaded_docker_images.tar和docker image load -i /opt/preloaded_docker_images.tar,簡單地在我們的Kubernetes worker的機器映象中預先載入了Docker映象。 為了提高效能,我們對於像Dota映象這樣的常見OpenAI內部影象的白名單也是這樣做的。
隨著我們的實驗規模越來越大,它們也變得越來越複雜,這些系統在很大程度上依賴於網路的運作。當我們第一次開始執行分散式實驗時,很明顯我們的網路並沒有被很好地配置。在機器之間,我們得到了10-15Gbit/s的吞吐量,但是我們使用Flannel的Kube Pod在~2Gbit/s上是最大的。機器區域的公共基準測試[4]顯示了類似的數字,這意味著這個問題不太可能是糟糕的配置,而是我們的環境所固有的一些東西。(相比之下,Flannel並沒有在我們的物理機器上增加這個開銷。)
為瞭解決這個問題,使用者可以新增兩個不同的設定來禁用他們的Pod:hostNetwork: true和dnsPolicy: ClusterFirstWithHostNet。(在此之前,請閱讀Kubernetes檔案中的警告[5]。)
儘管我們進行了DNS調優,但我們仍然看到DNS解析的間歇性問題。有一天,一位工程師報告說,nc -v到他們的Redis伺服器上花了30秒的時間才打印出連線。我們跟蹤這個問題到內核的ARP堆疊。對Redis pod主機的初步調查顯示,該網路出現了嚴重的問題:任何埠上的通訊都被掛了好幾秒鐘,而且沒有DNS名稱可以透過本地的dnsmasq守護行程來解決,而dig命令只是列印一個加密的失敗訊息:socket。socket.c:1915: internal_send: 127.0.0.1#53: Invalid argument.。dmesg日誌資訊更豐富:neighbor table overflow!這意味著ARP快取已經耗盡了空間。ARP用於將網路地址(比如IPv4地址)對映到物理地址,比如MAC地址。幸運的是,透過在/etc/sysctl.conf中設定一些選項,這很容易解決。
net.ipv4.neigh.default.gc_thresh1 = 80000
net.ipv4.neigh.default.gc_thresh2 = 90000
net.ipv4.neigh.default.gc_thresh3 = 100000
在HPC叢集中調優這個設定是很常見的,並且在Kubernetes叢集中尤其重要,因為每個Pod都有自己的IP地址,它佔用了ARP快取中的空間。
我們的Kubernetes叢集已經有3個月的歷史了,我們計劃在2018年擴充套件到更大的叢集。我們最近升級到1.8.4版本,並且很高興看到它現在正式支援5000。
-
https://github.com/axboe/fio
-
https://github.com/DataDog/dd-agent/issues/3381
-
https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
-
http://machinezone.github.io/research/networking-solutions-for-kubernetes/
-
https://kubernetes.io/docs/concepts/configuration/overview/
原文連結:https://blog.openai.com/scaling-kubernetes-to-2500-nodes/
本次培訓內容包括:Docker容器的原理與基本操作;容器網路與儲存解析;Kubernetes的架構與設計理念詳解;Kubernetes的資源物件使用說明;Kubernetes 中的開放介面CRI、CNI、CSI解析;Kubernetes監控、網路、日誌管理;容器應用的開發流程詳解等,點選識別下方二維碼加微信好友瞭解具體培訓內容。
長按二維碼向我轉賬
受蘋果公司新規定影響,微信 iOS 版的贊賞功能被關閉,可透過二維碼轉賬支援公眾號。