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的隔离性、稳定性、可观测性。

原文地址:https://www.cnblogs.com/gaorong/p/11716907.html

时间: 2024-08-29 17:27:14

kubernetes kubelet组件中cgroup的层层"戒备"的相关文章

CoreOS Linux引入了Kubernetes kubelet

CoreOS Linux引入了Kubernetes kubelet 作者:Kelsey Hightower 2015年8月14日 这周我们在 CoreOS Linux 的 alpha 开发版集成了 kubelet——Kubernetes 的一个核心内嵌组件.Kubelet 负责维护 pod(应用实例)集合.Pod 集合由本地系统的一个或多个容器构成.在 Kubernetes 集群中,kubelet 用于作为本地代理,通过访问 Kubernetes 的 API 服务器,监控 PodSpecs 的状

07-2.部署 kubelet 组件

07-2.部署 kubelet 组件 kublet 运行在每个 worker 节点上,接收 kube-apiserver 发送的请求,管理 Pod 容器,执行交互式命令,如 exec.run.logs 等. kublet 启动时自动向 kube-apiserver 注册节点信息,内置的 cadvisor 统计和监控节点的资源使用情况. 为确保安全,本文档只开启接收 https 请求的安全端口,对请求进行认证和授权,拒绝未授权的访问(如 apiserver.heapster). 下载和分发 kub

Kubernetes网络组件之Flannel策略实践(vxlan、host-gw)

4.3 Kubernetes网络组件之 FlannelFlannel是CoreOS维护的一个网络组件,Flannel为每个Pod提供全局唯一的IP,Flannel使用ETCD来存储Pod子网与Node IP之间的关系.flanneld守护进程在每台主机上运行,并负责维护ETCD信息和路由数据包.其实k8s网络组件flannel和calico主要解决的问题是k8s节点之间容器网络的通信,flannel要保证每个pod的IP是唯一的,怎么保证是唯一的,大部分组件的做法是在每个Node上分配一个唯一的

kubernetes HPA-超详细中文官方文档

HPA全称是Horizontal Pod Autoscaler,翻译成中文是POD水平自动伸缩,以下都会用HPA代替Horizontal Pod Autoscaler,HPA可以基于CPU利用率对replication controller.deployment和replicaset中的pod数量进行自动扩缩容(除了CPU利用率也可以基于其他应程序提供的度量指标custom metrics进行自动扩缩容).pod自动缩放不适用于无法缩放的对象,比如DaemonSets.HPA由Kubernete

Qt组件中的双缓冲无闪烁绘图

双缓冲绘图在Qt4中,所有的窗口部件默认都使用双缓冲进行绘图.使用双缓冲,可以减轻绘制的闪烁感.在有些情况下,用户要关闭双缓冲,自己管理绘图.下面的语句设置了窗口部件的Qt::WA_PaintOnScreen属性 ,就关闭了窗口部件的双缓冲.mywidget->setAttribute(Qt::WA_PaintOnScreen); 由于Qt4不再提供异或笔,组合模式QPainter::CompostionMode_Xor()并不是异或笔,Qt4只提供了QRubberBand实现矩形和直线的绘图反

详解angular2组件中的变化检测机制(对比angular1的脏检测)

组件和变化检测器 如你所知,Angular 2 应用程序是一颗组件树,而每个组件都有自己的变化检测器,这意味着应用程序也是一颗变化检测器树.顺便说一句,你可能会想.是由谁来生成变化检测器?这是个好问题,它们是由代码生成. Angular 2 编译器为每个组件自动创建变化检测器,而且最终生成的这些代码 JavaScript VM友好代码.这也是为什么新的变化检测是快速的 (相比于 Angular 1.x 的 $digest).基本上,每个组件可以在几毫秒内执行数万次检测.因此你的应用程序可以快速执

react 不能往组件中传入属性的值为 undefined

在使用 andt design 的时候遇到个需求,需要清除 Select 组件选中后的值,让它变成什么都没选中,显示 placeholder 刚开始以为设置为 null 即可,结果发现设置为 null 并没什么卵用,只是得到什么都没匹配上的结果,选择框中为空. 后来发现将 value 值设置为 undefined 即可. 其实,在 react 中,传入 undefined 就代表什么都没传入,这时组件中如果有默认的 props 值则会使用默认值. 所以,上面传入 null,其实也是传入了值,所以

我的Android进阶之旅------&gt; Android为TextView组件中显示的文本添加背景色

通过上一篇文章 我的Android进阶之旅------> Android在TextView中显示图片方法 (地址:http://blog.csdn.net/ouyang_peng/article/details/46916963) 我们学会了在TextView中显示图片的方法,现在我们来学习如何为TextView组件中显示的文本添加背景色.要求完成的样子如图所示: 首先来学习使用BackgroundColorSpan对象设置文字背景色,代码如下: TextView textView=(TextV

如何在SSIS的脚本组件中访问变量

原文:如何在SSIS的脚本组件中访问变量 这是一个小问题,我们在SSIS的设计中很多地方都会用到变量,我习惯性地将"变量"和"表达式"称为SSIS的灵魂,虽然不见得绝对准确,但它们确实是保证一个SSIS包灵活性的根本. 同时,我们可能也会在数据流任务中添加"脚本组件",用我们熟知的C#或者VB.NET编写一些数据转换处理的逻辑.这里可能就有一个需求,我们希望在脚本组件中访问变量(读或者写),但默认情况下,这个需求并不是那么容易实现.我们来看下面的