<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在k8s中,kube-scheduler是Kubernetes中的排程器,用於將Pod排程到可用的節點上。在排程過程中,kube-scheduler需要了解節點和Pod的資源需求和可用性情況,其中CPU和記憶體是最常見的資源需求。那麼這些資源的使用率是怎麼來的呢?當Pod排程到節點上後,系統是如何約束Pod的資源使用而不影響其他Pod的?當資源使用率達到了申請的資源時,會發生什麼?下面,我們就這些問題,詳細展開說說。閱讀本文,你將瞭解到
用過k8s的同學應該都知道,我們在設定deployment的時候,我們一般都會為cpu和記憶體設定limit和request,那麼這個設定具體在節點上是怎麼限制的呢?
cpu的request、limit分別是1個核和4個核,記憶體的request、limit分別是1Gi和4Gi(Gi=1024Mi,G=1000Mi)。我們都知道,資源的限制時使用cgroup實現的,那麼Pod的資源是怎麼實現的呢?我們去Pod所在的節點看下。
k8s的cpu限制的cgroup目錄在 /sys/fs/cgroup/cpu/kubepods ,該目錄內容如下
我們能看到besteffort 和 burstable兩個目錄,這兩個目錄涉及Pod的QoS
QoS(Quality of Service),大部分譯為“服務質量等級”,又譯作“服務質量保證”,是作用在 Pod 上的一個設定,當 Kubernetes 建立一個 Pod 時,它就會給這個 Pod 分配一個 QoS 等級,可以是以下等級之一:
這個東西的作用就是,當節點上出現資源壓力的時候,會根據QoS的等級順序進行驅逐,驅逐順序為Guaranteed<Burstable<BestEffort。
我們的nginx對資源要求的設定根據上面的描述可以看到是Burstable型別的
我們進該目錄看下:
裡面的目錄表示屬於Burstable型別的Pod的cpu cgroup都設定在這個目錄,再進到Pod所在目錄,可以看到有2個目錄,每個目錄是容器的cpu cgroup目錄,一個是nginx本身的,另外一個Infra容器(沙箱容器)。
我們重點看下紅框內的三個檔案的含義。
cpu.shares用來設定CPU的相對值,並且是針對所有的CPU(核心),預設值是1024等同於一個cpu核心。 CPU Shares將每個核心劃分為1024個片,並保證每個程序將按比例獲得這些片的份額。如果有1024個片(即1核),並且兩個程序設定cpu.shares均為1024,那麼這兩個程序中每個程序將獲得大約一半的cpu可用時間。
當系統中有兩個cgroup,分別是A和B,A的shares值是1024,B 的shares值是512, 那麼A將獲得1024/(1024+512)=66%的CPU資源,而B將獲得33%的CPU資源。shares有兩個特點:
如果A不忙,沒有使用到66%的CPU時間,那麼剩餘的CPU時間將會被系統分配給B,即B的CPU使用率可以超過33%。
如果新增了一個新的cgroup C,且它的shares值是1024,那麼A的限額變成了1024/(1024+512+1024)=40%,B的變成了20%。
從上面兩個特點可以看出:
在閒的時候,shares不起作用,只有在CPU忙的時候起作用。
由於shares是一個絕對值,單單看某個組的share是沒有意義的,需要和其它cgroup的值進行比較才能得到自己的相對限額,而在一個部署很多容器的機器上,cgroup的數量是變化的,所以這個限額也是變化的,自己設定了一個高的值,但別人可能設定了一個更高的值,所以這個功能沒法精確的控制CPU使用率。從share這個單詞(共用的意思)的意思,我們也能夠體會到這一點。
cpu.shares對應k8s內的resources.requests.cpu欄位,值對應關係為:resources.requests.cpu * 1024 = cpu.share
cpu.cfs_period_us用來設定時間週期長度,cpu.cfs_quota_us用來設定當前cgroup在設定的週期長度內所能使用的CPU時間數。 兩個檔案配合起來設定CPU的使用上限。兩個檔案的單位都是微秒(us),cfs_period_us的取值範圍為1毫秒(ms)到1秒(s),cfs_quota_us的取值大於1ms即可,如果cfs_quota_us的值為-1(預設值),表示不受cpu時間的限制。
cpu.cpu.cfs_period_us、cpu.cfs_quota_us對應k8s中的resources.limits.cpu欄位:resources.limits.cpu = cpu.cfs_quota_us/cpu.cfs_period_us
可以看到上面的nginx的這兩個比值正好是4,表示nginx最多可以分配到4個CPU。此時,就算系統很空閒,上面說的share沒有發揮作用,也不會分配超時4個CPU,這就是上限的限制。
在平時設定的時候,limit和request兩者最好不要相差過大,否則節點CPU容易出現超賣情況,如limit/request=4,那麼在排程的時候發現節點是有資源的,一旦排程完成後,Pod可能會由於跑出超過request的CPU,那麼節點其他Pod可能就會出現資源”飢餓“情況,反映到業務就是請求反應慢。CPU 屬於可壓縮資源,記憶體屬於不可壓縮資源。當可壓縮資源不足時,Pod 會飢餓,但是不會退出;當不可壓縮資源不足時,Pod 就會因為 OOM 被核心殺掉。
這個問題還得從原始碼入手,首先我們看看kube-scheduler在排程的時候對於資源的判斷都做了哪些事。kube-scheduler會使用informer監聽叢集內node的變化,如果有變化(如Node的狀態,Node的資源情況等),則呼叫事件函數寫入本地index store(cache)中,程式碼如下:
func addAllEventHandlers{ ... informerFactory.Core().V1().Nodes().Informer().AddEventHandler( cache.ResourceEventHandlerFuncs{ AddFunc: sched.addNodeToCache, UpdateFunc: sched.updateNodeInCache, DeleteFunc: sched.deleteNodeFromCache, }, ) ...
如上,如果叢集內加入了新節點,則會呼叫addNodeToCache函數將Node資訊加入本地快取,那麼咱們來看看addNodeToCache函數:
func (sched *Scheduler) addNodeToCache(obj interface{}) { node, ok := obj.(*v1.Node) if !ok { klog.ErrorS(nil, "Cannot convert to *v1.Node", "obj", obj) return } nodeInfo := sched.Cache.AddNode(node) klog.V(3).InfoS("Add event for node", "node", klog.KObj(node)) sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(queue.NodeAdd, preCheckForNode(nodeInfo)) }
從上面的程式碼我們看到,在把該節點加入cache後,還會呼叫MoveAllToActiveOrBackoffQueue 函數,對在 unschedulablePods (還沒有排程的Pod佇列)中的Pod進行一次Precheck ,如果MoveAllToActiveOrBackoffQueue** 函數如下
func (p *PriorityQueue) MoveAllToActiveOrBackoffQueue(event framework.ClusterEvent, preCheck PreEnqueueCheck) { p.lock.Lock() defer p.lock.Unlock() unschedulablePods := make([]*framework.QueuedPodInfo, 0, len(p.unschedulablePods.podInfoMap)) for _, pInfo := range p.unschedulablePods.podInfoMap { if preCheck == nil || preCheck(pInfo.Pod) { unschedulablePods = append(unschedulablePods, pInfo) } } p.movePodsToActiveOrBackoffQueue(unschedulablePods, event) }
如果上述的Precheck通過後,則會把Pod移到相應的佇列等待下一次排程。這裡的重點來了,本文是講關於資源相關的,那麼Precheck中到底做了什麼檢查呢?
func preCheckForNode(nodeInfo *framework.NodeInfo) queue.PreEnqueueCheck { // Note: the following checks doesn't take preemption into considerations, in very rare // cases (e.g., node resizing), "pod" may still fail a check but preemption helps. We deliberately // chose to ignore those cases as unschedulable pods will be re-queued eventually. return func(pod *v1.Pod) bool { admissionResults := AdmissionCheck(pod, nodeInfo, false) if len(admissionResults) != 0 { return false } _, isUntolerated := corev1helpers.FindMatchingUntoleratedTaint(nodeInfo.Node().Spec.Taints, pod.Spec.Tolerations, func(t *v1.Taint) bool { return t.Effect == v1.TaintEffectNoSchedule }) return !isUntolerated } } func AdmissionCheck(pod *v1.Pod, nodeInfo *framework.NodeInfo, includeAllFailures bool) []AdmissionResult { var admissionResults []AdmissionResult insufficientResources := noderesources.Fits(pod, nodeInfo) if len(insufficientResources) != 0 { for i := range insufficientResources { admissionResults = append(admissionResults, AdmissionResult{InsufficientResource: &insufficientResources[i]}) } if !includeAllFailures { return admissionResults } } if matches, _ := corev1nodeaffinity.GetRequiredNodeAffinity(pod).Match(nodeInfo.Node()); !matches { admissionResults = append(admissionResults, AdmissionResult{Name: nodeaffinity.Name, Reason: nodeaffinity.ErrReasonPod}) if !includeAllFailures { return admissionResults } } if !nodename.Fits(pod, nodeInfo) { admissionResults = append(admissionResults, AdmissionResult{Name: nodename.Name, Reason: nodename.ErrReason}) if !includeAllFailures { return admissionResults } } if !nodeports.Fits(pod, nodeInfo) { admissionResults = append(admissionResults, AdmissionResult{Name: nodeports.Name, Reason: nodeports.ErrReason}) if !includeAllFailures { return admissionResults } } return admissionResults }
preCheckForNode 呼叫了AdmissionCheck,在AdmissionCheck中分別做了資源檢查、親和性檢查、nodeName檢查、埠檢查。這裡我們只關注資源的檢查
func Fits(pod *v1.Pod, nodeInfo *framework.NodeInfo) []InsufficientResource { return fitsRequest(computePodResourceRequest(pod), nodeInfo, nil, nil) } func fitsRequest(podRequest *preFilterState, nodeInfo *framework.NodeInfo, ignoredExtendedResources, ignoredResourceGroups sets.String) []InsufficientResource { insufficientResources := make([]InsufficientResource, 0, 4) allowedPodNumber := nodeInfo.Allocatable.AllowedPodNumber if len(nodeInfo.Pods)+1 > allowedPodNumber { insufficientResources = append(insufficientResources, InsufficientResource{ ResourceName: v1.ResourcePods, Reason: "Too many pods", Requested: 1, Used: int64(len(nodeInfo.Pods)), Capacity: int64(allowedPodNumber), }) } if podRequest.MilliCPU == 0 && podRequest.Memory == 0 && podRequest.EphemeralStorage == 0 && len(podRequest.ScalarResources) == 0 { return insufficientResources } if podRequest.MilliCPU > (nodeInfo.Allocatable.MilliCPU - nodeInfo.Requested.MilliCPU) { insufficientResources = append(insufficientResources, InsufficientResource{ ResourceName: v1.ResourceCPU, Reason: "Insufficient cpu", Requested: podRequest.MilliCPU, Used: nodeInfo.Requested.MilliCPU, Capacity: nodeInfo.Allocatable.MilliCPU, }) } if podRequest.Memory > (nodeInfo.Allocatable.Memory - nodeInfo.Requested.Memory) { insufficientResources = append(insufficientResources, InsufficientResource{ ResourceName: v1.ResourceMemory, Reason: "Insufficient memory", Requested: podRequest.Memory, Used: nodeInfo.Requested.Memory, Capacity: nodeInfo.Allocatable.Memory, }) } if podRequest.EphemeralStorage > (nodeInfo.Allocatable.EphemeralStorage - nodeInfo.Requested.EphemeralStorage) { insufficientResources = append(insufficientResources, InsufficientResource{ ResourceName: v1.ResourceEphemeralStorage, Reason: "Insufficient ephemeral-storage", Requested: podRequest.EphemeralStorage, Used: nodeInfo.Requested.EphemeralStorage, Capacity: nodeInfo.Allocatable.EphemeralStorage, }) } for rName, rQuant := range podRequest.ScalarResources { if v1helper.IsExtendedResourceName(rName) { // If this resource is one of the extended resources that should be ignored, we will skip checking it. // rName is guaranteed to have a slash due to API validation. var rNamePrefix string if ignoredResourceGroups.Len() > 0 { rNamePrefix = strings.Split(string(rName), "/")[0] } if ignoredExtendedResources.Has(string(rName)) || ignoredResourceGroups.Has(rNamePrefix) { continue } } if rQuant > (nodeInfo.Allocatable.ScalarResources[rName] - nodeInfo.Requested.ScalarResources[rName]) { insufficientResources = append(insufficientResources, InsufficientResource{ ResourceName: rName, Reason: fmt.Sprintf("Insufficient %v", rName), Requested: podRequest.ScalarResources[rName], Used: nodeInfo.Requested.ScalarResources[rName], Capacity: nodeInfo.Allocatable.ScalarResources[rName], }) } } return insufficientResources }
fitsRequest 首先會呼叫computePodResourceRequest函數計算出這個Pod需要多少資源,然後跟目前節點還能分配的資源做比較,如果還能夠分配出資源,那麼針對於資源檢查這一項就通過了。如果Precheck所有項都能通過,那麼該Pod會被放入active佇列,該佇列裡的Pod就會被kube-scheduler取出做排程。前面所說的目前節點資源情況是從哪裡來的呢?kubelet會定期(或者node發生變化)上報心跳到Kube-apiserver,因為kube-scheduler監聽了node的變化,所以能感知到節點的資源使用情況。
當Kube-scheduler從佇列取到Pod後,會進行一系列的判斷(如PreFilter),還會涉及資源的檢查,這個資源使用情況也是kubelet上報的。
當我們describe 一個node的時候,可以看到能夠顯示資源allocatable的資訊,那這個資訊就是實時的資源使用情況嗎?答案是否定的,我們看下
kubectl describe node xxxx Capacity: cpu: 64 ephemeral-storage: 1056889268Ki hugepages-1Gi: 0 hugepages-2Mi: 0 memory: 263042696Ki pods: 110 Allocatable: cpu: 63 ephemeral-storage: 1044306356Ki hugepages-1Gi: 0 hugepages-2Mi: 0 memory: 257799816Ki pods: 110
注:本機是64U256G的機器
其中capacity是本機的硬體一共可以提供的資源,allocatable是可以分配的,那麼為什麼allocatable為什麼會和capacity不一樣呢?這裡就涉及到了預留資源和驅逐相關內容
systemReserved: cpu: "0.5" ephemeral-storage: 1Gi memory: 2Gi pid: "1000" kubeReserved: cpu: "0.5" ephemeral-storage: 1Gi memory: 2Gi pid: "1000 evictionHard: imagefs.available: 10Gi memory.available: 1Gi nodefs.available: 10Gi nodefs.inodesFree: 5%
systemReserved, kubeReserved分別 *表示預留給作業系統和kubernetes元件的資源,kubelet在上報可用資源的時候需要減去這部分資源;evictionHard表示資源只剩下這麼多的時候,就會啟動Pod的驅逐,所以這部分資源也不能算在可分配裡面的。這麼算起來,上面的capacity減去上述三者相加正好是allocatable的值,也就是該節點實際可分配的資源。他們的關係可以用下圖表示
*
但是這裡有個值得注意的點,上面通過describe出來的allocatable的值是一個靜態的值,表示該節點總共可以分配多少資源,而不是此時此刻節點可以分配多少資源,kube-scheduler依據Kubelet動態上報的資料來判斷某個節點是否能夠排程。
還需要注意,要使systemReserved, kubeReserved設定的資源不算在可分配的資源裡面,還需要設定如下設定:
# 該設定表示,capacity減去下面的設定的資源才是節點d當前可分配的 # 預設是pods,表示只減去pods佔用了的資源 enforceNodeAllocatable: - pods - kube-reserved - system-reserved # 如果你使用的是systemd作為cgroup驅動,你還需要設定下面的設定 # 否則kubelet無法正常啟動,因為找不到cgroup目錄 # k8s官方推薦s會用systemd kubeReservedCgroup: /kubelet.slice systemReservedCgroup: /system.slice/kubelet.service
到這裡,我們就簡單講了scheduler是如何在排程的時候,在資源層面是如何判斷的。當然了,上面只簡單講了排程的時候pod被移到可排程佇列的情況,後面還有prefilter、filter、score等步驟,但是這些步驟在判斷資源情況時,跟上面是一樣的。
以上就是一文詳解kubernetes 中資源分配的那些事的詳細內容,更多關於kubernetes 資源分配的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45