带你玩转kubernetes-k8s(第61篇-Kubernetes之资源紧缺时的Pod驱逐机制)

释放双眼,带上耳机,听听看~!

资源紧缺时的Pod驱逐机制

如何在系统硬件资源紧缺的情况下保证Node的稳定性,是kubelet需要解决的一个重要问题。尤其对于内存和磁盘这种不可压缩的资源,紧缺就意味着不稳定。下面对驱逐的策略、信号、阈值、监控频率和驱逐操作进行详细说明。

驱逐策略

kubelet持续监控主机的资源使用情况,并尽量防止计算资源被耗尽。一旦出现资源紧缺的迹象,kubelet就会主动终止一个或多个Pod的运行,以回收紧缺的资源。当一个Pod被终止时,其中的容器会全部停止,Pod的状态会被设置为Failed。

驱逐信号

在下表中提到了一些信号,kubelet能够利用这些信号作为决策依据来触发驱逐行为。其中,描述列中的内容来自kubelet Summary API;每个信号都支持整数值或者百分比的表示方法,百分比的分母部分就是各个信号相关资源的总量。

memory.available
memory.available:=node.status.capacity[memory] -node.stats.memory.workingSet
nodefs.available
nodefs.available:=node.stats.fs.available
nodefs.inodesFree
nodefs.inodesFree:=node.stats.fs.inodesFree
imagefs.available
imagefs.available:=node.stats.runtime.imagefs.available
imagefs.inodesFree
imagefs.inodesFree:=node.stats.runtime.imagefs.inodesFree

memory.available的值取自cgroupfs,而不是free -m命令,这是因为free -m不支持在容器内工作。如果用户使用了node allocatable功能,则除了节点自身的内存需要判断,还需要利用cgroup根据用户Pod部分的情况进行判断。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1#!/bin/bash
2#!/usr/bin/env bash
3
4# this script reproduces what the kubelet does
5# to calculate memory. available relative to root cgroup.
6
7# current memory usage
8memory_capacity_in_kb=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
9memory_capacity_in_bytes=$((memory_capacity_in_kb * 1024 ))
10memory_usage_in_bytes=$(cat /sys/fs/cgroup/memory/memory.usage_in_bytes)
11memory_total_inactive_file=$(cat /sys/fs/cgroup/memory/memory.stat | grep total_inactive_file | awk '{print $2}')
12
13
14memory_working_set=$memory_usage_in_bytes
15if [ "$memory_working_set" -lt "$memory_total_inactive_file"];
16then
17  memory_working_set=0
18else
19  memory_working_set=$((memory_usage_in_bytes-memory_total_inactive_file))
20fi
21
22memory_available_in_bytes=$((memory_capacity_in_bytes - memory_working_set ))
23memory_available_in_kb=$((memory_available_in_bytes / 1024 ))
24memory_available_in_mb=$((memory_available_in_kb / 1024 ))
25
26echo "memory.capacity_in_bytes  $memory_capacity_in_bytes"
27echo "memory.usage_in_bytes $memory_usage_in_bytes"
28echo "memory.total_inactive_file $memory_total_inactive_file"
29echo "memory.working_set $memory_working_set"
30echo "memory.available_in_bytes $memory_available_in_bytes"
31echo "memory.available_in_kb $memory_available_in_kb"
32echo "memory.available in mb $memory_available_in_mb"
33

kubelet假设inactive_file(不活跃LRU列表中的file-backed内存,以字节为单位)在紧缺情况下可以回收,因此对其进行了排除。

kubelete支持以下两种文件系统。

(1)nodefs:保存kubelet的卷和守护进程日志等。
(2)imagefs:在容器运行时保存镜像及可写入层。

kubelet使用cAdvisor自动监控这些文件系统。kubelet不关注其他文件系统,不支持所有其他类型的配置,例如保存在独立文件系统中的卷和日志。

磁盘压力相关的资源回收机制正在逐渐被驱逐策略接管,未来会停止对现有垃圾收集方式的支持。

驱逐阀值

kubelet可以定义驱逐阈值,一旦超出阈值,就会触发kubelet进行资源回收操作。

阀值的定义方式为:


1
2
1<eviction-signal> <operator> <quantity>
2

其中:

◎ 在上表中列出了驱逐信号的名称。
◎ 当前仅支持一个operator(运算符):< (小于)。
◎ quantity需要符合Kubernetes的数量表达方式,也可以用以%结尾的百分比表示。

例如,如果一个节点有10GiB内存,我们希望在可用内存不足1GiB时进行驱逐,就可以用下面任一方式来定义驱逐阈值。
◎ memory.available<10%。
◎ memory.available<1GiB。

驱逐阈值又可以通过软阈值和硬阈值两种方式进行设置。

1.驱逐软阀值

驱逐软阈值由一个驱逐阈值和一个管理员设定的宽限期共同定义。当系统资源消耗达到软阈值时,在这一状况的持续时间达到宽限期之前,kubelet不会触发驱逐动作。如果没有定义宽限期,则kubelet会拒绝启动。

另外,可以定义终止Pod的宽限期。如果定义了这一宽限期,那么kubelet会使用pod.Spec.TerminationGracePeriodSeconds和最大宽限期这两个值之间较小的数值进行宽限,如果没有指定,则kubelet会立即杀掉Pod。

软阈值的定义包括以下几个参数。
◎ –eviction-soft:描述驱逐阈值(例如memory.available<1.5GiB),如果满足这一条件的持续时间超过宽限期,就会触发对Pod的驱逐动作。
◎ –eviction-soft-grace-period:驱逐宽限期(例如memory.available=1m30s),用于定义达到软阈值之后持续时间超过多久才进行驱逐。
◎ –eviction-max-pod-grace-period:在达到软阈值后,终止Pod的最大宽限时间(单位为s)。

2.驱逐硬阀值

硬阈值没有宽限期,如果达到了硬阈值,则kubelet会立即杀掉Pod并进行资源回收。

硬阈值的定义包括参数–eviction-hard:驱逐硬阈值,一旦达到阈值,就会触发对Pod的驱逐操作。

kubelet的默认硬阈值定义如下:


1
2
1--eviction-hard=memory.available&lt;100Mi
2

驱逐监控频率

kubelet的–housekeeping-interval参数定义了一个时间间隔,kubelet每隔一个这样的时间间隔就会对驱逐阈值进行评估。

节点的状况

kubelet会将一个或多个驱逐信号与节点的状况对应起来。

无论触发了硬阈值还是软阈值,kubelet都会认为当前节点的压力太大,如下表所示为节点状况与驱逐信号的对应关系。

MemoryPressure
memory.available
节点的可用内存到达了驱逐阀值
DiskPressure
nodefs.available,nodefs.inodesFree,imagefs.availbale, imagefs.inodesFree
节点的root文件系统或者镜像文件系统的可用 空间达到了驱逐阀值

 

kubelet会持续向Master报告节点状态的更新过程,这一频率由参数–node-statusupdate- frequency指定,默认为10s。

节点状况的抖动

如果一个节点的状况在软阈值的上下抖动,但是又没有超过宽限期,则会导致该节点的相应状态在True和False之间不断变换,可能会对调度的决策过程产生负面影响。

要防止这种状况,可以使用参数–eviction-pressure-transition-period(在脱离压力状态前需要等待的时间,默认值为5m0s),为kubelet设置在脱离压力状态之前需要等待的时间。

这样一来,kubelet在把压力状态设置为False之前,会确认在检测周期之内该节点没有达到驱逐阈值。

回收Node级别的资源

如果达到了驱逐阈值,并且也过了宽限期,kubelet就会回收超出限量的资源,直到驱逐信号量回到阈值以内。

kubelet在驱逐用户Pod之前,会尝试回收Node级别的资源。在观测到磁盘压力的情况下,基于服务器是否为容器运行时定义了独立的imagefs,会导致不同的资源回收过程。

1.有Imagefs的情况

(1)如果nodefs文件系统达到了驱逐阈值,则kubelet会删掉死掉的Pod、容器来清理空间。
(2)如果imagefs文件系统达到了驱逐阈值,则kubelet会删掉所有无用的镜像来清理空间。

2.没有Imagefs的情况

如果nodefs文件系统达到了驱逐阈值,则kubelet会按照下面的顺序来清理空间。
(1)删除死掉的Pod、容器。
(2)删除所有无用的镜像。

驱逐用户的Pod

如果kubelet无法通过节点级别的资源回收获取足够的资源,就会驱逐用户的Pod。

kubelet会按照下面的标准对Pod的驱逐行为进行判断。

◎ Pod要求的服务质量。
◎ 根据Pod调度请求的被耗尽资源的消耗量。

接下来,kubelet按照下面的顺序驱逐Pod。

◎ BestEffort:紧缺资源消耗最多的Pod最先被驱逐。
◎ Burstable:根据相对请求来判断,紧缺资源消耗最多的Pod最先被驱逐,如果没有Pod超出它们的请求,则策略会瞄准紧缺资源消耗量最大的Pod。
◎ Guaranteed:根据相对请求来判断,紧缺资源消耗最多的Pod最先被驱逐,如果没有Pod超出它们的请求,策略会瞄准紧缺资源消耗量最大的Pod。

Guaranteed Pod永远不会因为其他Pod的资源消费被驱逐。如果系统进程(例如kubelet、docker、journald等)消耗了超出system-reserved或者kube-reserved的资源,而在这一节点上只运行了Guaranteed Pod,那么为了保证节点的稳定性并降低异常消耗对其他Guaranteed Pod的影响,必须选择一个Guaranteed Pod进行驱逐。

本地磁盘是一种BestEffort资源。如有必要,kubelet会在DiskPressure的情况下,对Pod进行驱逐以回收磁盘资源。kubelet会按照QoS进行评估。如果kubelet判定缺乏inode资源,就会通过驱逐最低QoS的Pod的方式来回收inodes。如果kubelet判定缺乏磁盘空间,就会在相同QoS的Pod中,选择消耗最多磁盘空间的Pod进行驱逐。下面针对有Imagefs和没有Imagefs的两种情况,说明kubelet在驱逐Pod时选择Pod的排序算法,然后按顺序对Pod进行驱逐。

1.有Imagefs的情况

如果nodefs触发了驱逐,则kubelet会根据nodefs的使用情况(以Pod中所有容器的本地卷和日志所占的空间进行计算)对Pod进行排序。

如果imagefs触发了驱逐,则kubelet会根据Pod中所有容器消耗的可写入层的使用空间进行排序。

2.没有Imagefs的情况

如果nodefs触发了驱逐,则kubelet会对各个Pod中所有容器的总体磁盘消耗(以本地卷+日志+所有容器的写入层所占的空间进行计算)进行排序。

资源最少回收量

在某些场景下,驱逐Pod可能只回收了很少的资源,这就导致了kubelet反复触发驱逐阈值。另外,回收磁盘这样的资源,是需要消耗时间的。

要缓和这种状况,kubelet可以对每种资源都定义minimum-reclaim。kubelet一旦监测到了资源压力,就会试着回收不少于minimum-reclaim的资源数量,使得资源消耗量回到期望的范围。

例如,可以配置–eviction-minimum-reclaim如下:


1
2
3
1--eviction-hard=memory.available&lt;500Mi,nodefs.avaliable&lt;1Gi,imagefs.available&lt;100Gi
2--evition-minimum-reclaim=&quot;memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi&quot;
3

这样配置的效果如下。

◎ 当memory.available超过阈值触发了驱逐操作时,kubelet会启动资源回收,并保证memory.available至少有500MiB。
◎ 如果是nodefs.available超过阈值并触发了驱逐操作,则kubelet会恢复nodefs.available到至少1.5GiB。
◎ 对于imagefs.available超过阈值并触发了驱逐操作的情况,kubelet会保证imagefs.available恢复到最少102GiB。

在默认情况下,所有资源的eviction-minimum-reclaim都为0。

节点资源紧张情况下的系统行为

1.调度器的行为

在节点资源紧缺的情况下,节点会向Master报告这一状况。在Master上运行的调度器(Scheduler)以此为信号,不再继续向该节点调度新的Pod。如下表所示为节点状况与调度行为的对应关系。

MemoryPressure
不再调度新的BestEffort Pod到这个节点
DiskOressure
不再向这一节点调度Pod

2.Node的OOM行为

如果节点在kubelet能够回收内存之前遭遇了系统的OOM(内存不足),节点则依赖oom_killer的设置进行响应(OOM评分系统详见上节的描述)。

kubelet根据Pod的QoS为每个容器都设置了一个oom_score_adj值,如下表所示。

Guarantced
-988
BestEffort
100
Burstable
min(max(2,1000-(1000*memoryRequestsBytes)/machineMemoryCapacityBytes), 999)

如果kubelet无法在系统OOM之前回收足够的内存,则oom_killer会根据内存使用比率来计算oom_score,将得出的结果和oom_score_adj相加,得分最高的Pod首先被驱逐。

这个策略的思路是,QoS最低且相对于调度的Request来说消耗最多内存的Pod会首先被驱逐,来保障内存的回收。

与Pod驱逐不同,如果一个Pod的容器被OOM杀掉,则是可能被kubelet根据RestartPolicy重启的。

3.对DaemonSet类型的Pod驱逐的考虑

通过DaemonSet创建的Pod具有在节点上自动重启的特性,因此我们不希望kubelet驱逐这种Pod;然而kubelet目前并没有能力分辨DaemonSet的Pod,所以无法单独为其制定驱逐策略,所以强烈建议不要在DaemonSet中创建BestEffort类型的Pod,避免产生驱逐方面的问题。

可调度的资源和去捉策略实践

假设一个集群的资源管理需求如下。
◎ 节点内存容量:10GiB

◎ 保留10%的内存给系统守护进程(内核、kubelet等)。

◎ 在内存使用率达到95%时驱逐Pod,以此降低系统压力并防止系统OOM。
为了满足这些需求,kubelet应该设置如下参数:


1
2
3
1--evicition-hard=memory.available&lt;500Mi
2--system-reseved=memory=1.5Gi
3

在这个配置方式中隐式包含这样一个设置:系统预留内存也包括资源驱逐阈值。

如果内存占用超出这一设置,则要么是Pod占用了超过其Request的内存,要么就是系统使用了超过500MiB内存。

在这种设置下,节点一旦开始接近内存压力,调度器就不会向该节点部署Pod,并且假定这些Pod使用的资源数量少于其请求的资源数量。

现阶段的问题

1.kubelet无法及时观测到内存压力

kubelet目前从cAdvisor定时获取内存使用状况的统计情况。如果内存使用在这个时间段内发生了快速增长,且kubelet无法观察到MemoryPressure,则可能会触发OOMKiller。Kubernetes正在尝试将这一过程集成到memcg通知API中来减少这一延迟,而不是让内核首先发现这一情况。

对用户来说,一个较为可靠的处理方式就是设置驱逐阈值为大约75%,这样就降低了发生OOM的几率,提高了驱逐的标准,有助于集群状态的平衡。

2.kubelet可能会错误地驱逐更多的Pod

这也是状态搜集存在时间差导致的。未来可能会通过按需获取根容器的统计信息来减少计算偏差(https://github.com/google/cadvisor/issues/1247)。

Pod Disruption Budget(主动驱逐保护)

在Kubernetes集群运行的过程中,许多管理操作都可能对Pod进行主动驱逐,“主动”一词意味着这一操作可以安全地延迟一段时间,目前主要针对以下两种场景。
◎ 节点的维护或升级时(kubectl drain)。
◎ 对应用的自动缩容操作(autoscaling down)。

未来,rescheduler也可能执行这个操作。

作为对比,由于节点不可用(Not Ready)导致的Pod驱逐就不能被称为主动了。

对于主动驱逐的场景来说,应用如果能够保持存活的Pod的数量,则会非常有用。通过使用PodDisruptionBudget,应用可以保证那些会主动移除Pod的集群操作永远不会在同一时间停掉太多Pod,从而导致服务中断或者服务降级等。例如,在对某些Node进行维护时,系统应该保证应用以不低于一定数量的Pod保障服务的正常运行。kubectl drain操作将遵循PodDisruptionBudget的设定,如果在该节点上运行了属于同一服务的多个Pod,则为了保证最少存活数量,系统将确保每终止一个Pod后,一定会在另一台健康的Node上启动新的Pod,再继续终止下一个Pod。

PodDisruptionBudget资源对象在Kubernetes 1.5版本时升级为Beta版本,用于指定一个Pod集合在一段时间内存活的最小实例数量或者百分比。一个PodDisruptionBudget作用于一组被同一个控制器管理的Pod,例如ReplicaSet或RC。与通常的Pod删除不同,驱逐Pod的控制器将使用/eviction接口对Pod进行驱逐,如果这一主动驱逐行为违反了PodDisruptionBudget的约定,就会被API Server拒绝。

PodDisruptionBudget本身无法真正保障指定数量或百分比的Pod的存活。例如,在一个节点中包含了目标Pod中的一个,如果节点故障,就会导致Pod数量少于minAvailable。PodDisruptionBudget对象的保护作用仅仅针对主动驱逐的场景,而非所有场景。

对PodDisruptionBudget的定义包括如下两部分。

◎ Label Selector:用于筛选被管理的Pod。

◎ minAvailable:指定驱逐过程需要保障的最少Pod数量。minAvailable可以是一个数字,也可以是一个百分比,例如100%就表示不允许进行主动驱逐。

PodDisruptionBudget示例如下。

(1) 首先创建一个Deployment,Pod数量为3个:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1apiVersion: extionsions/v1beta1
2kind: Deployment
3metadata:
4  name: nginx
5  labels:
6    name: nginx
7spec:
8  replicas: 3
9  selector:
10    matchLables:
11      name: nginx
12  template:
13    metadata:
14      labels:
15        name: nginx
16    spec:
17      containers:
18      - name: nginx
19        image: nginx
20        ports:
21        - containerPort: 80
22          protocol: TCP
23
24
25
26
27# kubectl create -f nginx.yaml
28# kubectl get pod
29

(2)  接线来创建一个PodDisruptionBudget对象:


1
2
3
4
5
6
7
8
9
10
1apiVersion: policy/v1beta1
2kind: PodDisruptionBudget
3metadata:
4  name: nginx
5spec:
6  minAvailable: 3
7  selector:
8    matchLabels:
9      name: nginx
10

PodDisruptionBudget使用的是和Deployment一样的Label Selector,并且设置存活Pod的数量不得少于3个。

 

(3)主动驱逐验证。对Pod的主动驱逐操作将通过驱逐API(/eviction)来完成。可以将这个API看作受策略控制的对Pod的DELETE操作。要实现一次主动驱逐(更准确的说法是创建一个eviction),则需要POST一个JSON请求,以eviction.json文件格式表示,内容如下:


1
2
3
4
5
6
7
8
9
10
11
1{
2  &quot;apiVersion&quot;: &quot;policy/v1beta&quot;,
3  &quot;kind&quot;: &quot;Eviction&quot;,
4  &quot;metadata&quot;: {
5     &quot;name&quot;: &quot;ngin-1968-...&quot;
6     &quot;namespace&quot;: &quot;default&quot;
7  
8 }
9
10}
11

用curl命令执行eviction操作:


1
2
1curl -v -H &#x27;Content-type: application/json&#x27;
2

由于PodDisruptionBudget设置存活的Pod数量不能少于3个,因此驱逐操作会失败,在返回的错误信息中会包含如下内容:


1
2
1&quot;message&quot;: &quot;Cannot evict pod as it would violate the pod&#x27;s disruption budget.&quot;
2

使用Kubectl get pods 查看Pod列表,会看到Pod的数量和名称没有发生变化。

最后用kubectl delete pdb nginx命令删除pbd对象。

再次执行上文中的curl指令,会执行成功。通过kubectl get pods命令查看Pod列表,会发现Pod的数量虽然没有发生变化,但是指定的Pod已经消失了,取而代之的是一个新的Pod。

 

给TA打赏
共{{data.count}}人
人已打赏
安全运维

故障复盘的简洁框架-黄金三问

2021-9-30 19:18:23

安全运维

OpenSSH-8.7p1离线升级修复安全漏洞

2021-10-23 10:13:25

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索