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

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

  1. 二进制安装kubernetes(五) kubelet组件安装

    概述资料地址:https://blog.csdn.net/bbwangj/article/details/81904350 Kubelet组件运行在Node节点上,维持运行中的Pods以及提供kube ...

  2. CoreOS Linux引入了Kubernetes kubelet

    CoreOS Linux引入了Kubernetes kubelet 作者:Kelsey Hightower 2015年8月14日 这周我们在 CoreOS Linux 的 alpha 开发版集成了 k ...

  3. Kubernetes集群中修复状态为NotReady的节点

    度个假回来发现自己集群中的节点都挂了,全部是NotReady状态 但是除了.10节点外,其他主机并没有挂,可以远程连接上, 那就考虑是kubernetes系统的问题 解决的方法是重启kube-prox ...

  4. 【转载】浅析从外部访问 Kubernetes 集群中应用的几种方式

    一般情况下,Kubernetes 的 Cluster Network 是属于私有网络,只能在 Cluster Network 内部才能访问部署的应用.那么如何才能将 Kubernetes 集群中的应用 ...

  5. Kubernetes - Kubelet TLS Bootstrapping

    一.简单说明 写这个的初衷是自己搜索TLS Bootstrapping的时候没有搜到自己想要的东西,因为TLS Bootstrapping经过很多版本之后也发生了一些变化,所以网上很多也是老的内容了. ...

  6. 如何在 Kubernetes 集群中玩转 Fluid + JuiceFS

    作者简介: 吕冬冬,云知声超算平台架构师, 负责大规模分布式机器学习平台架构设计与功能研发,负责深度学习算法应用的优化与 AI 模型加速.研究领域包括高性能计算.分布式文件存储.分布式缓存等. 朱唯唯 ...

  7. suse 12 二进制部署 Kubernetets 1.19.7 - 第09章 - 部署kubelet组件

    文章目录 1.9.部署kubelet 1.9.0.创建kubelet bootstrap kubeconfig文件 1.9.1.创建kubelet配置文件 1.9.2.配置kubelet为system ...

  8. 在Kubernetes集群中使用calico做网络驱动的配置方法

    参考calico官网:http://docs.projectcalico.org/v2.0/getting-started/kubernetes/installation/hosted/kubeadm ...

  9. kubelet组件部署

    目录 前言 创建 kubelet bootstrap kubeconfig 文件 查看kubeadm为各个节点创建的token 查看各 token 关联的 Secret 创建和分发kubelet参数配 ...

随机推荐

  1. 32 (OC)* keyChain的本质

    1:它是一个sqlite数据库,其保存的所有数据都是加密过的. 2:Keychain是加密规则(key)的集合.每个规则必须含有以下三个要素:认证算法.认证密钥(加密字符串).规则的时间. 3:key ...

  2. NodeManager概述(基本职能和内部架构)

    概述 NodeManager是运行在单个节点上的代理,它需要与应用程序的的ApplicationMaster和集群管理者ResourceManager交互: 从ApplicationMaster上接收 ...

  3. shiro+spring

    公司自用的管理系统使用了shiro,但是对于这个登录页面跳转.登录的过程逻辑以及登录成功或者失败的跳转页面一直不理解,查看相关文档资料,整理出一些结果并本地调试测试,记录下备以后回顾之用. 对于spr ...

  4. NABCD分析 [团队任务]

    N(Need,需求) 学校有许多闲置的自己用不着或者想出手的二手物品,加群发消息寻找物品太过繁琐,同样兼职信息在QQ群混杂在一起尤为不便.因此我们打算做一个专门发布信息的App.包括发布闲置物品,兼职 ...

  5. 基于djiango实现简易版的图书管理系统

    介绍: 本程序仅仅实现图书数据的增删查 树形结构如下   全部代码如下: url: from django.urls import path from front import views as fr ...

  6. 2019-2020-1 20199303<Linux内核原理与分析>第二周作业

    2019-2020-1 20199303第二周作业 1.汇编与寄存器的学习 寄存器是中央处理器内的组成部份.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中 ...

  7. Save&Load--Unity存档读档的学习总结

    存档与读档功能 举例: 传统RPG游戏(仙剑.空之轨迹): 1.角色信息(生命值,等级) 2.道具信息(装备,药品) 3.场景信息(场景名称.角色坐标) 4.事件信息(任务相关) 关卡类游戏:关卡的通 ...

  8. java 中使用StopWatch来计算时间差

    以前在进行时间耗时时我们通常的做法是先给出计算前后两个的时间值,然后通过详见来计算耗时时长. eg: long start = System.currentTimeMillis(); ......业务 ...

  9. java进阶文章优选链接,面试干货集合

    Java多线程: java多线程详细总结:https://blog.csdn.net/chenruijia170707/article/details/78505351 ThreadLocal 用法及 ...

  10. <机器学习>无监督学习算法总结

    本文仅对常见的无监督学习算法进行了简单讲述,其他的如自动编码器,受限玻尔兹曼机用于无监督学习,神经网络用于无监督学习等未包括.同时虽然整体上分为了聚类和降维两大类,但实际上这两类并非完全正交,很多地方 ...