集群管理员参考
资源配额
当多个用户或团队共享具有固定节点数目的集群时,人们会担心有人使用超过其基于公平原则所分配到的资源量。
资源配额是帮助管理员解决这一问题的工具。
资源配额,通过 ResourceQuota
对象来定义,对每个命名空间的资源消耗总量提供限制。 它可以限制命名空间中某种类型的对象的总数目上限,也可以限制命令空间中的 Pod的总数以及使用的计算资源(cpu,memory,gpu)的总上限。
资源配额的工作方式如下:
- 集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象。
- 当用户在命名空间下创建资源(如 Pod、Service 等)时,Kubernetes 的配额系统会 跟踪集群的资源使用情况,以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额。
- 如果资源创建或者更新请求违反了配额约束,那么该请求会报错(HTTP 403 FORBIDDEN), 并在消息中给出有可能违反的约束。
- 如果命名空间下ResourceQuota的计算资源 (如 cpu 和 memory)的配额被启用,则用户在pod中必须为 这些资源设定请求值(request)和约束值(limit),否则配额系统将拒绝 Pod 的创建。 提示: 可使用 LimitRanger 准入控制器来为没有设置计算资源需求的 Pod 设置默认值。
为命名空间配置内存和 CPU 总额
这里给出一个 ResourceQuota 对象的配置文件mem-cpu-demo.yaml。cpu的单位也可以使用m表示,1表示1核,1核=1000m:
apiVersion: v1
kind: ResourceQuota
metadata:
name: laravel-resource
namespace: laravel
spec:
hard:
requests.cpu: "1.5"
requests.memory: 2Gi
limits.cpu: "2"
limits.memory: 3Gi
查看yaml文件
kubectl get quota -n laravel -o yaml laravel-resource
可以看到如下一段,used配置项中
spec:
hard:
limits.cpu: "2"
limits.memory: 3Gi
requests.cpu: 1500m
requests.memory: 2Gi
status:
hard:
limits.cpu: "2"
limits.memory: 3Gi
requests.cpu: 1500m
requests.memory: 2Gi
used:
limits.cpu: "0"
limits.memory: "0"
requests.cpu: "0"
requests.memory: "0"
该ResourceQuota 在 laravel 命名空间中设置了如下要求:
- 该命名空间下的每个容器必须有内存请求和限制,以及 CPU 请求和限制。
-
该命名空间下的所有容器
的内存请求总和不能超过2 GiB。 -
该命名空间下的所有容器
的内存限制总和不能超过3 GiB。 -
该命名空间下的所有容器
的 CPU 请求总和不能超过1.5 cpu。 -
该命名空间下的所有容器
的 CPU 限制总和不能超过2 cpu。
由上面的介绍我们可以知道resourceQuota
资源对象约束的是该命名空间下所有容器的资源总和
。
然后我们新建一个deployment,注意replicas
为2
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx-php7
namespace: laravel
labels:
name: nginx-php7
spec:
replicas: 2
selector:
matchLabels:
name: nginx-php7
template:
metadata:
labels:
name: nginx-php7
spec:
containers:
- name: nginx-php7
image: harbor.maigengduo.com/laravel/nginx-php7:20201119062847
resources:
limits:
memory: "200Mi"
cpu: "400m"
requests:
memory: "100Mi"
cpu: "200m"
我们在调度该pod的节点上查看该pod的资源使用情况,可以查看到该pod内的资源配置已经生效为limit memory为200Mi,我们当前使用了45.34Mi,cpu使用; 0.00%,100%表示1核。
docker stats d2b44c799158
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
d2b44c799158 k8s_nginx-php7_nginx-php7-55447877c6-l75h6_laravel_8157fec1-04c2-46e4-8c9e-018dc69e9058_0 0.00% 45.34MiB / 200MiB 22.67% 0B / 0B 3.38MB / 0B 7
接着我们在查看下quota资源对象,显示如下。由于上述pod的replicas为2,所以可以看到used配置中所有资源使用量翻倍。
kubectl get quota -n laravel -o yaml laravel-resource
·····
spec:
hard:
limits.cpu: "2"
limits.memory: 3Gi
requests.cpu: 1500m
requests.memory: 2Gi
status:
hard:
limits.cpu: "2"
limits.memory: 3Gi
requests.cpu: 1500m
requests.memory: 2Gi
used:
limits.cpu: 800m
limits.memory: 400Mi
requests.cpu: 400m
requests.memory: 200Mi
····
我们看到目前所有pod的memory的请求量为200Mi,而总的请求量为2Gi,接下来我们继续创建一个pod
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx-php7-backup
namespace: laravel
labels:
name: nginx-php7-backup
spec:
replicas: 1
selector:
matchLabels:
name: nginx-php7-backup
template:
metadata:
labels:
name: nginx-php7-backup
spec:
containers:
- name: nginx-php7-backup
image: harbor.maigengduo.com/laravel/nginx-php7:20201119062847
resources:
limits:
memory: "2Gi"
cpu: "600m"
requests:
memory: "1.9Gi"
cpu: "400m"
由于配置文件中的1.9Gi+200Mi>2Gi,所以查看该deployment的yaml文件
message: 'pods "nginx-php7-backup-6785856766-6bp55" is forbidden: exceeded quota:
laravel-resource, requested: requests.memory=2040109465600m, used: requests.memory=200Mi,
limited: requests.memory=2Gi'
reason: FailedCreate
可以看到返回Forbidden,表示当前的memory超出了resource资源的中的requests.memory,所以构建失败。
注意:ResourceQuota资源对象不影响已经运行的pod资源,也就是说和secret类似,当ResourceQuota资源对象更改,必须重新部署pod才能生效。
配置命名空间下 Pod 配额和 API 对象配额
下面配置文件只是列举一些常见的配额,其他配额请参考
apiVersion: v1
kind: ResourceQuota
metadata:
name: pod-demo
spec:
hard:
pods: "2"
persistentvolumeclaims: "1"
services.loadbalancers: "2"
services.nodeports: "0"
为命名空间配置默认内存请求和限制
上面介绍的ResourceQuota
资源类型是限制该命名空间内的所有pod的资源总和
,而且该命名空间下面的所有pod都需要设置limits和requests的cpu和memory。
现在有一个资源类型为LimitRange
,该类型的作用域为Container
,可以为命名空间配置默认内存请求和限制。
使用LimitRange
资源通常需要和ResourceQuota资源
配合使用。我们这里默认部署了ResourceQuota
资源,以上节中的代码为例。
部署LimitRange资源对象代码
apiVersion: v1
kind: LimitRange
metadata:
name: laravel-limit-range
namespace: laravel
spec:
limits:
- default:
memory: 100Mi
cpu: 100m
defaultRequest:
memory: 100Mi
cpu: 100m
type: Container
然后我们在部署一个deployment,注意该deployment并没有resources相关配置。
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx
namespace: laravel
labels:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
name: nginx
template:
metadata:
labels:
name: nginx
spec:
containers:
- name: nginx
image: harbor.maigengduo.com/laravel/nginx-php7:20201119062847
然后查看该pod的yaml文件,有下面一段代码,显示默认使用了LimitRange的相关配置。
[root@master ~]# kubectl get pod -n laravel -o yaml nginx-php7-ingress-76b78bd46d-2m5jh
......
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 100m
memory: 100Mi
......
查看该pod的负载情况,该pod使用内存45.3M
[root@worker2 ~]# docker stats cf5fd9c6c1bf
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
cf5fd9c6c1bf k8s_nginx-php7-ingress_nginx-php7-ingress-76b78bd46d-2m5jh_laravel_283235dd-ecf9-4208-a6b7-7e58b569350c_0 0.00% 45.34MiB / 100MiB 45.34% 0B / 0B 0B / 0B 7
注意:LimitRange资源对象不影响已经运行的pod资源,也就是说和secret类似,当LimitRange资源对象更改,必须重新部署pod才能生效。
为命名空间配置内存限制的最小值和最大值
在ReourceQuota对象所规定的requests和limits的范围内,开发人员随意定义pod中的requests和limits的大小,这是个很严重的问题呀。
还是以最上面的ResourceQuota
资源为例。
比如直接定义一个下面这样的pod, requests. memory等于ResourceQuota资源中的requests. memory。
也就是这个命名空间下只能有下面这一个pod了,后面在生产pod将会包403Forbbin。
apiVersion: v1
kind: Pod
metadata:
name: default-cpu-demo-2
spec:
containers:
- name: default-cpu-demo-2-ctr
image: nginx
resources:
limits:
memory: "3Gi"
cpu: "1"
requests:
memory: "2Gi"
cpu: "400m"
上面这个例子是个极端情况,但不排除。我们的目的是让pod中的resource值更加贴切真实场景,而不是随意设置大小,所以我们需要给pod的resource值设置一个范围。于是就有了本小结,如题为命名空间配置内存限制的最小值和最大值
。
实现该功能同样使用的是LimitRange
资源。将上节中的LimitRange
资源重新整合如下
apiVersion: v1
kind: LimitRange
metadata:
name: laravel-limit-range
namespace: laravel
spec:
limits:
- default:
memory: 100Mi
cpu: 100m
defaultRequest:
memory: 100Mi
cpu: 100m
max:
memory: 500Mi
cpu: 600m
min:
memory: 100Mi
cpu: 100m
type: Container
现在,只要在laravel 命名空间中创建容器,Kubernetes 就会执行下面的步骤:
- 如果 Container 未指定自己的内存请求和限制,将为它指定默认的内存请求和限制。
- 验证 Container 的
内存请求
是否大于
或等于100 MiB,如果小于则报forbidden。 - 验证 Container 的
内存限制
是否小于
或等于500MiB,如果大于则报forbidden。 - 验证 Container 的
cpu请求
是否大于
或等于100m,如果大于则报forbidden。 - 验证 Container 的
cpu限制
是否小于
或等于600MiB,如果大于则报forbidden。
上面介绍的是内存设置最大和最小值,其实cpu也是同理,请参考该文章。
开发人员参考
为容器和 Pod 分配内存资源
其实为容器分配内存资源在上面已经介绍过了,我们本节主要学习以下几个知识点
- 了解metrics-server,它是使用
Horizontal Pod Autoscaler
资源类型的基础,以及用于一些监控资源命令。
# 查看节点的资源消耗情况
kubectl top node
# 类似于 docker stats containerId
kubectl top pod -n namespace
- 超过容器限制的内存
做第一个实验,当容器内产生了一个bug或者某个进程疯狂消耗内存时,该容器会发生什么情况?
下面这个容器可以看到limit.memory为3G.
apiVersion: v1
kind: Pod
metadata:
name: default-cpu-demo-2
spec:
containers:
- name: default-cpu-demo-2-ctr
image: nginx
resources:
limits:
memory: "3Gi"
cpu: "1"
requests:
memory: "2Gi"
cpu: "400m"
我们进入这个容器后写一个脚本memory.sh,如下看到是个耗内存的无限循环。
#!/bin/bash
str="dfsfsfwrwwwewe1"
while [ TRUE ]
do
str="$str$str"
echo "===="
sleep 0.2
done
执行该脚本后,会看到该容器的MEN USAGE会立马飙升直至接近3G然后恢复成最初的值,接着就看到该脚本进程以及被killed掉了。
[root@nginx-php7-backup-668d86fc8b-kk7vt home]# sh memory.sh
====
====
====
====
====
====
====
====
====
====
Killed
做第二个实验:pod使用内存超过现在内存。
apiVersion: v1
kind: Pod
metadata:
name: memory-demo-2
namespace: mem-example
spec:
containers:
- name: memory-demo-2-ctr
image: polinux/stress
resources:
requests:
memory: "50Mi"
limits:
memory: "100Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]
该pod内存请求为50Mi,内存限制为100Mi,在配置文件的 args 部分中,你可以看到容器会尝试分配 250 MiB 内存,这远高于 100 MiB 的限制,启动后发生什么呢?
[root@master ~]# kubectl get pod -n laravel
NAME READY STATUS RESTARTS AGE
memory-demo-2-758bc658 0/1 OOMKilled 173 15h
过了一段时间查看,发现重启了173篇,可怕,最后查看下具体原因,发现为OOMKilled。。。
[root@master ~]# kubectl describe pod -n mysql-758bc658c8-jk968
......
......
Containers:
mysql:
Container ID: docker://6dddad72f7e1af9149e6bef5ce537b782a57f7a28cdce650c5fd6f81880ce035
Image: harbor.maigengduo.com/laravel/mysql5.7:202007071543
Image ID: docker-pullable://harbor.maigengduo.com/laravel/mysql5.7@sha256:82bc22792429e6ff0af3ade4dbf8e5e30656030fb3347defc5fcbb5ff06fdd66
Port: 3306/TCP
Host Port: 0/TCP
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Fri, 04 Dec 2020 08:54:14 +0800
Finished: Fri, 04 Dec 2020 08:54:19 +0800
......
......
总结:由第一个实验我们可以看到container中的某个进程(非主进程)使用内存接近limit.memory值时也就是内存不够时,会killed掉该进程。
由第二个实验可以看出,当节点拥有足够的可用内存时,容器可以使用其请求的内存。 但是,容器不允许使用超过其限制的内存。 如果容器分配的内存超过其限制,该容器会成为被终止的候选容器。 如果容器继续消耗超出其限制的内存,则终止容器。 如果终止的容器可以被重启,则 kubelet 会重新启动它,就像其他任何类型的运行时失败一样。
配置resource资源的重要性:如果不配置,container默认的limit限制内存是所在节点的可用内存,如果该container中某个进程疯狂消耗内存,将会耗尽所有可用节点内存为止,这样会造成其他的服务不可用,也有可能会杀死一些系统守护进程,所以这是一个非常可怕的事情。
- 超过整个节点容量的内存
内存请求和限制是与容器关联的,但将 Pod 视为具有内存请求和限制,也是很有用的。Pod 的内存请求是 Pod 中所有容器的内存请求之和。 同理,Pod 的内存限制是 Pod 中所有容器的内存限制之和。
Pod 的调度基于请求。只有当节点拥有足够满足 Pod 内存请求的内存时,才会将 Pod 调度至节点上运行
,这点一定要明白。看下面的代码
apiVersion: v1
kind: Pod
metadata:
name: memory-demo-3
namespace: mem-example
spec:
containers:
- name: memory-demo-3-ctr
image: polinux/stress
resources:
limits:
memory: "1000Gi"
requests:
memory: "1000Gi"
- name: memory-demo-4-ctr
image: polinux/stress
resources:
limits:
memory: "1000Gi"
requests:
memory: "1000Gi"
如果该pod想要被调度,被调度的节点需要有2000Gi的可用内存,很明显这是不可能的,所以该pod不会被调度到任何的节点上,会一直处于pendding
状态。
kubectl get pod -n mem-example
NAME READY STATUS RESTARTS AGE
memory-demo-3 0/1 Pending 0 25s
查看关于 Pod 的详细信息,包括事件:
kubectl describe pod memory-demo-3 --namespace=mem-example
输出结果显示:由于节点内存不足,该容器无法被调度:
Events:
... Reason Message
------ -------
... FailedScheduling No nodes are available that match all of the following predicates:: Insufficient memory (3).
为容器和 Pod 分配CPU资源
在这节中我们主要测试容器内的cpu使用量超过limit.cpu时,容器的表现情况。
我们创建一个如下的pod,limit.cpu为2,2表示2核cpu,1核为1000m,100%表示1核,如果拿百分比表示就是200%。
apiVersion: v1
kind: Pod
metadata:
name: memory-demo-3
namespace: cpu-example
spec:
containers:
- name: cpu-demo
image: polinux/stress
resources:
limits:
cpu: 2"
requests:
cpu: "0.5"
我们进入该容器内执行如下语句,大概意思将空数据持续输出到/dev/null,相当于copy操作,所以该命令会跑满cpu。
dd if=/dev/zero of=/dev/null &
dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。
if=文件名:输入文件名,缺省为标准输入。即指定源文件。< if=input file >
of=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >
执行完上面这个命令后,我们查看该container的资源消耗情况,**我们会看到该容器的cpu会立即跑满为100%,并一直持续在1000%,表明已经占用1核了。
docker status 8f40fe0fd2b7
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
8f40fe0fd2b7 k8s_nginx-php7-backup_nginx-php7-backup-668d86fc8b-kk7vt_laravel_74aa3803-0794-4f8d-8e2a-2bc9beba3920_0 100% 71.81MiB / 200MiB 35.90% 0B / 0B 105MB /
此时我们在执行一次dd命令,发现cpu的使用率达到200%,说明已经使用2核,然后再次执行dd命令,发现cpu使用率持续在200%保持不动了,重点是container并没有像memory那样killed掉该进程。
由此得出如下结论
memory为不可压缩资源,memory不够时会立即killed掉使用最多内存的进程。cpu为可压缩资源,在cpu达到最大值时不会kill进程。
配置 Pod 的服务质量
本节介绍怎样配置 Pod 让其获得特定的服务质量(QoS)类。Kubernetes 使用 QoS 类来决定 Pod 的调度和驱逐策略。
pod的QoS类有如下3中情况
- Guaranteed (可靠)
- Burstable(基本可靠)
- BestEffort(不可靠)
Guaranteed
对于 QoS 类为 Guaranteed 的 Pod:
- Pod 中的每个容器,包含初始化容器,
必须指定内存请求和内存限制,并且两者要相等
。 - Pod 中的每个容器,包含初始化容器,
必须指定 CPU 请求和 CPU 限制,并且两者要相等
。
查看pod的yaml文件,在最末尾会看到qosClass关键字
spec:
containers:
...
resources:
limits:
cpu: 700m
memory: 200Mi
requests:
cpu: 700m
memory: 200Mi
...
status:
qosClass: Guaranteed
Burstable
对于 QoS 类为 Guaranteed 的 Pod:
- Pod 不符合 Guaranteed QoS 类的标准。
- Pod 中
至少一个
容器具有内存或 CPU请求
。
BestEffort
对于 QoS 类为 BestEffort 的 Pod,Pod 中的容器必须没有设置内存和 CPU 限制或请求。例如下面这个pod
piVersion: v1
kind: Pod
metadata:
name: qos-demo-3
namespace: qos-example
spec:
containers:
- name: qos-demo-3-ctr
image: nginx
pod驱逐策略
Kubelet主动监测和防止worker节点
上计算资源的全面短缺。在worker节点
资源短缺时,kubelet
可以主动地结束一个或多个 Pod 以回收短缺的资源。 当 kubelet
结束一个 Pod 时,它将终止 Pod 中的所有容器,而 Pod 的 Phase
将变为 Failed
。 如果被驱逐的 Pod 由 Deployment 管理,这个 Deployment 会创建另一个 Pod 给 Kubernetes 来调度。
驱逐信号
什么是驱逐信号呢?简单理解就是,kubelet什么时候什么场景下开始驱逐pod呢?总的有几个参考值吧,这就是驱逐信号,k8s提供了以下几种驱逐信号
驱逐信号 | 描述 |
---|---|
memory.available | node.status.capacity[memory] - node.stats.memory.workingSet |
nodefs.available | node.stats.fs.available |
nodefs.inodesFree | node.stats.fs.inodesFree |
imagefs.available | node.stats.runtime.imagefs.available |
imagefs.inodesFree | node.stats.runtime.imagefs.inodesFree |
memory.available是针对内存资源,其余4个驱逐信号都是针对磁盘空间的。
上面的每个信号都支持字面值或百分比的值。基于百分比的值的计算与每个信号对应的总容量相关。
memory.available 的值从 cgroupfs 获取,而不是通过类似 free -m 的工具。 这很重要,因为 free -m 不能在容器中工作
kubelet 只支持两种文件系统分区
。
- nodefs 文件系统,kubelet 将其用于卷和守护程序日志等。
- imagefs 文件系统,容器运行时用于保存镜像和容器可写层。
imagefs 可选。kubelet 使用 cAdvisor 自动发现这些文件系统。 kubelet 不关心其它文件系统。当前不支持配置任何其它类型。 例如,在专用 filesytem 中存储卷和日志是 不可以 的。
可能看到这里对那倆
文件系统分区
有点懵逼。我们可以想想worker节点中什么最耗磁盘空间,无非也就下面几项
- 镜像文件:有的很大,几百M。每次更新deployment,都会拉取新的镜像,长此以往,不通版本的镜像肯定要消耗很大磁盘空间的。
imagefs
文件系统正是管理这些镜像的,如果我们配置的驱逐信号中有imagefs.available
或者imagefs.inodesFree
,且触发驱逐阀值后,k8s将会主动清理一些没有使用的版本镜像,以此来释放更多的磁盘空间。
- 日志:随着应用的长时间运行,日志会越来越庞大,volume不管你使用的是emptyDir,hostPath,还是nfs,clustersfs,都会将日志存储在节点上的,占用worker节点的磁盘空间也将会越来越大。docker中的日志分为俩种,docker主进程的标准输出日志和项目中用户产生的文件日志,正好对应
nodefs
文件系统中的卷和守护程序日志。如果我们配置的驱逐信号中有nodefs.available
或者nodefs.inodesFree
且触发驱逐阀值后,k8s将会清理这些日志,清理这些的日志的方式只能是驱逐pod了。
软驱逐阈值
使用一对由驱逐阈值和宽限期组成的配置对。在宽限期内持续
超过阀值则进行驱逐。比如下面这俩行配置,表示的意义是当节点的memory.available小于500Mi,且在1分30内持续小于500Mi时,开启触发驱逐。下面这俩行是成对出现的。
软驱逐阈值使用关键字eviction-soft
--eviction-soft=memory.available<500Mi
--eviction-soft-grace-period=memory.available=1m30s
硬驱逐阈值
相对于软驱逐阈值,区别就是没有缓冲时间,只要触发驱逐信号,就立即开始驱逐而不是优雅的终止。
硬驱逐阈值使用关键字eviction-hard
。比如下面例子
--eviction-hard=memory.available<500Mi,nodefs.available<20%
kubelet 有如下所示的
默认
硬驱逐阈值:memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%
节点状态振荡
如果使用的是软驱逐阈值,则会有下面这种情况发生:
节点在软驱逐阈值的上下振荡,但没有超过关联的宽限期时,将引起对应节点的状态持续在 true 和 false 间跳变,并导致不好的调度结果。
为了防止这种振荡,可以定义下面的标志,用于控制 kubelet 从压力状态中退出之前必须等待的时间。
- eviction-pressure-transition-period 是 kubelet 从压力状态中退出之前必须等待的时长。
kubelet 将确保在设定的时间段内没有发现和指定压力条件相对应的驱逐阈值被满足时,才会将状态变回 false。
--eviction-pressure-transition-period=1m30s
最小驱逐回收
在某些场景,驱逐 pod 只回收了少量资源。这将有可能导致 kubelet 反复碰到驱逐阈值,然后反复驱逐pod。
为了减少这类问题,kubelet可以为每个资源配置一个 minimum-reclaim。 当 kubelet 发现资源压力时,kubelet将尝试至少回收驱逐阈值之下 minimum-reclaim 数量的资源。
例如使用下面的配置:
--eviction-hard=memory.available<500Mi,nodefs.available<1Gi,imagefs.available<100Gi
--eviction-minimum-reclaim="memory.available=`100Mi,nodefs.available=500Mi,imagefs.available=2Gi"`
如果 memory.available 驱逐阈值被触发,kubelet 将保证 memory.available 至少为 (500+100)Mi。 如果 nodefs.available驱逐阈值被触发,kubelet 将保证 nodefs.available 至少为 (1Gi+500Mi)。如果 imagefs.available驱逐阈值被触发,kubelet 将保证 imagefs.available 至少为 (100+2)Gi, 直到不再有相关资源报告压力为止。
所有资源的默认 eviction-minimum-reclaim 值为 0。
磁盘空间紧缺
如果满足驱逐阈值并超过了宽限期,kubelet将启动回收压力资源的过程,直到它发现低于设定阈值的信号为止。
kubelet 将尝试在驱逐pod前回收节点层级资源,发现磁盘压力时,通过会根据下面俩个步骤执行
- 回收层级资源
如果节点针对容器运行时配置有独占的 imagefs,kubelet回收节点层级资源的方式将会不同,这句话理解有点不太明白,如果你知道,请留言指教。
我的理解是在配置驱逐信号时是否使用imagefs相关的信号:
-
- 如果
nodefs
文件系统满足驱逐阈值,kubelet
通过驱逐 pod 及其容器来释放磁盘空间。 - 如果
imagefs
文件系统满足驱逐阈值,kubelet
通过删除所有未使用的镜像来释放磁盘空间。
- 如果
-
- 如果
nodefs
满足驱逐阈值,kubelet
将以下面的顺序释放磁盘空间:
- 删除停止运行的 pod/container
- 删除全部没有使用的镜像
- 如果
- 驱逐pod
如果 kubelet 在节点上无法回收足够的层级资源,kubelet将开始驱逐 pod。
还记得我们上面章节中提到的pod服务质量吗?有Guaranteed,Burstable和BestEffort 3种服务质量。
驱逐pod的顺序就是根据pod的服务质量来排序的
,大致顺序如下,这是我理解的:
- 找出
BestEffort
类型的pod和Burstable
类型超过出请求
的pod,这类pod按照优先级排序,优先级低的优先被驱逐,以此类推,直至满足驱逐信号。如果pod未设置的优先级则默认为0,驱逐顺序如下(当前所有pod的优先级都为0)- 先驱逐
BestEffort
类型的pod,驱逐顺序为pod请求内存从高到低依次逐出。 - 如果逐出所有BestEffort类型的pod还没有满足,则开始逐出
Burstable
类型超出请求的pod,逐出顺序同样为请求从高到低。
- 先驱逐
- 剩下的都是Guaranteed和 Burstable类型且其使用率低于请求的pod,
正常低于请求值的pod是不会被驱逐的
。但是有一种特殊情况:如果系统守护进程(例如 kubelet、docker、和 journald)消耗的资源多于通过 system-reserved 或 kube-reserved 分配保留的资源时,为保持节点的稳定性并限制意外消耗对其他 pod 的影响,此时节点就会驱逐这些低于请求的pod,驱逐顺序为优先级由低到高。
优先级和服务质量的关系:
正常情况下
逐出操作不会逐出 Pod 资源用量未超出其请求值的 Pod
,如果优先级较低的 Pod 未超出其请求值,它们不会被逐出。其他优先级较高的 且用量超出请求值的 Pod 则可能被逐出。
内存空间紧缺
如果节点在 kubelet
回收内存之前经历了系统 OOM(内存不足)事件,它将基于 oom-killer 做出响应。
kubelet
基于 pod 的 service 质量为每个容器设置一个 oom_score_adj
值。
Service 质量 | oom_score_adj |
---|---|
Guaranteed |
-998 |
BestEffort |
1000 |
Burstable |
min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999) |
如果 kubelet
在节点经历系统 OOM 之前无法回收内存,oom_killer
将基于它在节点上 使用的内存百分比算出一个 oom_score
,并加上 oom_score_adj
得到容器的有效 oom_score
,然后结束得分最高的容器。
预期的行为应该是拥有最低服务质量并消耗和调度请求相关内存量最多的容器第一个被结束,以回收内存。
和 pod 驱逐不同,如果一个 Pod 的容器是被 OOM 结束的,基于其 RestartPolicy
, 它可能会被 kubelet
重新启动。
内存OOM时,结束顺序大致如下
- 结束
BestEffort
类型的pod,结束顺序为使用内存从高到低。 - 结束
Burstable
类型的pod,先结束超过请求内存的pod,超出请求内存的pod逐出顺序也是请求内存从高到低;其次再结束未超过请求的pod,结束的顺序都是内存占用从高到低。 - 结束
Guaranteed
类型的pod,结束顺序为使用内存从高到低。
为系统守护进程预留计算资源
默认情况下 pod 能够使用节点全部可用容量。 这是个问题,因为节点自己通常运行了不少驱动 OS 和 Kubernetes 的系统守护进程。 除非为这些系统守护进程留出资源,否则它们将与 pod 争夺资源并导致节点资源短缺问题。
resource最佳实践
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
EvictionSoft:
- memory.available < 300Mi
EvictionSoftGracePeriod:
- memory.available=1m30s
SystemReserved:
- memory=500Mi
EvictionPressureTransitionPeriod:
- memory.available=1m30s
EvictionMinimumReclaim:
- memory.available=100Mi
EvictionHard:
- nodefs.available<1Gi
- imagefs.available<1Gi
上述场景如下:
- 节点内存容量300Mi
- 操作员希望为系统守护进程保留
200Mi
内存容量(内核、kubelet等),这个配置的暗示是理解系统保留应该包含被驱逐阈值覆盖的内存数量
。 - 别的参数就不解释了上面都有。
具体的部署kubelet可参考该文章
延伸
伪装超额现象
前提:某命名空间ResourceQuota的limit.cpu为2,该命名空间下面有4个pod,每个pod的limit.cpu都为450m。
450x4=1800=1.8G ,1.8G<2G
由上面的运算我们知道4个pod满足该命名空间下ResourceQuota
资源限制,所以在第一次
部署4个pod时候都可以成功,pod显示running状态。
随后更新podA,执行完kubeclt apply -f yaml之后查看pod状态,发现并没有更新,状态还是第一次部署的pod,是不是很奇怪。。。
然后我查看所属deployment信息,发现如下
[root@master docker]# kubectl get deploy -n laravel -o yaml nginx-php7-ingress.
.......
.......
- lastTransitionTime: "2020-11-25T07:35:37Z"
lastUpdateTime: "2020-11-25T07:35:37Z"
message: 'pods "nginx-php7-ingress-7cf795845f-x9lgs" is forbidden: exceeded quota:
laravel-resource, requested: limits.cpu=450m, used: limits.cpu=1800m, limited:
limits.cpu=2'
reason: FailedCreate
status: "True"
type: ReplicaFailure
......
提示信息已经很明显了,就是超出了ResourceQuota资源类型中的limit.cpu了,可是明明是1.8G,为什么会超出2G呢?
其实很简单,deployment默认的部署方式是滚动部署,简单的说就是新pod running之前,旧的pod还存在,新pod处于running状态了,旧的pod才会开始Terminating状态。
现在就明白了,为什么提示超过2G:其实更新podA时使用的滚动部署方式,也就是说更新时是相当于存在5个pod,
450x5=2250=2.25G, 2.25G>2G
到这里我们在延伸一点东西。针对上面这种情况,我们ResourceQuota和pod该设置多大呢?
比如我们每次k8s自动化部署时都会更新x,y,z3个deployment,且该deployment只有一个pod,n表示ResourceQuota中的限制,表达式如下
2(x+y+z)<=n
到这里我们就明白了,也希望各位小伙伴注意哦,笔芯。