理解k8s資源限制系列(二):cpu time


本文介紹幾種在K8S中限制資源使用的幾種方法。

資源類型

在K8S中可以對兩類資源進行限制:cpu和內存。

CPU的單位有:

  • 正實數,代表分配幾顆CPU,可以是小數點,比如0.5代表0.5顆CPU,意思是一 顆CPU的一半時間。2代表兩顆CPU。
  • 正整數m,也代表1000m=1,所以500m等價於0.5

內存的單位:

  • 正整數,直接的數字代表Byte
  • kKKi,Kilobyte
  • mMMi,Megabyte
  • gGGi,Gigabyte
  • tTTi,Terabyte
  • pPPi,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 limit
  • spec.limits[].default.memory,同上
  • spec.limits[].defaultRequest.cpu,default request
  • spec.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.cpulimit必須<=某值
  • spec.limits[].max.memory,同上
  • spec.limits[].min.cpurequest必須>=某值
  • 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或內存管理的更多信息,我已經提供了一些指向下面這些主題的更詳細信息的鏈接。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM