节点资源管理
kubectl get node xxx -o yaml
allocatable:
cpu: "40"
memory: 263927444Ki
pods: "110"
capacity:
cpu: "40"
memory: 264029844Ki
pods: "110"
capacity
由于 capacity 反映的是这台机器的资源真实数量,所以确认这个信息的任务理所应当交给运行在每台机器上面的 kubelet 上。 kubelet 目前把 cadvisor 的大量代码直接作为 vendor 引入。其实就相当于在 kubelet 内部启动了一个小的 cadvisor。 在 kubelet 启动后,这个内部的 cadvisor 子模块也会相应启动,并且获取这台机器上面的各种信息。其中就包括了有关这台机器的资源信息,而这个信息也自然作为这台机器的真实资源信息,通过 kubelet 再上报给 apiserver。
allocatable
kubernetes 分别提供了 Kube-Reserved 和 System-Reserved 特性,可以分别为这两组进程设定一个预留的计算资源量,比如预留 2 核 4G 的计算资源给系统进程用。
我们应该都有了解,kubernetes 底层是通过 cgroup 特性来实现资源的隔离与限制的,而这种资源的预留也是通过 cgroup 技术来实现的。
在默认情况下,针对每一种基本资源(CPU、memory),kubernetes 首先会创建一个根 cgroup 组,作为所有容器 cgroup 的根,名字叫做 kubepods。这个 cgroup 就是用来限制这台计算节点上所有 pod 所使用的资源的。默认情况下这个 kubepods cgroup 组所获取的资源就等同于该计算节点的全部资源。
但是,当开启 Kube-Reserved 和 System-Reserved 特性时,kubernetes 则会为 kubepods cgroup 再创建两个同级的兄弟 cgroup,分别叫做 kube-reserved 和 system-reserved,分别用来为 kubernetes daemon、system daemon 预留一定的资源并且与 kubepods cgroup 共同分配这个机器的资源。所以 kubepods 能够被分配到的资源势必就会小于机器的资源真实量了,这样从而就达到了为 kubernetes daemon,system daemon 预留资源的效果。
Eviction Threshold
当机器上面的【内存】以及【磁盘资源】这两种不可压缩资源严重不足时,kubelet 就会根据这台机器上面所有 pod 的 QoS 级别(Qos 级别下面会介绍),以及他们的内存使用情况,进行一个综合排名,把排名最靠前的 pod 进行迁移,从而释放出足够的内存资源。 所以针对内存资源,它的 allocatable 应该是 [capacity] - [kube-reserved] - [system-reserved] - [hard-eviction]
Soft Eviction Thresholds驱逐软阈值
软阈值需要和一个宽限期参数协同工作。当系统资源消耗达到软阈值时,这一状况的持续时间超过了宽限期之前,Kubelet 不会触发任何动作。软移除门限的配置支持下列标记: eviction-soft 描述了移除门限的集合(例如 memory.available<1.5Gi),如果在宽限期之外满足条件将触发 pod 移除。 eviction-soft-grace-period 描述了移除宽限期的集合(例如 memory.available=1m30s),对应于在移除 pod 前软移除门限应该被控制的时长。 eviction-max-pod-grace-period 描述了当满足软移除门限并终止 pod 时允许的最大宽限期值(秒数)
Hard Eviction Thresholds驱逐硬阈值
The kubelet has the following default hard eviction threshold:
memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%
pod 资源管理与分配
request
request 指的是针对这种资源,这个容器希望能够保证能够获取到的最少的量。但是在实际情况下,CPU request 是可以通过 cpu.shares 特性能够实现的。但是内存资源,由于它是不可压缩的,所以在某种场景中,是有可能因为其他 memory limit 设置比 request 高的容器对内存先进行了大量使用导致其他 pod 连 request 的内存量都有可能无法得到满足。
limit
limit 对于 CPU,还有内存,指的都是容器对这个资源使用的上限。但是这两种资源在针对容器使用量超过 limit 所表现出的行为也是不同的。对 CPU 来说,容器使用 CPU 过多,内核调度器就会切换,使其使用的量不会超过 limit 对内存来说,容器使用内存超过 limit,这个容器就会被 OOM kill 掉,从而发生容器的重启。 在容器没有指定 request 的时候,request 的值和 limit 默认相等。而如果容器没有指定 limit 的时候,request 和 limit 会被设置成的值则根据不同的资源有不同的策略。
pod QoS 分类
-
Guaranteed • Burstable • BestEffort
如果不配置 requests 和 limits,pod 的 QoS 竟然是最低的。没错,所以推荐大家理解 QoS 的概念,并且按照需求一定要给 pod 配置 requests 和 limits 参数,不仅可以让调度更准确,也能让系统更加稳定。
kubelet 就是基于 【pod 申请的资源】 + 【pod 的 QoS 级别】来最终为这个 pod 分配资源的。而分配资源的根本方法就是基于 cgroup 的机制。
kubernetes 在拿到一个 pod 的资源申请信息后,针对每一种资源,他都会做如下几件事情:
- 对 pod 中的每一个容器,都创建一个 container level cgroup(注:这一步真实情况是 kubernetes 向 docker daemon 发送命令完成的)。
- 然后为这个 pod 创建一个 pod level cgroup ,它会成为这个 pod 下面包含的所有 container level cgroup 的父 cgroup。
- 最终,这个 pod level cgroup 最终会根据这个 pod 的 QoS 级别,可能被划分到某一个 QoS level cgroup 中,成为这个 QoS level cgroup 的子 cgroup。
- 整个 QoS level cgroup 还是所有容器的根 cgroup - kubepods 的子 cgroup。 所以这个嵌套关系通过下述图片可以比较清晰的展示出来。
PriorityClass
优先级的使用也比较简单,只需要在 pod.spec.PriorityClassName 指定要使用的优先级名字,即可以设置当前 pod 的优先级为对应的值。
apiVersion: scheduling.k8s.io/v1alpha1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
优先级的使用也比较简单,只需要在 pod.spec.PriorityClassName 指定要使用的优先级名字,即可以设置当前 pod 的优先级为对应的值。
Pod 的优先级在调度的时候会使用到。首先,待调度的 pod 都在同一个队列中,启用了 pod priority 之后,调度器会根据优先级的大小,把优先级高的 pod 放在前面,提前调度。
另外,如果在调度的时候,发现某个 pod 因为资源不足无法找到合适的节点,调度器会尝试 preempt 的逻辑。
简单来说,调度器会试图找到这样一个节点:找到它上面优先级低于当前要调度 pod 的所有 pod,如果杀死它们,能腾足够的资源,调度器会执行删除操作,把 pod 调度到节点上。
namespace资源管理
LimitRange
为每个 pod 都手动配置这些参数是挺麻烦的事情,kubernetes 提供了 LimitRange 资源,可以让我们配置某个 namespace 默认的 request 和 limit 值
apiVersion: "v1"kind: "LimitRange"metadata:
name: you-shall-have-limits
spec:
limits:
- type: "Container"
max:
cpu: "2"
memory: "1Gi"
min:
cpu: "100m"
memory: "4Mi"
default:
cpu: "500m"
memory: "200Mi"
defaultRequest:
cpu: "200m"
memory: "100Mi"
如果对应 namespace 创建的 pod 没有写资源的 requests 和 limits 字段,那么它会自动拥有下面的配置信息:
- 内存请求是 100Mi,上限是 200Mi
- CPU 请求是 200m,上限是 500m 当然,如果 pod 自己配置了对应的参数,kubernetes 会使用 pod 中的配置。使用 LimitRange 能够让 namespace 中的 pod 资源规范化,便于统一的资源管理。
ResourceQuota
前面讲到的资源管理和调度可以认为 kubernetes 把这个集群的资源整合起来,组成一个资源池,每个应用(pod)会自动从整个池中分配资源来使用。默认情况下只要集群还有可用的资源,应用就能使用,并没有限制。kubernetes 本身考虑到了多用户和多租户的场景,提出了 namespace 的概念来对集群做一个简单的隔离。
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota
spec:
hard:
cpu: "20"
memory: 1Gi
pods: "10"
replicationcontrollers: "20"
resourcequotas: "1"
services: "5"
Resource quota 要解决的问题和使用都相对独立和简单,但是它也有一个限制:那就是它不能根据集群资源动态伸缩。一旦配置之后,resource quota 就不会改变,即使集群增加了节点,整体资源增多也没有用。kubernetes 现在没有解决这个问题,但是用户可以通过编写一个 controller 的方式来自己实现。
查看资源使用情况
kubectl get pods -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{"\t"}{.spec.containers[0].resources.limits.cpu}{"\t"}{"\t"}{.spec.containers[0].resources.limits.memory}{"\t"}{"\t"}{.spec.nodeName}{"\t"}{"\t"}{.spec.nodeSelector.dgroup}{"\t"}{"\t"}{.status.startTime}{"\n"}{end}' --sort-by=.metadata.creationTimestamp -n kubeflow |grep jupyter- |grep -v "jupyter-web-app-6d85d5d85d-n58wc" |grep -v "jupyter-0"
kubectl get po -n model-deployment -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.creationTimestamp}{"\t"}{.status.startTime}{"\t"}{.spec.containers[0].resources}{"\t"}{.status.phase}{"\n"}{end}' --sort-by=.metadata.creationTimestamp|egrep "Run|Pend" |grep -v "kuai"
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.dgroup}{"\t"}{.status.allocatable.cpu}{"\t"}{.status.allocatable.memory}{"\n"}{end}' -l dgroup=zxyz1vza