大家好! 我叫 Oleg Sidorenkov,在 DomClick 擔任基礎建設團隊負責人。 我們在生產中使用 Kubik 已經三年多了,在這段時間裡我們經歷了許多不同的有趣時刻。 今天,我將告訴您如何透過正確的方法,為您的叢集從普通 Kubernetes 中獲得更多效能。 準備就緒,穩紮穩打!
大家都很清楚,Kubernetes 是一個可擴充的容器編排開源系統; 好吧,或者 5 個透過管理伺服器環境中微服務的生命週期發揮神奇作用的二進位檔案。 此外,它是一個相當靈活的工具,可以像樂高一樣組裝,以最大程度地定制不同的任務。
一切似乎都很好:將伺服器扔進叢集就像柴火扔進火箱一樣,你不會有任何悲傷。 但如果你是為了環境,你會想:“我怎麼能讓火繼續燃燒,不傷害森林呢?” 換句話說,如何找到改善基礎設施和降低成本的方法。
1. 監控團隊和應用程式資源
最常見但有效的方法之一是引入請求/限制。 按命名空間劃分應用程序,按開發團隊劃分命名空間。 在部署之前,設定應用程式的處理器時間、記憶體和臨時儲存消耗值。
resources:
requests:
memory: 2Gi
cpu: 250m
limits:
memory: 4Gi
cpu: 500m
根據經驗,我們得出的結論是:您不應將限制的請求增加兩倍以上。 叢集的容量是根據請求計算的,如果你給應用程式不同的資源,例如5-10倍,那麼想像一下當你的節點裝滿了pod並突然收到負載時會發生什麼。 沒什麼好的。 最低限度是節流,最高限度是,您將告別工作線程,並在 Pod 開始移動後在剩餘節點上獲得循環負載。
此外,在幫助下 limitranges
一開始,您可以設定容器的資源值 - 最小值、最大值和預設值:
➜ ~ kubectl describe limitranges --namespace ops
Name: limit-range
Namespace: ops
Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
---- -------- --- --- --------------- ------------- -----------------------
Container cpu 50m 10 100m 100m 2
Container ephemeral-storage 12Mi 8Gi 128Mi 4Gi -
Container memory 64Mi 40Gi 128Mi 128Mi 2
不要忘記限制命名空間資源,以免一個團隊無法接管叢集的所有資源:
➜ ~ kubectl describe resourcequotas --namespace ops
Name: resource-quota
Namespace: ops
Resource Used Hard
-------- ---- ----
limits.cpu 77250m 80
limits.memory 124814367488 150Gi
pods 31 45
requests.cpu 53850m 80
requests.memory 75613234944 150Gi
services 26 50
services.loadbalancers 0 0
services.nodeports 0 0
從描述中可以看出 resourcequotas
,如果維運團隊想要部署將消耗另外 10 個 cpu 的 pod,調度程序將不允許這樣做並會拋出錯誤:
Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10
為了解決這樣的問題,你可以寫一個工具,例如,
2. 選擇最佳的檔案儲存方式
這裡我想談談持久捲和 Kubernetes Worker 節點的磁碟子系統。 我希望沒有人在生產中使用 HDD 上的“Cube”,但有時普通 SSD 已經不夠了。 我們遇到了由於I/O操作導致日誌殺盤的問題,解決方案並不多:
-
使用高效能 SSD 或切換到 NVMe(如果您管理自己的硬體)。
-
降低日誌記錄等級。
-
對佔用磁碟的 pod 進行「智慧」平衡(
podAntiAffinity
).
上面的畫面顯示了啟用 access_logs 日誌記錄(約 12 個日誌/秒)時,在 nginx-ingress-controller 下到磁碟發生的情況。 當然,這種情況可能會導致該節點上的所有應用程式效能下降。
至於PV,唉,我還沒有嘗試過一切
3. 收集優化影像
最好使用容器優化的鏡像,以便 Kubernetes 可以更快地獲取它們並更有效地執行它們。
優化意味著圖像:
-
僅包含一個應用程式或僅執行一項功能;
-
尺寸小,因為大圖像在網路上傳輸效果較差;
-
擁有運作狀況和就緒端點,Kubernetes 可以使用這些端點在停機時採取行動;
-
使用容器友善的作業系統(如 Alpine 或 CoreOS),它們更能抵抗配置錯誤;
-
使用多階段構建,以便您只能部署已編譯的應用程序,而不能部署隨附的原始程式碼。
有許多工具和服務可讓您即時檢查和優化影像。 始終保持最新狀態並進行安全測試非常重要。 結果你得到:
-
減少了整個叢集的網路負載。
-
減少容器啟動時間。
-
整個 Docker 註冊表的大小更小。
4.使用DNS緩存
如果我們談論高負載,那麼如果不調整叢集的 DNS 系統,生活會非常糟糕。 曾幾何時,Kubernetes 開發人員支援他們的 kube-dns 解決方案。 這裡也實現了,但是這個軟體沒有經過特別的調整,沒有產生所需的效能,儘管它看起來是一個簡單的任務。 然後 coredns 出現了,我們切換到了它並沒有悲傷;它後來成為 K8s 中的預設 DNS 服務。 在某個時候,我們的 DNS 系統的 rps 成長到了 40,這個解決方案也變得不夠用了。 但是,幸運的是,Nodelocaldns 出現了,又稱為節點本地緩存,又名
我們為什麼要用這個? Linux 核心中存在一個錯誤,當透過UDP 上的conntrack NAT 進行多次呼叫時,會導致conntrack 表中的條目出現競爭條件,並且透過NAT 的部分流量會遺失(每次通過服務的行程都是NAT) 。 Nodelocaldns 透過擺脫 NAT 並升級到上游 DNS 的 TCP 連線以及本機快取上游 DNS 查詢(包括短暫的 5 秒負快取)來解決此問題。
5. 自動水平和垂直縮放 Pod
您能否自信地說您的所有微服務都已準備好將負載增加兩到三倍? 如何為您的應用程式正確分配資源? 讓幾個 Pod 超出工作負載運行可能是多餘的,但讓它們背對背運行可能會面臨因服務流量突然增加而導致停機的風險。 服務如
VPA 讓您根據實際使用情況自動提高 Pod 中容器的請求/限制。 怎麼可能有用呢? 如果您的 Pod 由於某種原因無法水平擴展(這並不完全可靠),那麼您可以嘗試將對其資源的變更委託給 VPA。 它的功能是基於來自度量伺服器的歷史和當前資料的推薦系統,因此如果您不想自動更改請求/限制,您可以簡單地監視容器的建議資源並優化設定以節省 CPU 和叢集中的記憶體。
圖片取自https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231
Kubernetes 中的調度程序始終基於請求。 無論您在那裡輸入什麼值,調度程序都會根據它搜尋合適的節點。 Cubelet 需要限制值來了解何時限製或終止 pod。 由於唯一重要的參數是請求值,VPA 將使用它。 每當您垂直擴展應用程式時,您都可以定義請求的內容。 那麼極限會發生什麼事? 此參數也將按比例縮放。
例如,以下是常用的 pod 設定:
resources:
requests:
memory: 250Mi
cpu: 200m
limits:
memory: 500Mi
cpu: 350m
推薦引擎確定您的應用程式需要 300m CPU 和 500Mi 才能正常運作。 您將獲得以下設定:
resources:
requests:
memory: 500Mi
cpu: 300m
limits:
memory: 1000Mi
cpu: 525m
如上所述,這是基於清單中的請求/限制比率的比例縮放:
-
CPU:200m→300m:比例1:1.75;
-
內存:250Mi→500Mi:比例1:2。
至於 HPA,那麼運作機制就更透明。 CPU 和記憶體等指標設有閾值,如果所有副本的平均值超過閾值,則應用程式將按 +1 sub 縮放,直到該值低於閾值或達到最大副本數。
圖片取自https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231
除了 CPU 和記憶體等常用指標之外,您還可以在 Prometheus 中設定自訂指標的閾值,如果您認為這是何時擴展應用程式的最準確指示,則可以使用它們。 一旦應用程式穩定在指定的指標閾值以下,HPA 將開始將 Pod 縮減到最小副本數或直到負載達到指定的閾值。
6. 不要忘記 Node Affinity 和 Pod Affinity
並非所有節點都在相同的硬體上運行,也並非所有 Pod 都需要運行運算密集型應用程式。 Kubernetes 讓您可以使用以下指令設定節點和 Pod 的專業化 節點親和力 и Pod 親和力.
如果您有適合計算密集型操作的節點,那麼為了獲得最大效率,最好將應用程式綁定到相應的節點。 若要執行此操作,請使用 nodeSelector
帶有節點標籤。
假設您有兩個節點:其中一個具有 CPUType=HIGHFREQ
和大量快速核心,另一個具有 MemoryType=HIGHMEMORY
更多內存和更快的性能。 最簡單的方法是將部署指派給節點 HIGHFREQ
透過添加到該部分 spec
這個選擇器:
…
nodeSelector:
CPUType: HIGHFREQ
一種更昂貴且更具體的方法是使用 nodeAffinity
在現場 affinity
劇集 spec
。 有兩種選擇:
-
requiredDuringSchedulingIgnoredDuringExecution
:硬設定(調度程序將僅在特定節點(而不是其他節點)部署 Pod); -
preferredDuringSchedulingIgnoredDuringExecution
:軟設定(調度程序將嘗試部署到特定節點,如果失敗,它將嘗試部署到下一個可用節點)。
您可以指定管理節點標籤的特定語法,例如 In
, NotIn
, Exists
, DoesNotExist
, Gt
或 Lt
。 但是,請記住,長標籤清單中的複雜方法會減慢關鍵情況下的決策速度。 換句話說,保持簡單。
如上所述,Kubernetes 可讓您設定目前 pod 的親和性。 也就是說,您可以讓某些 Pod 與同一可用區(與雲端相關)或節點中的其他 Pod 一起工作。
В podAffinity
領域 affinity
劇集 spec
與以下情況相同的欄位可用 nodeAffinity
: requiredDuringSchedulingIgnoredDuringExecution
и preferredDuringSchedulingIgnoredDuringExecution
。 唯一的區別是 matchExpressions
會將 Pod 綁定到已經執行具有該標籤的 Pod 的節點。
Kubernetes也提供了一個領域 podAntiAffinity
,相反,它不會將 pod 綁定到具有特定 pod 的節點。
關於表達式 nodeAffinity
可以給出相同的建議:嘗試保持規則簡單且符合邏輯,不要嘗試使用一組複雜的規則來超載 pod 規範。 建立與叢集條件不匹配的規則非常容易,從而對調度程序造成不必要的負載並降低整體效能。
7. 污點和寬容
還有另一種方法來管理調度程序。 如果您有一個包含數百個節點和數千個微服務的大型集群,那麼很難不允許某些 Pod 託管在某些節點上。
污點機制(禁止規則)對此有所幫助。 例如,在某些場景下,您可以禁止某些節點執行 Pod。 若要將污點套用到特定節點,您需要使用該選項 taint
在 kubectl. 指定鍵和值,然後像這樣進行污點 NoSchedule
或 NoExecute
:
$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule
另外值得注意的是,污點機制支援三種主要效果: NoSchedule
, NoExecute
и PreferNoSchedule
.
-
NoSchedule
意味著現在 pod 規範中不會有對應的條目tolerations
,它將無法部署在節點上(在本例中node10
). -
PreferNoSchedule
- 簡化版本NoSchedule
。 在這種情況下,排程器將嘗試不指派沒有符合條目的 Podtolerations
每個節點,但這不是一個硬限制。 如果叢集中沒有資源,則 Pod 將開始在此節點上部署。 -
NoExecute
- 此效果會觸發沒有對應條目的吊艙立即撤離tolerations
.
有趣的是,可以使用容忍機制來取消這種行為。 當存在“禁止”節點並且您只需要在其上放置基礎設施服務時,這很方便。 怎麼做? 僅允許具有適當容差的 pod。
Pod 規格如下:
spec:
tolerations:
- key: "node-role.kubernetes.io/ingress"
operator: "Equal"
value: "true"
effect: "NoSchedule"
這並不意味著下一次重新部署將落在該特定節點上,這不是節點親和性機制,並且 nodeSelector
。 但透過組合多種功能,您可以實現非常靈活的調度程式設定。
8. 設定 Pod 部署優先權
僅僅因為將 Pod 分配給節點並不意味著必須以相同的優先順序對待所有 Pod。 例如,您可能想要在其他 pod 之前部署一些 pod。
Kubernetes 提供了不同的方法來設定 Pod 優先權和搶佔。 設定由幾個部分組成: 對象 PriorityClass
和字段描述 priorityClassName
在 pod 規範中。 讓我們來看一個例子:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 99999
globalDefault: false
description: "This priority class should be used for very important pods only"
我們創造 PriorityClass
,為其指定名稱、描述和值。 越高 value
,優先順序越高。 該值可以是小於或等於 32 的任何 1 位元整數。較高的值是為通常無法搶佔的關鍵任務系統 Pod 保留的。 只有當高優先級的 pod 無處掉頭時才會發生位移,然後某個節點的部分 pod 會被疏散。 如果這個機制對你來說太僵化,你可以加入選項 preemptionPolicy: Never
,然後就不會發生搶佔,pod會站在佇列的第一位,等待調度器為它找到空閒資源。
接下來,我們建立一個 pod,在其中指定名稱 priorityClassName
:
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
role: myrole
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
protocol: TCP
priorityClassName: high-priority
您可以根據需要建立任意多個優先級類別,但建議不要為此而得意忘形(例如,將自己限制為低、中和高優先級)。
因此,如有必要,您可以提高部署關鍵服務(例如 nginx-ingress-controller、coredns 等)的效率。
9.優化ETCD集群
ETCD堪稱整個集群的大腦。 將該資料庫的操作保持在高水準非常重要,因為 Cube 中的操作速度取決於它。 一個相當標準且同時良好的解決方案是將 ETCD 叢集保留在主節點上,以便將 kube-apiserver 的延遲降至最低。 如果您做不到這一點,請將 ETCD 放置得盡可能近,並在參與者之間提供良好的頻寬。 也要注意 ETCD 中有多少節點可以掉下來而不會對群集造成損害
請記住,過度增加集群中的成員數量可能會以犧牲性能為代價來提高容錯能力,一切都應該適度。
如果我們談論設定服務,有一些建議:
結論
本文描述了我們團隊試圖遵守的要點。 這不是操作的分步描述,而是可能對優化叢集開銷有用的選項。 顯然,每個叢集都有其獨特的方式,配置解決方案可能差異很大,因此獲得有關如何監控 Kubernetes 叢集以及如何提高其效能的回饋將會很有趣。 在評論中分享您的經驗,了解會很有趣。
來源: www.habr.com