溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Kubernetes 中的 requests 和 limits 的概念是什么

發(fā)布時間:2021-07-12 10:32:09 來源:億速云 閱讀:145 作者:chen 欄目:云計算

本篇內容主要講解“Kubernetes 中的 requests 和 limits 的概念是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Kubernetes 中的 requests 和 limits 的概念是什么”吧!

在 K8s 集群中部署資源的時候,你是否經(jīng)常遇到以下情形:

  1. 經(jīng)常在 Kubernetes 集群種部署負載的時候不設置 CPU requests 或將 CPU requests 設置得過低(這樣“看上去”就可以在每個節(jié)點上容納更多 Pod )。

    在業(yè)務比較繁忙的時候,節(jié)點的 CPU 全負荷運行。業(yè)務延遲明顯增加,有時甚至機器會莫名其妙地進入 CPU 軟死鎖等“假死”狀態(tài)。

  2. 類似地,部署負載的時候,不設置內存 requests 或者內存 requests 設置得過低,這時會發(fā)現(xiàn)有些 Pod 會不斷地失敗重啟。

    而不斷重啟的這些 Pod 通常跑的是 Java 業(yè)務應用。但是這些 Java 應用本地調試運行地時候明明都是正常的。

  3. 在 Kubernetes 集群中,集群負載并不是完全均勻地在節(jié)點間分配的,通常內存不均勻分配的情況較為突出,集群中某些節(jié)點的內存使用率明顯高于其他節(jié)點。

    Kubernetes 作為一個眾所周知的云原生分布式容器編排系統(tǒng),一個所謂的事實上標準,其調度器不是應該保證資源的均勻分配嗎?

如果在業(yè)務高峰時間遇到上述問題,并且機器已經(jīng) hang 住甚至無法遠程 ssh 登陸,那么通常留給集群管理員的只剩下重啟集群這一個選項。

如果你遇到過上面類似的情形,想了解如何規(guī)避相關問題或者你是 Kubernetes 運維開發(fā)人員,想對這類問題的本質一探究竟,那么請耐心閱讀下面的章節(jié)。

我們會先對這類問題做一個定性分析,并給出避免此類問題的最佳實踐,最后如果你對 Kubernetes requestslimits 的底層機制感興趣,我們可以從源碼角度做進一步地分析,做到“知其然也知其所以然”。

問題分析

  1. 對于情形 1

    首先我們需要知道對于 CPU 和內存這 2 類資源,他們是有一定區(qū)別的。 CPU 屬于可壓縮資源,其中 CPU 資源的分配和管理是 Linux 內核借助于完全公平調度算法( CFS )和 Cgroup 機制共同完成的。

    簡單地講,如果 Pod 中服務使用 CPU 超過設置的 CPU limits, Pod 的 CPU 資源會被限流( throttled )。對于沒有設置limit的 Pod ,一旦節(jié)點的空閑 CPU 資源耗盡,之前分配的 CPU 資源會逐漸減少。

    不管是上面的哪種情況,最終的結果都是 Pod 已經(jīng)越來越無法承載外部更多的請求,表現(xiàn)為應用延時增加,響應變慢。

  2. 對于情形 2

    內存屬于不可壓縮資源, Pod 之間是無法共享的,完全獨占的,這也就意味著資源一旦耗盡或者不足,分配新的資源一定是會失敗的。有的 Pod 內部進程在初始化啟動時會提前開辟出一段內存空間。

    比如 JVM 虛擬機在啟動的時候會申請一段內存空間。如果內存 requests 指定的數(shù)值小于 JVM 虛擬機向系統(tǒng)申請的內存,導致內存申請失?。?oom-kill ),從而 Pod 出現(xiàn)不斷地失敗重啟。

  3. 對于情形 3

    實際上在創(chuàng)建 Pod 的過程中,一方面, Kubernetes 需要撥備包含 CPU 和內存在內的多種資源,這里的資源均衡是包含 CPU 和內存在內的所有資源的綜合考量。

    另一方面, Kubernetes 內置的調度算法不僅僅涉及到“最小資源分配節(jié)點”,還會把其他諸如 Pod 親和性等因素考慮在內。并且 Kubernetes 調度基于的是資源的 requests 數(shù)值,而之所以往往觀察到的是內存分布不夠均衡,是因為對于應用來說,相比于其他資源,內存一般是更緊缺的一類資源。

    Kubernetes 的調度機制是基于當前的狀態(tài)。比如當出現(xiàn)新的 Pod 進行調度時,調度程序會根據(jù)其當時對 Kubernetes 集群的資源描述做出最佳調度決定。

    但是 Kubernetes 集群是非常動態(tài)的。比如一個節(jié)點為了維護,我們先執(zhí)行了驅逐操作,這個節(jié)點上的所有 Pod 會被驅逐到其他節(jié)點去,當我們維護完成后,之前的 Pod 并不會自動回到該節(jié)點上來,因為 Pod 一旦被綁定了節(jié)點是不會觸發(fā)重新調度的。

最佳實踐

由上面的分析我們可以看到,集群的穩(wěn)定性直接決定了其上運行的業(yè)務應用的穩(wěn)定性。而臨時性的資源短缺往往是導致集群不穩(wěn)定的主要因素。集群一旦不穩(wěn)定,輕則業(yè)務應用的性能下降,重則出現(xiàn)相關結點不可用。

那么如何提高集群的穩(wěn)定性呢?

一方面,可以通過編輯 Kubelet 配置文件來預留一部分系統(tǒng)資源,從而保證當可用計算資源較少時 kubelet 所在節(jié)點的穩(wěn)定性。這在處理如內存和硬盤之類的不可壓縮資源時尤為重要。

另一方面,通過合理地設置 Pod 的 QoS 可以進一步提高集群穩(wěn)定性:不同 QoS 的 Pod 具有不同的 OOM 分數(shù),當出現(xiàn)資源不足時,集群會優(yōu)先 Kill 掉 Best-Effort 類型的 Pod ,其次是 Burstable 類型的 Pod ,最后是Guaranteed 類型的 Pod 。

因此,如果資源充足,可將 QoS pods 類型均設置為 Guaranteed 。用計算資源換業(yè)務性能和穩(wěn)定性,減少排查問題時間和成本。同時如果想更好的提高資源利用率,業(yè)務服務也可以設置為 Guaranteed ,而其他服務根據(jù)重要程度可分別設置為 BurstableBest-Effort 。

下面我們會以 Kubesphere 平臺為例,演示如何方便優(yōu)雅地配置 Pod 相關的資源。

KubeSphere 資源配置實踐

前面我們已經(jīng)了解到 Kubernetes 中requestslimits這 2 個參數(shù)的合理設置對整個集群的穩(wěn)定性至關重要。而作為 Kubernetes 的發(fā)行版,KubeSphere 極大地降低了 Kubernetes 的學習門檻,配合簡潔美觀的 UI 界面,你會發(fā)現(xiàn)有效運維原來是一件如此輕松的事情。下面我們將演示如何在 KubeSphere 平臺中配置容器的相關資源配額與限制。

相關概念

在進行演示之前,讓我們再回顧一下 Kubernetes 相關概念。

requests 與 limits 簡介

為了實現(xiàn) Kubernetes 集群中資源的有效調度和充分利用, Kubernetes 采用requestslimits兩種限制類型來對資源進行容器粒度的分配。每一個容器都可以獨立地設定相應的requestslimits。這 2 個參數(shù)是通過每個容器 containerSpec 的 resources 字段進行設置的。一般來說,在調度的時候requests比較重要,在運行時limits比較重要。

resources:  
    requests:    
        cpu: 50m
        memory: 50Mi
   limits:    
        cpu: 100m
        memory: 100Mi

requests定義了對應容器需要的最小資源量。這句話的含義是,舉例來講,比如對于一個 Spring Boot 業(yè)務容器,這里的requests必須是容器鏡像中 JVM 虛擬機需要占用的最少資源。如果這里把 Pod 的內存requests指定為 10Mi ,顯然是不合理的,JVM 實際占用的內存 Xms 超出了 Kubernetes 分配給 Pod 的內存,導致 Pod 內存溢出,從而 Kubernetes 不斷重啟 Pod 。

limits定義了這個容器最大可以消耗的資源上限,防止過量消耗資源導致資源短缺甚至宕機。特別的,設置為 0 表示對使用的資源不做限制。值得一提的是,當設置limits而沒有設置requests時,Kubernetes 默認令requests等于limits

進一步可以把requestslimits描述的資源分為 2 類:可壓縮資源(例如 CPU )和不可壓縮資源(例如內存)。合理地設置limits參數(shù)對于不可壓縮資源來講尤為重要。

前面我們已經(jīng)知道requests參數(shù)會對最終的 Kubernetes 調度結果起到直接的顯而易見的影響。借助于 Linux 內核 Cgroup 機制,limits參數(shù)實際上是被 Kubernetes 用來約束分配給進程的資源。對于內存參數(shù)而言,實際上就是告訴 Linux 內核什么時候相關容器進程可以為了清理空間而被殺死( oom-kill )。

總結一下:

  • 對于 CPU,如果 Pod 中服務使用 CPU 超過設置的limits,Pod 不會被 kill 掉但會被限制。如果沒有設置 limits ,pod 可以使用全部空閑的 CPU 資源。

  • 對于內存,當一個 Pod 使用內存超過了設置的limits,Pod 中 container 的進程會被 kernel 因 OOM kill 掉。當 container 因為 OOM 被 kill 掉時,系統(tǒng)傾向于在其原所在的機器上重啟該 container 或本機或其他重新創(chuàng)建一個 Pod。

  • 0 <= requests <=Node Allocatable, requests <= limits <= Infinity

Pod 的服務質量( QoS )

Kubernetes 創(chuàng)建 Pod 時就給它指定了下列一種 QoS 類:Guaranteed,Burstable,BestEffort。

  • Guaranteed:Pod 中的每個容器,包含初始化容器,必須指定內存和 CPU 的requestslimits,并且兩者要相等。

  • Burstable:Pod 不符合 Guaranteed QoS 類的標準;Pod 中至少一個容器具有內存或 CPU requests。

  • BestEffort:Pod 中的容器必須沒有設置內存和 CPU requestslimits

結合結點上 Kubelet 的 CPU 管理策略,可以對指定 Pod 進行綁核操作,參見官方文檔。

準備工作

您需要創(chuàng)建一個企業(yè)空間、一個項目和一個帳戶 ( ws-admin ),務必邀請該帳戶到項目中并賦予 admin 角色。有關更多信息,請參見創(chuàng)建企業(yè)空間、項目、帳戶和角色。

設置項目配額( Resource Quotas )

  1. 進入項目基本信息界面,依次直接點擊“項目管理 -> 編輯配額”進入項目的配額設置頁面。

Kubernetes 中的 requests 和 limits 的概念是什么

  1. 進入項目配額頁面,為該項目分別指定requestslimits配額。

Kubernetes 中的 requests 和 limits 的概念是什么

設置項目配額的有 2 方面的作用:

  • 限定了該項目下所有 pod 指定的requestslimits之和分別要小于等與這里指定的項目的總requestslimits。

  • 如果在項目中創(chuàng)建任何一個容器沒有指定requests或者limits,那么相應的資源會創(chuàng)建報錯,并會以事件的形式給出報錯提示。

可以看到,設定項目配額以后,在該項目中創(chuàng)建任何容器都需要指定requestslimits,隱含實現(xiàn)了所謂的“code is law”,即人人都需要遵守的規(guī)則。

Kubesphere 中的項目配額等價于 Kubernetes 中的 resource quotas ,項目配額除了能夠以項目為單位管理 CPU 和內存的使用使用分配情況,還能夠管理其他類型的資源數(shù)目等,詳細信息參見資源配額。

設置容器資源的默認請求

上面我們已經(jīng)討論過項目中開啟了配額以后,那么之后創(chuàng)建的 Pod 必須明確指定相應的 requestslimits 。事實上,在實際的測試或者生產環(huán)境當中,大部分 Pod 的 requestslimits 是高度相近甚至完全相同的。

有沒有辦法在項目中,事先設定好默認的缺省 requestslimits ,當用戶沒有指定容器的 requestslimits 時,直接應用默認值,若 Pod 已經(jīng)指定 requestslimits 是否直接跳過呢?答案是肯定的。

  1. 進入項目基本信息界面,依次直接點擊“項目管理 -> 編輯資源默認請求”進入項目的默認請求設置頁面。

Kubernetes 中的 requests 和 limits 的概念是什么

  1. 進入項目配額頁面,為該項目分別指定 CPU 和內存的默認值。

Kubernetes 中的 requests 和 limits 的概念是什么

KubeSphere 中的項目容器資源默認請求是借助于 Kubernetes 中的 Limit Ranges ,目前 KubeSphere 支持 CPU 和內存的requestslimits的默認值設定。

前面我們已經(jīng)了解到,對于一些關鍵的業(yè)務容器,通常其流量和負載相比于其他 Pod 都是比較高的,對于這類容器的requestslimits需要具體問題具體分析。

分析的維度是多個方面的,例如該業(yè)務容器是 CPU 密集型的,還是 IO 密集型的。是單點的還是高可用的,這個服務的上游和下游是誰等等。

另一方面,在生產環(huán)境中這類業(yè)務容器的負載從時間維度看的話,往往是具有周期性的。因此,業(yè)務容器的歷史監(jiān)控數(shù)據(jù)可以在參數(shù)設置方面提供重要的參考價值。

而 KubeSphere 在最初的設計中,就已經(jīng)在架構層面考慮到了這點,將 Prometheus 組件無縫集成到 KubeSphere 平臺中,并提供縱向上至集群層級,下至 Pod 層級的完整的監(jiān)控體系。橫向涵蓋 CPU ,內存,網(wǎng)絡,存儲等。

一般,requests值可以設定為歷史數(shù)據(jù)的均值,而limits要大于歷史數(shù)據(jù)的均值,最終數(shù)值還需要結合具體情況做一些小的調整。

源碼分析

前面我們從日常 Kubernetes 運維出發(fā),描述了由于 requestslimits參數(shù)配置不當而引起的一系列問題,闡述了問題產生的原因并給出的最佳實踐。

下面我們將深入到 Kubernetes 內部,從代碼里表征的邏輯關系來進一步分析和驗證上面給出的結論。

requests 是如何影響 Kubernetes 調度決策的?

我們知道在 Kubernetes 中 Pod 是最小的調度單位,Pod 的requests與 Pod 內容器的requests關系如下:

func computePodResourceRequest(pod *v1.Pod) *preFilterState {
	result := &preFilterState{}
	for _, container := range pod.Spec.Containers {
		result.Add(container.Resources.Requests)
	}

	// take max_resource(sum_pod, any_init_container)
	for _, container := range pod.Spec.InitContainers {
		result.SetMaxResource(container.Resources.Requests)
	}

	// If Overhead is being utilized, add to the total requests for the pod
	if pod.Spec.Overhead != nil && utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) {
		result.Add(pod.Spec.Overhead)
	}

	return result
}
...
func (f *Fit) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) *framework.Status {
	cycleState.Write(preFilterStateKey, computePodResourceRequest(pod))
	return nil
}
...
func getPreFilterState(cycleState *framework.CycleState) (*preFilterState, error) {
	c, err := cycleState.Read(preFilterStateKey)
	if err != nil {
		// preFilterState doesn't exist, likely PreFilter wasn't invoked.
		return nil, fmt.Errorf("error reading %q from cycleState: %v", preFilterStateKey, err)
	}

	s, ok := c.(*preFilterState)
	if !ok {
		return nil, fmt.Errorf("%+v  convert to NodeResourcesFit.preFilterState error", c)
	}
	return s, nil
}
...
func (f *Fit) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
	s, err := getPreFilterState(cycleState)
	if err != nil {
		return framework.NewStatus(framework.Error, err.Error())
	}

	insufficientResources := fitsRequest(s, nodeInfo, f.ignoredResources, f.ignoredResourceGroups)

	if len(insufficientResources) != 0 {
		// We will keep all failure reasons.
		failureReasons := make([]string, 0, len(insufficientResources))
		for _, r := range insufficientResources {
			failureReasons = append(failureReasons, r.Reason)
		}
		return framework.NewStatus(framework.Unschedulable, failureReasons...)
	}
	return nil
}

從上面的源碼中不難看出,調度器(實際上是 Schedule thread )首先會在 Pre filter 階段計算出待調度 Pod 所需要的資源,具體講就是從 Pod Spec 中分別計算初始容器和工作容器requests之和,并取其較大者,特別地,對于像 Kata-container 這樣的微虛機,其自身的虛擬化開銷相比于容器來說是不能忽略不計的,所以還需要加上虛擬化本身的資源開銷,計算出的結果存入到緩存中,在緊接著的 Filter 階段,會遍歷所有節(jié)點過濾出符合條件的節(jié)點。

在過濾出所有符合條件的節(jié)點以后,如果當前滿足的條件的節(jié)點只有一個,那么該 Pod 隨后將被調度到該節(jié)點。但是更多的情況下,此時過濾之后符合條件的節(jié)點往往有多個,這時候就需要進入 Score 階段,依次對這些節(jié)點進行打分( Score )。而打分本身也是包括多個維度,通過內置 plugin 的形式綜合評判的。值得注意的是,前面我們定義的 Pod 的requestslimits參數(shù)也會直接影響到NodeResourcesLeastAllocated算法最終的計算結果。源碼如下:

func leastResourceScorer(resToWeightMap resourceToWeightMap) func(resourceToValueMap, resourceToValueMap, bool, int, int) int64 {
	return func(requested, allocable resourceToValueMap, includeVolumes bool, requestedVolumes int, allocatableVolumes int) int64 {
		var nodeScore, weightSum int64
		for resource, weight := range resToWeightMap {
			resourceScore := leastRequestedScore(requested[resource], allocable[resource])
			nodeScore += resourceScore * weight
			weightSum += weight
		}
		return nodeScore / weightSum
	}
}
...
func leastRequestedScore(requested, capacity int64) int64 {
	if capacity == 0 {
		return 0
	}
	if requested > capacity {
		return 0
	}

	return ((capacity - requested) * int64(framework.MaxNodeScore)) / capacity
}

可以看到在NodeResourcesLeastAllocated算法中,對于同一個 Pod ,目標節(jié)點的資源越充裕,那么該節(jié)點的得分也就越高。換句話說,同一個 Pod 更傾向于調度到資源充足的節(jié)點。

需要注意的是,實際上在創(chuàng)建 Pod 的過程中,一方面, Kubernetes 需要撥備包含 CPU 和內存在內的多種資源。每種資源都會對應一個權重(對應源碼中的 resToWeightMap 數(shù)據(jù)結構),所以這里的資源均衡是包含 CPU 和內存在內的所有資源的綜合考量。另一方面,在 Score 階段,除了NodeResourcesLeastAllocated算法以外,調用器還會使用到其他算法(例如InterPodAffinity)進行分數(shù)的評定。

注:在 Kubernetes 調度器中,會把調度過程分為若干個階段,即 Pre filter, Filter, Post filter, Score 等。在 Pre filter 階段,用于選擇符合 Pod Spec 描述的 Nodes 。

QoS 是如何影響 Kubernetes 調度決策的?

QOS 作為 Kubernetes 中一種資源保護機制,主要是針對不可壓縮資源的一種控制技術。比如在內存中其通過為不同的 Pod 和容器構造 OOM 評分,并且通過內核的策略的輔助,從而實現(xiàn)當節(jié)點內存資源不足的時候,內核可以按照策略的優(yōu)先級,優(yōu)先 kill 掉優(yōu)先級比較低(分值越高優(yōu)先級越低)的 Pod。相關源碼如下:

func GetContainerOOMScoreAdjust(pod *v1.Pod, container *v1.Container, memoryCapacity int64) int {
	if types.IsCriticalPod(pod) {
		// Critical pods should be the last to get killed.
		return guaranteedOOMScoreAdj
	}

	switch v1qos.GetPodQOS(pod) {
	case v1.PodQOSGuaranteed:
		// Guaranteed containers should be the last to get killed.
		return guaranteedOOMScoreAdj
	case v1.PodQOSBestEffort:
		return besteffortOOMScoreAdj
	}

	// Burstable containers are a middle tier, between Guaranteed and Best-Effort. Ideally,
	// we want to protect Burstable containers that consume less memory than requested.
	// The formula below is a heuristic. A container requesting for 10% of a system's
	// memory will have an OOM score adjust of 900. If a process in container Y
	// uses over 10% of memory, its OOM score will be 1000. The idea is that containers
	// which use more than their request will have an OOM score of 1000 and will be prime
	// targets for OOM kills.
	// Note that this is a heuristic, it won't work if a container has many small processes.
	memoryRequest := container.Resources.Requests.Memory().Value()
	oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity
	// A guaranteed pod using 100% of memory can have an OOM score of 10. Ensure
	// that burstable pods have a higher OOM score adjustment.
	if int(oomScoreAdjust) < (1000 + guaranteedOOMScoreAdj) {
		return (1000 + guaranteedOOMScoreAdj)
	}
	// Give burstable pods a higher chance of survival over besteffort pods.
	if int(oomScoreAdjust) == besteffortOOMScoreAdj {
		return int(oomScoreAdjust - 1)
	}
	return int(oomScoreAdjust)
}

總結

Kubernetes 是一個具有良好移植和擴展性的開源平臺,用于管理容器化的工作負載和服務。 Kubernetes 擁有一個龐大且快速增長的生態(tài)系統(tǒng),已成為容器編排領域的事實標準。但是也不可避免地引入許多復雜性。

而 KubeSphere 作為國內唯一一個開源的 Kubernetes 發(fā)行版,極大地降低了使用 Kubernetes 的門檻。借助于 KubeSphere 平臺,原先需要通過后臺命令行和 yaml 文件管理的系統(tǒng)配置,現(xiàn)在只需要在簡潔美觀的 UI 界面上輕松完成。

到此,相信大家對“Kubernetes 中的 requests 和 limits 的概念是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI