kubernetes kubelet組件中cgroup的層層"戒備"


cgroup是linux內核中用於實現資源使用限制和統計的模塊,docker的風靡一時少不了cgroup等特性的支持。kubernetes作為容器編排引擎,除了借助docker進行容器進程的資源管理外,還提供了一些更加高級的資源管理功能,以提高資源利用率和更加穩定的程序運行環境,其中必然少不了cgroup這類資源管控技術的應用,那么kubernetes是如何使用cgroup哪? 如果仔細觀察kubelet中關於cgroup的配置,就會發現這些配置參數多達十幾個,錯綜復雜,怎樣才能合理的配置這些參數哪?

kubelet作為kubernetes中的node agent,所有cgroup的操作都由其內部的containerManager模塊實現,containerManager會通過cgroup將資源使用層層限制: container-> pod-> qos -> node。每一層都抽象出一種資源管理模型,通過這種方式提供了一種穩定的運行環境。

Conainer level cgroups

kubernetes對於容器級別的隔離其實是交由底層的runtime來負責的,例如docker, 當我們指定運行容器所需要資源的request和limit時,docker會為容器設置進程所運行cgroup的cpu.share, cpu.quota, cpu.period, mem.limit等指標來,具體cgroup中各個參數含義處不再分析,感興趣的同學可以查閱相關資料。

Pod level cgroups

一個pod中往往有一個或者有多個容器,但是如果我們將這些容器的資源使用進行簡單的加和並不能准確的反應出整個pod的資源使用,因為每個pod都會有一些overhead的資源,例如sandbox容器使用的資源,docker的containerd-shim使用的資源,此外如果指定memory類型的volume時,這部分內存資源也是屬於該pod占用的。因為這些資源並不屬於某一個特定的容器,我們無法僅僅通過容器的資源使用量簡單累加獲取到整個pod的資源,為了方便統計一個pod所使用的資源(resource accounting),並且合理的將所有使用到的資源都納入管轄范圍內,kubernetes引入了pod level Cgroup,會為每個pod創建一個cgroup。該特性通過指定--cgroups-per-qos=true開啟, 在1.6+版本中是默認開啟。kubelet會為每個pod創建一個`pod<pod.UID>`的cgroup,該cgroup的資源限制取決於pod中容器的資源request,limit值。

  • 如果為所有容器都指定了request和limit值,則pod cgroups資源值設置為所有容器的加和,即:
pod<UID>/cpu.shares = sum(pod.spec.containers.resources.requests[cpu])
pod<UID>/cpu.cfs_quota_us = sum(pod.spec.containers.resources.limits[cpu])
pod<UID>/memory.limit_in_bytes = sum(pod.spec.containers.resources.limits[memory])
  • 如果其中某個容器只指定了request沒有指定limit則並不會設置pod cgroup的limit值, 只設置其cpu.share值:
pod<UID>/cpu.shares = sum(pod.spec.containers.resources.requests[cpu])
  • 如果所有容器沒有指定request和limit值,則只設置pod cgroup的cpu.share, 該pod在資源空閑的時候可以使用完node所有的資源,但是當資源緊張的時候無法獲取到任何資源來執行,這也符合低優先級任務的定位:
pod<UID>/cpu.shares = 2

其實上面三種設置方式對應的就是三種QoS pod。 這樣設置pod level cgourp可以確保在合理指定容器資源時能夠防止資源的超量使用, 如果未指定則可以使用到足夠多的可用資源。 每次啟動pod時kubelet就會同步對應的pod level cgroup。

QoS level cgroup

kubernetes中會將所有的pod按照資源request, limit設置分為不同的QoS classes, 從而擁有不同的優先級。如果指定了--cgroups-per-qos也會為每個QoS也會對應一個cgroup,該功能默認開啟,這樣就可以利用cgroup來做一些QoS級別的資源統計,必要時也可以通過該cgroup限制某個QoS級別的pod能使用的資源總和。此時每個QoS cgroup相當於一個資源pool, 內部的pod可以共用pool中的資源,但是對於整個pool會進行一些資源的限制,避免在資源緊張時低優先級的pod搶占高優先級的pod的資源。對於guaranteed級別的pod,因為pod本身已經指定了request和limit,擁有了足夠的限制,無需再增加cgroup來約束。但是對於BurstableBestEffort類型的pod,因為有的pod和容器沒有指定資源限制,在極端條件下會無限制的占用資源,所以我們需要分別設置BurstableBestEffortcgroup, 然后將對應的pod都創建在該cgroup下。kubelet希望盡可能提高資源利用率,讓BurstableBestEffort類型的pod在需要的時候能夠使用足夠多的空閑資源,所以默認並不會為該QoS設置資源的limit。但是也需要保證當高優先級的pod需要使用資源時,低優先級的pod能夠及時將資源釋放出來:對於可壓縮的資源例如CPU, kubelet會通過CPU CFS shares來控制,當CPU資源緊張時通過CFS share來將資源按照比例分配給各個QoS pod,保證每個pod都能夠得到其所申請的資源。具體來說: 對於cpu的設置,besteffort和burstable的資源使用限制如下:

ROOT/besteffort/cpu.shares = 2
ROOT/burstable/cpu.shares = max(sum(Burstable pods cpu requests, 2)

對於不可壓縮資源內存,要滿足"高優先級pod使用資源時及時釋放低優先級的pod占用的資源"就比較困難了,kubelet只能通過資源預留的機制,為高優先級的pod預留一定的資源,該特性默認關閉,用戶可以通過--qos-reserved來設置預留的資源比例,例如--qos-reserved=memory=50%表示預留50%高優先級request的資源值,當前只支持memory, 此時qos cgroups的限制如下:

ROOT/burstable/memory.limit_in_bytes = 
    Node.Allocatable - {(summation of memory requests of `Guaranteed` pods)*(reservePercent / 100)}
ROOT/besteffort/memory.limit_in_bytes = 
   Node.Allocatable - {(summation of memory requests of all `Guaranteed` and `Burstable` pods)*(reservePercent / 100)}

同時根據 cpu.shares 的背后實現原理,位於不同層級下面的 cgroup,他們看待同樣數量的 cpu.shares 配置可能最終獲得不同的資源量。比如在 Guaranteed 級別的 pod cgroup 里面指定的 cpu.shares=1024,和 burstable 下面的某個 pod cgroup 指定 cpu.shares=1024 可能最終獲取的 cpu 資源並不完全相同。所以每次創建、刪除pod都需要根據上述公式動態計算cgroup值並進行調整。此時kubelet先會盡力去更新低優先級的pod,給高優先級的QoS預留足夠的資源。因為memory是不可壓縮資源,可能當pod啟動時,低優先級的pod使用的資源已經超過限制了,如果此時直接設置期望的值會導致失敗,此時kubelet會盡力去設置一個能夠設置的最小值(即當前cgroup使用的資源值),避免資源使用進一步增加。通過設置qos資源預留能夠保障高優先級的資源可用性,但是對低優先級的任務可能不太友好,官方默認是關閉該策略,可以根據不同的任務類型合理取舍。

Node level cgroups

對於node層面的資源,kubernetes會將一個node上面的資源按照使用對象分為三部分: 

  1. 業務進程使用的資源, 即pods使用的資源; 
  2. kubernetes組件使用的資源,例如kubelet, docker; 
  3. 系統組件使用的資源,例如logind, journald等進程。 

通常情況下,我們為提高集群資源利用率,會進行適當超配資源,如果控制不當,業務進程可能會占用完整個node的資源,從而使的第二,三部分核心的程序所使用的資源受到壓制,從而影響到系統穩定性,為避免這樣的情況發生,我們需要合理限制pods的資源使用,從而為系統組件等核心程序預留足夠的資源,保證即使在極端條件下有充足的資源來使用。

kubelet會將所有的pod都創建一個kubepods的cgroup下,通過該cgroup來限制node上運行的pod最大可以使用的資源。該cgroup的資源限制取值為: ${Node Capacity} - ${Kube-Reserved} - ${System-Reserved},其中kube-reserved是為kubernetes組件提供的資源預留,system-reserved是為系統組件預留的資源,分別通過--kube-reserved, --system-reserved來指定,例如--kube-reserved=cpu=100m,memory=100Mi

除了指定預留給系統運行的資源外, 如果要限制系統運行的資源,可以通過--enforce-node-allocatable來設置,該flag指定需要執行限制的資源類型,默認值為pods,即通過上述kubepods來限制pods的使用資源,此外還支持限制的資源類型有:

  • system-reserved: 限制kubernetes組件的資源使用,如果開啟該限制,則需要同時設置--kube-reserved-cgroup參數指定所作用的cgroup 
  • kube-reserved: 限制系統組件的資源使用,如果開啟該限制則需要同時設置--system-reserved-cgroup
    - none: 不進行任何資源限制

如果需要指定多種類型,通過逗號分割枚舉即可,注意如果開啟了system-reservedkube-reserved的限制,則意味着將限制這些核心組件的資源使用,以上述--kube-reserved=cpu=100m,memory=100Mi為例,所有的kubernetes組件最多可以使用cpu: 100m,memory: 100Mi。 對此我們需要格外小心了,除非已經很了解自己的資源使用屬性,否則並不建議對這兩種資源進行限制,避免核心組件CPU飢餓或者內存OOM。
默認情況下該--enforce-node-allocatable的值為pods,即只限制容器使用的資源,但不限制系統進程和kubernetes進程的資源使用量。

kubelet會在資源緊張的時候主動驅逐低優先級的pod,可以指定{Hard-Eviction-Threshold}來設置閾值,這樣一個node真正可以為pod使用的資源量為: ${Allocatable} = ${Node Capacity} - ${Kube-Reserved} - ${System-Reserved} - ${Hard-Eviction-Threshold}, 這也是調度器進行調度時所使用的資源值。

核心組件的Cgroup

除了上述提到的cgroup設置外,kubelet中還有一些對於單個組件的cgroup設置, 例如:

  • --runtime-cgroups: 用來指定docker等runtime運行的Cgroup。目前docker-CRI的實現dockershim會管理該cgroup和oom score, 確保dockerd和docker-containerd進程是運行在該cgroup之內,這里會對內存進行限制,使其最大使用宿主機70%的內存,主要是為了防止docker之前內存泄露的bug。 kubelet在此處只是不斷獲取該cgroup信息供kuelet SummarProvider進行獲取統計信息,從而通過summary api暴露出去。
  • --system-cgroups: 將所有系統進程都移動到該cgroup下,會進行統計資源使用。如果不指定該參數則不運行在容器中,對應的summary stat數據也不會統計。此處系統進程不包括內核進程, 因為我們並不想限制內核進程的使用。
  • --kubelet-cgroups: 如果指定改參數,則containerManager會確保kubelet在該cgroup內運行,同樣也會做資源統計,也會調整OOM score值。 summaryProvider會定期同步信息,來獲取stat信息。如果不指定該參數,則kubelet會自動地找到kubelet所在的cgroup, 並進行資源的統計。
  • --cgroup-root kubelet中所有的cgroup層級都會在該root路徑下,默認是/,如果開啟--cgroups-per-qos=true,則在kubelet containerManager中會調整為/kubepods

以上runtime-cgroups, system-cgroups, kubelet-cgoups的設置都是可選的,如果不進行指定也可以正常運行。但是如果顯式指定后就需要與前面提到的--kube-reserved-cgroup--system-reserved-cgroup搭配使用,如果配置不當難以達到預期效果:

如果在--enforce-node-allocatable參數中指定了kube-reserved來限制kubernetes組件的資源限制后,kube-reserved-cgroup的應該是:runtime-cgroups, kubelet-cgoups的父cgroup。只有對應的進程都應該運行該cgroup之下,才能進行限制, kubelet會設置kube-reserved-cgroup的資源限制但並不會將這些進程加入到該cgroup中,我們要想讓該配置生效,就必須讓通過制定--runtime-cgroups, --kubelet-cgoups來將這些進程加入到該cgroup中。同理如果上述--enforce-node-allocatable參數中指定了system-reserved來限制系統進程的資源,則--system-reserved-cgroup 的參數應該與--system-cgroups參數相同,這樣系統進程才會運行到system-reserved-cgroup中起到資源限制的作用。

最后整個整個cgroup hierarchy 如下:

root
 | 
 +- kube-reserved
 |   |
 |	 +- kubelet (kubelet process)
 |   | 
 |	 +- runtime (docker-engine, containerd...)
 |
 +- system-reserved (systemd process: logind...)
 |
 +- kubepods
 |    |
 |    +- Pod1
 |    |   |
 |    |   +- Container11 (limit: cpu: 10m, memory: 1Gi)
 |    |   |     |
 |    |   |     +- cpu.quota: 10m
 |    |   |     +- cpu.share: 10m
 |    |   |     +- mem.limit: 1Gi
 |    |   |
 |    |   +- Container12 (limit: cpu: 100m, memory: 2Gi)
 |    |   |     |
 |    |   |     +- cpu.quota: 10m
 |    |   |     +- cpu.share: 10m
 |    |   |     +- mem.limit: 2Gi
 |    |   |
 |    |   +- cpu.quota: 110m  
 |    |   +- cpu.share: 110m
 |    |   +- mem.limit: 3Gi
 |    |
 |    +- Pod2
 |    |   +- Container21 (limit: cpu: 20m, memory: 2Gi)
 |    |   |     |
 |    |   |     +- cpu.quota: 20m
 |    |   |     +- cpu.share: 20m
 |    |   |     +- mem.limit: 2Gi
 |    |   |
 |    |   +- cpu.quota: 20m  
 |    |   +- cpu.share: 20m
 |    |   +- mem.limit: 2Gi
 |    |
 |    +- burstable
 |    |   |
 |    |   +- Pod3
 |    |   |   |
 |    |   |   +- Container31 (limit: cpu: 50m, memory: 2Gi; request: cpu: 20m, memory: 1Gi )
 |    |   |   |     |
 |    |   |   |     +- cpu.quota: 50m
 |    |   |   |     +- cpu.share: 20m
 |    |   |   |     +- mem.limit: 2Gi
 |    |   |   |
 |    |   |   +- Container32 (limit: cpu: 100m, memory: 1Gi)
 |    |   |   |     |
 |    |   |   |     +- cpu.quota: 100m
 |    |   |   |     +- cpu.share: 100m
 |    |   |   |     +- mem.limit: 1Gi
 |    |   |   |
 |    |   |   +- cpu.quota: 150m  
 |    |   |   +- cpu.share: 120m
 |    |   |   +- mem.limit: 3Gi
 |    |   |
 |    |   +- Pod4
 |    |   |   +- Container41 (limit: cpu: 20m, memory: 2Gi; request: cpu: 10m, memory: 1Gi )
 |    |   |   |     |
 |    |   |   |     +- cpu.quota: 20m
 |    |   |   |     +- cpu.share: 10m
 |    |   |   |     +- mem.limit: 2Gi
 |    |   |   |
 |    |   |   +- cpu.quota: 20m  
 |    |   |   +- cpu.share: 10m
 |    |   |   +- mem.limit: 2Gi
 |    |   |
 |    |   +- cpu.share: 130m
 |    |   +- mem.limit: $(Allocatable - 5Gi)
 |    |
 |    +- besteffort
 |    |   |
 |    |   +- Pod5
 |    |   |   |
 |    |   |   +- Container6 
 |    |   |   +- Container7
 |    |   |
 |    |   +- cpu.share: 2
 |    |   +- mem.limit: $(Allocatable - 7Gi)



上述所有的操作在kubelet中是通過containerManager來實現的, containerManager啟動的時候首先會setupNode初始化各種cgroup,具體包括:通過enforceNodeAllocatableCgroups來設置kubepods, kube-reservedsystem-reserved三個cgroup的資源使用, 啟動qosContainerManager來定期同步各個QoS class的資源使用,會在后台不斷同步kubelet,docker,system cgroup。在每個pod啟動/退出時候會調用podContainerManager來創建/刪除pod級別的cgroup並調用 UpdateQoSCgroups 來更新QoS級別的cgroup。 上述所有更新cgroup的操作都會利用一個cgroupManager來實現。

Summary

可以看出kubelet是將cgroup使用到了極致的地步,通過層層限制提高node的隔離性、穩定性、可觀測性。


免責聲明!

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



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