本文介紹幾種在K8S中限制資源使用的幾種方法。
資源類型
在K8S中可以對兩類資源進行限制:cpu和內存。
CPU的單位有:
正實數
,代表分配幾顆CPU,可以是小數點,比如0.5
代表0.5顆CPU,意思是一 顆CPU的一半時間。2
代表兩顆CPU。正整數m
,也代表1000m=1
,所以500m
等價於0.5
。
內存的單位:
正整數
,直接的數字代表Bytek
、K
、Ki
,Kilobytem
、M
、Mi
,Megabyteg
、G
、Gi
,Gigabytet
、T
、Ti
,Terabytep
、P
、Pi
,Petabyte
方法一:在Pod Container Spec中設定資源限制
在K8S中,對於資源的設定是落在Pod里的Container上的,主要有兩類,limits
控制上限,requests
控制下限。其位置在:
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
舉例:
apiVersion: v1 kind: Pod metadata: name: frontend spec: containers: - name: ... image: ... resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m"
方法二:在Namespace中限定
方法一雖然很好,但是其不是強制性的,因此很容易出現因忘記設定limits
/request
,導致Host資源使用過度的情形,因此我們需要一種全局性的資源限制設定,以防止這種情況發生。K8S通過在Namespace
設定LimitRange
來達成這一目的。
配置默認request
/limit
:
如果配置里默認的request
/limit
,那么當Pod Spec沒有設定request
/limit
的時候,會使用這個配置,有效避免無限使用資源的情況。
配置位置在:
spec.limits[].default.cpu
,default limitspec.limits[].default.memory
,同上spec.limits[].defaultRequest.cpu
,default requestspec.limits[].defaultRequest.memory
,同上
例子:
apiVersion: v1 kind: LimitRange metadata: name: <name> spec: limits: - default: memory: 512Mi cpu: 1 defaultRequest: memory: 256Mi cpu: 0.5 type: Container
配置request
/limit
的約束
我們還可以在K8S里對request
/limit
進行以下限定:
- 某資源的
request
必須>=某值
- 某資源的
limit
必須<=某值
這樣的話就能有效避免Pod Spec中亂設limit
導致資源耗盡的情況,或者亂設request
導致Pod無法得到足夠資源的情況。
配置位置在:
spec.limits[].max.cpu
,limit
必須<=某值
spec.limits[].max.memory
,同上spec.limits[].min.cpu
,request
必須>=某值
spec.limits[].min.memory
,同上
例子:
apiVersion: v1
kind: LimitRange
metadata: name: <name>
spec:
limits:
- max:
memory: 1Gi
cpu: 800m
min:
memory: 500Mi
cpu: 200m
type: Container
在關於kubernetes資源限制的這個由兩部分組成的系列文章的第一篇文章中,我討論了如何使用ResourceRequirements對象來設置容器中容器的內存限制,以及容器運行時和linux控制組如何實現這些限制。我還談到了請求之間的區別,用於在調度時通知調度程序pod的需求,以及限制,用於在主機系統處於內存壓力時幫助內核強制執行使用限制。在這篇文章中,我想繼續詳細查看cpu時間請求和限制。閱讀完第一篇文章並不是從這篇文章中獲取價值的先決條件,但我建議你在某些時候閱讀它們,以便全面了解工程師和集群管理員可以使用的控件。

CPU限制
正如我在第一篇文章中提到的,cpu限制比內存限制更復雜,原因將在下面說明。好消息是cpu限制是由我們剛才看到的相同cgroups機制控制的,所以所有相同的內省思想和工具都適用,我們可以只關注差異。讓我們首先將cpu限制添加回上次查看的示例資源對象:

單位后綴m代表“千分之一核心”,因此該資源對象指定容器進程需要50/1000的核心(5%)並且允許最多使用100/1000核心(10%)。同樣,2000m將是兩個完整核心,也可以指定為2或2.0。讓我們創建一個只有cpu請求的pod,看看它是如何在docker和cgroup級別配置的:
我們可以看到kubernetes配置了50m cpu請求:
我們還可以看到docker配置了具有相同限制的容器:

為什么51,而不是50?cpu控制組和docker都將核划分為1024個共享,而kubernetes將其划分為1000. docker如何將此請求應用於容器進程?與設置內存限制導致docker配置進程的內存cgroup的方式相同,設置cpu限制會導致它配置cpu,cpuacct cgroup:

Docker的HostConfig.CpuShares容器屬性映射到cgroup的cpu.shares屬性,所以讓我們看一下:

你可能會驚訝地發現設置cpu請求會將值傳播到cgroup,因為在上一篇文章中我們看到設置內存請求沒有。底線是關於內存軟限制的內核行為對kubernetes不是很有用,因為設置cpu.shares很有用。我將在下面詳細討論為什么。那么當我們設置cpu限制時會發生什么?我們來看看:
現在我們還可以在kubernetes pod資源對象中看到限制:
並在docker容器配置中:

如上所述,cpu請求存儲在HostConfig.CpuShares屬性中。但是,cpu限制不太明顯。它由兩個值表示:HostConfig.CpuPeriod和HostConfig.CpuQuota。這些docker容器配置屬性映射到進程的cpu、cpuacct cgroup的兩個附加屬性:cpu.cfs_period_us和cpu.cfs_quota_us。我們來看看那些:

正如預期的那樣,這些值設置為與docker容器配置中指定的值相同。但是這兩個屬性的值是如何從我們的pod中的100m cpu限制設置得出的,它們如何實現該限制?答案在於cpu請求和cpu限制是使用兩個獨立的控制系統實現的。請求使用cpu共享系統,兩者中較早的一個。Cpu共享將每個核划分為1024個切片,並保證每個進程將獲得這些切片的比例份額。如果有1024個切片,並且兩個進程中的每一個都將cpu.shares設置為512,那么它們將分別獲得大約一半的可用時間。但是,cpu共享系統無法強制執行上限。如果一個進程不使用其共享,則另一個進程可以自由使用。
大約在2010年,谷歌和其他人注意到這可能會導致問題。作為回應,增加了第二個功能更強大的系統:cpu帶寬控制。帶寬控制系統定義一個周期,通常為1/10秒,或100000微秒,以及一個配額,表示允許進程在cpu上運行的那個周期內的最大切片數。在這個例子中,我們要求我們的pod上的cpu限制為100m。這是100/1000的核,或100000微秒的CPU時間中的10000。因此,我們的限制請求轉換為在進程的cpu,cpuacct cgroup上設置cpu.cfs_period_us = 100000和cpu.cfs_quota_us = 10000。順便說一下,這些名稱中的cfs代表Completely Fair Scheduler,它是默認的linux cpu調度程序。還有一個實時調度程序,它有自己相應的配額值。
所以我們已經看到在kubernetes中設置cpu請求最終會設置cpu.shares cgroup屬性,並且通過設置cpu.cfs_period_us和cpu.cfs_quota_us來設置cpu限制可以使用不同的系統。與內存限制一樣,請求主要對調度程序有用,調度程序使用它來查找至少具有多個可用cpu共享的節點。與內存請求不同,設置cpu請求還會在cgroup上設置一個屬性,以幫助內核實際將該數量的共享分配給進程。限制也與內存區別對待。超出內存限制使你的容器進程成為oom-killing的候選者,而你的進程基本上不能超過設置的cpu配額,並且永遠不會因為嘗試使用比分配的更多的CPU時間而被驅逐。系統會在調度程序中強制執行配額,以便進程在限制時受到限制。
如果你未在容器上設置這些屬性,或將它們設置為不准確的值,會發生什么?與內存一樣,如果設置限制但不設置請求,kubernetes會將請求默認為限制。如果你非常了解工作負載所需的CPU時間,那么這可能會很好。設置一個沒有限制的請求怎么樣?在這種情況下,kubernetes能夠准確地安排你的pod,並且內核將確保它至少獲得所請求的共享數量,但是你的進程將不會被阻止使用超過所請求的cpu數量,這將被盜來自其他進程的cpu共享(如果可用)。既不設置請求也不設置限制是最糟糕的情況:調度程序不知道容器需要什么,並且進程對cpu共享的使用是無限的,這可能會對節點產生負面影響。這是我想要談論的最后一件事的好消息:確保命名空間中的默認限制。
默認限制
鑒於我們剛剛討論過關於忽略資源限制對pod容器的負面影響的所有內容,你可能認為能夠設置默認值會很好,因此允許進入群集的每個pod都至少設置了一些限制。Kubernetes允許我們使用LimitRange v1 api對象在每個命名空間的基礎上執行此操作。要建立默認限制,請在要將其應用於的命名空間中創建LimitRange對象。這是一個例子:

這里的命名可能有點令人困惑,所以讓我們簡單地把它拆掉。default key低於限制表示每個資源的默認限制。在這種情況下,任何允許沒有內存限制的命名空間的pod都將被分配100Mi的限制。任何沒有cpu限制的pod都將被分配100m的限制。defaultRequest鍵用於資源請求。如果在沒有內存請求的情況下創建pod,則會為其分配默認請求50Mi,如果沒有cpu請求,則默認值為50m。max和min鍵有點不同:基本上如果設置了pod,如果設置了違反這些邊界的請求或限制,則不會允許pod進入命名空間。我沒有找到這些的用途,但也許你有,如果是這樣發表評論,讓我們知道你對他們做了什么。
LimitRange中規定的默認值由LimitRanger插件應用於pod,這是一個kubernetes許可控制器。入場控制器是插件,在api接受對象之后但在創建pod之前有機會修改podSpecs。對於LimitRanger,它會查看每個pod,如果它沒有指定命名空間中存在默認設置的給定請求或限制,則它將應用該默認值。通過檢查pod元數據中的注釋,你可以看到LimitRanger已在你的pod上設置了默認值。這是LimitRanger應用100m的默認cpu請求的示例:

這包含了對kubernetes資源限制的看法。我希望這個信息對你有所幫助。如果你有興趣閱讀有關使用資源限制和默認值,linux cgroups或內存管理的更多信息,我已經提供了一些指向下面這些主題的更詳細信息的鏈接。