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来约束。但是对于Burstable和BestEffort类型的pod,因为有的pod和容器没有指定资源限制,在极端条件下会无限制的占用资源,所以我们需要分别设置Burstable和BestEffortcgroup, 然后将对应的pod都创建在该cgroup下。kubelet希望尽可能提高资源利用率,让Burstable和BestEffort类型的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上面的资源按照使用对象分为三部分:
- 业务进程使用的资源, 即pods使用的资源;
- kubernetes组件使用的资源,例如kubelet, docker;
- 系统组件使用的资源,例如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参数指定所作用的cgroupkube-reserved: 限制系统组件的资源使用,如果开启该限制则需要同时设置--system-reserved-cgroup
-none: 不进行任何资源限制
如果需要指定多种类型,通过逗号分割枚举即可,注意如果开启了system-reserved和kube-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-rootkubelet中所有的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-reserved,system-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的层层"戒备"的更多相关文章
- 二进制安装kubernetes(五) kubelet组件安装
概述资料地址:https://blog.csdn.net/bbwangj/article/details/81904350 Kubelet组件运行在Node节点上,维持运行中的Pods以及提供kube ...
- CoreOS Linux引入了Kubernetes kubelet
CoreOS Linux引入了Kubernetes kubelet 作者:Kelsey Hightower 2015年8月14日 这周我们在 CoreOS Linux 的 alpha 开发版集成了 k ...
- Kubernetes集群中修复状态为NotReady的节点
度个假回来发现自己集群中的节点都挂了,全部是NotReady状态 但是除了.10节点外,其他主机并没有挂,可以远程连接上, 那就考虑是kubernetes系统的问题 解决的方法是重启kube-prox ...
- 【转载】浅析从外部访问 Kubernetes 集群中应用的几种方式
一般情况下,Kubernetes 的 Cluster Network 是属于私有网络,只能在 Cluster Network 内部才能访问部署的应用.那么如何才能将 Kubernetes 集群中的应用 ...
- Kubernetes - Kubelet TLS Bootstrapping
一.简单说明 写这个的初衷是自己搜索TLS Bootstrapping的时候没有搜到自己想要的东西,因为TLS Bootstrapping经过很多版本之后也发生了一些变化,所以网上很多也是老的内容了. ...
- 如何在 Kubernetes 集群中玩转 Fluid + JuiceFS
作者简介: 吕冬冬,云知声超算平台架构师, 负责大规模分布式机器学习平台架构设计与功能研发,负责深度学习算法应用的优化与 AI 模型加速.研究领域包括高性能计算.分布式文件存储.分布式缓存等. 朱唯唯 ...
- 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 ...
- 在Kubernetes集群中使用calico做网络驱动的配置方法
参考calico官网:http://docs.projectcalico.org/v2.0/getting-started/kubernetes/installation/hosted/kubeadm ...
- kubelet组件部署
目录 前言 创建 kubelet bootstrap kubeconfig 文件 查看kubeadm为各个节点创建的token 查看各 token 关联的 Secret 创建和分发kubelet参数配 ...
随机推荐
- .NET Core 获取请求类容(body)
.Net Core 对于body多次读取,开放了一个参数EnableRewind(),该参数在第一次读取body之前开启,之后body信息可以多次读取:core时代取消了之前的stream.posit ...
- Vue-学习笔记0-独立项目搭建
前言 搭建Vue+Webpack项目,使用vue-cli搭建项目. 准备 vue独立项目依赖node的npm包管理器,所以需要先安装node. 相关的npm常用命令文章: Npm-常用命令,点击访问 ...
- C++类拷贝控制 深拷贝 浅拷贝
普通类型对象之间的复制很简单,而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量,这篇文章将帮你理清C++类对象的拷贝方式 拷贝构造函数,拷贝赋值运算符 首先我们简单了解下默认的拷贝 ...
- Mybatis源码解析,一步一步从浅入深(一):创建准备工程
Spring SpringMVC Mybatis(简称ssm)是一个很流行的java web框架,而Mybatis作为ORM 持久层框架,因其灵活简单,深受青睐.而且现在的招聘职位中都要求应试者熟悉M ...
- 机器学习之SVM调参实例
一.任务 这次我们将了解在机器学习中支持向量机的使用方法以及一些参数的调整.支持向量机的基本原理就是将低维不可分问题转换为高维可分问题,在前面的博客具体介绍过了,这里就不再介绍了. 首先导入相关标准库 ...
- HNU_中小学数学卷子自动生成程序(个人项目)简要分析
一.前言 首先,在这里特别感谢我的结对编程伙伴HnuLyx,他在算法上和设计思路上都与我有很大的不同,个人项目互评中,为我的项目提出了很多很好的建议,让我在认清自己不足的同时,了解到很多以前没有注意的 ...
- gym101666题解
A Amsterdam Distance 题意 求圆环上的两点距离. 分析 显然是沿半径方向走到内圈再走圆弧最短. 代码 #include <bits/stdc++.h> using na ...
- AVR单片机教程——数字IO寄存器
前两篇教程中我们学习了LED.按键.开关的基本原理,数字输入输出的使用以及两者之间的关系.我们用到了 pin_mode . pin_read 和 pin_write 这三个函数,实际上它们离最底层(至 ...
- 夯实Java基础系列18:深入理解Java内部类及其实现原理
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
- python2和3区别
核心类差异 Python3对Unicode字符的原生支持 Python2中使用 ASCII 码作为默认编码方式导致string有两种类型str和unicode,Python3只支持unicode的st ...