13.12、Kubernetes节点的驱逐与预留
K8S 的节点上的资源会被 pod 和系统进程所使用,如果默认什么都不配置,那么节点上的全部资源都是可以分配给pod使用的,系统进程本身没有保障,这样做很危险:
节点资源主要分为两类:
oom 分数:
image 所以,OOM 的优先级如下:
BestEffort Pod > Burstable Pod > 其它进程 > Guaranteed Pod > kubelet/docker 等 > sshd 等进程
因此需要对节点的内存等资源进行配置,以保证节点核心进程运行正常。
节点资源的配置一般分为 2 种:
image
allocatable的值即对应 describe node 时看到的allocatable容量,pod 调度的上限
计算公式:节点上可配置值 = 总量 - 预留值 - 驱逐阈值
Allocatable = Capacity - Reserved(kube+system) - Eviction Threshold
以上配置均在kubelet 中添加,涉及的参数有:
--enforce-node-allocatable=pods,kube-reserved,system-reserved
--kube-reserved-cgroup=/system.slice/kubelet.service
--system-reserved-cgroup=/system.slice
--kube-reserved=cpu=200m,memory=250Mi
--system-reserved=cpu=200m,memory=250Mi
--eviction-hard=memory.available<5%,nodefs.available<10%,imagefs.available<10%
--eviction-soft=memory.available<10%,nodefs.available<15%,imagefs.available<15%
--eviction-soft-grace-period=memory.available=2m,nodefs.available=2m,imagefs.available=2m
--eviction-max-pod-grace-period=30
--eviction-minimum-reclaim=memory.available=0Mi,nodefs.available=500Mi,imagefs.available=500Mi
配置的含义如下: (1)--enforce-node-allocatable
含义:指定kubelet为哪些进程做硬限制,可选的值有:
* pods
* kube-reserved
* system-reserve
这个参数开启并指定pods后kubelet会为所有pod的总cgroup做资源限制(通过cgroup中的kubepods.limit_in_bytes),限制为公式计算出的allocatable的大小。
假如想为系统进程和k8s进程也做cgroup级别的硬限制,还可以在限制列表中再加system-reserved和kube-reserved,同时还要分别加上--kube-reserved-cgroup和--system-reserved-cgroup以指定分别限制在哪个cgroup里。
配置:--enforce-node-allocatable=pods,kube-reserved,system-reserved
(2)设置k8s组件的cgroup
含义:这个参数用来指定k8s系统组件所使用的cgroup。
注意,这里指定的cgroup及其子系统需要预先创建好,kubelet并不会为你自动创建好。
配置:--kube-reserved-cgroup=/system.slice/kubelet.service
(3)设置系统守护进程的cgroup
含义:这个参数用来指定系统守护进程所使用的cgroup。
注意,这里指定的cgroup及其子系统需要预先创建好,kubelet并不会为你自动创建好。
配置:--system-reserved-cgroup=/system.slice
(4)配置 k8s组件预留资源的大小,CPU、Mem
指定为k8s系统组件(kubelet、kube-proxy、dockerd等)预留的资源量,
如:--kube-reserved=cpu=1,memory=2Gi,ephemeral-storage=1Gi。
这里的kube-reserved只为非pod形式启动的kube组件预留资源,假如组件要是以static pod(kubeadm)形式启动的,那并不在这个kube-reserved管理并限制的cgroup中,而是在kubepod这个cgroup中。
(ephemeral storage需要kubelet开启feature-gates,预留的是临时存储空间(log,EmptyDir),生产环境建议先不使用)
ephemeral-storage是kubernetes1.8开始引入的一个资源限制的对象,kubernetes 1.10版本中kubelet默认已经打开的了,到目前1.11还是beta阶段,主要是用于对本地临时存储使用空间大小的限制,如对pod的empty dir、/var/lib/kubelet、日志、容器可读写层的使用大小的限制。
(5)配置 系统守护进程预留资源的大小(预留的值需要根据机器上容器的密度做一个合理的值)
含义:为系统守护进程(sshd, udev等)预留的资源量,
如:--system-reserved=cpu=500m,memory=1Gi,ephemeral-storage=1Gi。
注意,除了考虑为系统进程预留的量之外,还应该为kernel和用户登录会话预留一些内存。
配置:--system-reserved=cpu=200m,memory=250Mi
(6)配置 驱逐pod的硬阈值
含义:设置进行pod驱逐的阈值,这个参数只支持内存和磁盘。
通过--eviction-hard标志预留一些内存后,当节点上的可用内存降至保留值以下时,
kubelet 将会对pod进行驱逐。
配置:--eviction-hard=memory.available<5%,nodefs.available<10%,imagefs.available<10%
(7)配置 驱逐pod的软阈值
--eviction-soft=memory.available<10%,nodefs.available<15%,imagefs.available<15%
(8)定义达到软阈值之后,持续时间超过多久才进行驱逐
--eviction-soft-grace-period=memory.available=2m,nodefs.available=2m,imagefs.available=2m
(9)驱逐pod前最大等待时间=min(pod.Spec.TerminationGracePeriodSeconds, eviction-max-pod-grace-period),单位为秒
--eviction-max-pod-grace-period=30
(10)至少回收的资源量
--eviction-minimum-reclaim=memory.available=0Mi,nodefs.available=500Mi,imagefs.available=500Mi
以上配置均为百分比,举例: 以2核4GB内存40GB磁盘空间的配置为例,Allocatable是1.6 CPU,3.3Gi 内存,25Gi磁盘。当pod的总内存消耗大于3.3Gi或者磁盘消耗大于25Gi时,会根据相应策略驱逐pod。
kubelet 利用metric的值作为决策依据来触发驱逐行为,下面内容来自于 Kubelet summary API。 一旦超出阈值,就会触发 kubelet 进行资源回收的动作(区别于软驱逐,有宽限期),指标如下:
image
例如如果一个 Node 有 10Gi 内存,我们希望在可用内存不足 1Gi 时进行驱逐,就可以选取下面的一种方式来定义驱逐阈值:
可以配置百分比或者实际值,但是操作符只能使用小于号,即<
软阈值需要和一个宽限期参数协同工作。当系统资源消耗达到软阈值时,这一状况的持续时间超过了宽限期之前,Kubelet 不会触发任何动作。如果没有定义宽限期,Kubelet 会拒绝启动。 另外还可以定义一个 Pod 结束的宽限期。如果定义了这一宽限期,那么 Kubelet 会使用 pod.Spec.TerminationGracePeriodSeconds 和最大宽限期这两个值之间较小的那个(进行宽限),如果没有指定的话,kubelet 会不留宽限立即杀死 Pod。 软阈值的定义包括以下几个参数:
Housekeeping interval 参数定义一个时间间隔,Kubelet 每隔这一段就会对驱逐阈值进行评估。
如果触发了硬阈值,或者符合软阈值的时间持续了与其对应的宽限期,Kubelet 就会认为当前节点压力太大,下面的节点状态定义描述了这种对应关系。
image
Kubelet 会持续报告节点状态的更新过程,这一频率由参数 —node-status-update-frequency 指定,缺省情况下取值为 10s。
如果一个节点的状况在软阈值的上下波动,但是又不会超过他的宽限期,将会导致该节点的状态持续的在是否之间徘徊,最终会影响降低调度的决策过程。
要防止这种状况,下面的标志可以用来通知 Kubelet,在脱离pressure之前,必须等待。
eviction-pressure-transition-period
定义了在脱离pressure状态之前要等待的时间
Kubelet 在把pressure状态设置为 False 之前,会确认在周期之内,该节点没有达到阈值
如果达到了驱逐阈值,并且超出了宽限期,那么 Kubelet 会开始回收超出限量的资源,直到回到阈值以内。 Kubelet 在驱逐用户 Pod 之前,会尝试回收节点级别的资源。如果服务器为容器定义了独立的 imagefs,他的回收过程会有所不同。 有 Imagefs 如果 nodefs 文件系统到达了驱逐阈值,kubelet 会按照下面的顺序来清理空间:
如果 imagefs 文件系统到达了驱逐阈值,kubelet 会按照下面的顺序来清理空间:
没有 Imagefs 如果 nodefs 文件系统到达了驱逐阈值,kubelet 会按照下面的顺序来清理空间。
Kubelet 会按照下面的标准对 Pod 的驱逐行为进行评判:
接下来,Pod 按照下面的顺序进行驱逐(QOS):
参考 POD的QOS:服务质量等级 Guaranteed Pod 不会因为其他 Pod 的资源被驱逐。如果系统进程(例如 kubelet、docker、journald 等)消耗了超出 system-reserved 或者 kube-reserved 的资源,而且这一节点上只运行了 Guaranteed Pod,那么为了保证节点的稳定性并降低异常请求对其他 Guaranteed Pod 的影响,必须选择一个 Guaranteed Pod 进行驱逐。 本地磁盘是一个 BestEffort 资源。如有必要,kubelet 会在 DiskPressure 的情况下,kubelet 会按照 QoS 进行评估。如果 Kubelet 判定缺乏 inode 资源,就会通过驱逐最低 QoS 的 Pod 的方式来回收 inodes。如果 kubelet 判定缺乏磁盘空间,就会通过在相同 QoS 的 Pods 中,选择消耗最多磁盘空间的 Pod 进行驱逐。
有 Imagefs
没有 Imagefs
例如下面的配置:
--eviction-hard=memory.available<500Mi,nodefs.available<1Gi,imagefs.available<100Gi
--eviction-minimum-reclaim="memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi"
缺省情况下,所有资源的 eviction-minimum-reclaim 为 0。 在节点资源紧缺的情况下,调度器将不再继续向此节点部署新的 Pod
如果节点在 Kubelet 能够回收内存之前,遭遇到了系统的 OOM (内存不足),节点就依赖 oom_killer 进行响应了。 kubelet 根据 Pod 的 QoS 为每个容器设置了一个 oom_score_adj 值。
image 如果 kubelet 无法在系统 OOM 之前回收足够的内存,oom_killer 就会根据根据内存使用比率来计算 oom_score,得出结果和 oom_score_adj 相加,最后得分最高的 Pod 会被首先驱逐。 跟 Pod 驱逐不同,如果一个 Pod 的容器被 OOM 杀掉,他是可能被 kubelet 根据 RestartPolicy 重启的。
因为 DaemonSet 中的 Pod 会立即重建到同一个节点,所以 Kubelet 不应驱逐 DaemonSet 中的 Pod。 但是目前 Kubelet 无法分辨一个 Pod 是否由 DaemonSet 创建。如果Kubelet 能够识别这一点,那么就可以先从驱逐候选列表中过滤掉 DaemonSet 的 Pod。 一般来说,强烈建议 DaemonSet 不要创建 BestEffort Pod,而是使用 Guaranteed Pod,来避免进入驱逐候选列表。
Kubelet 目前从 cAdvisor 定时获取内存使用状况统计。如果内存使用在这个时间段内发生了快速增长,Kubelet 就无法观察到 MemoryPressure,可能会触发 OOMKiller。我们正在尝试将这一过程集成到 memcg 通知 API 中,来降低这一延迟,而不是让内核首先发现这一情况。 如果用户不是希望获得终极使用率,而是作为一个过量使用的衡量方式,对付这一个问题的较为可靠的方式就是设置驱逐阈值为 75% 容量。这样就提高了避开 OOM 的能力,提高了驱逐的标准,有助于集群状态的平衡。
这也是因为状态搜集的时间差导致的。未来会加入功能,让根容器的统计频率和其他容器分别开来(https://github.com/google/cadvisor/issues/1247)。
目前不可能知道一个容器消耗了多少 inode。如果 Kubelet 觉察到了 inode 耗尽,他会利用 QoS 对 Pod 进行驱逐评估。在 cadvisor 中有一个 issue,来跟踪容器的 inode 消耗,这样我们就能利用 inode 进行评估了。例如如果我们知道一个容器创建了大量的 0 字节文件,就会优先驱逐这一 Pod
1、资源预留需要设置,pod 的 limit 也要设置。 2、cpu是可压缩资源,内存、磁盘资源是不可压缩资源。内存一定要预留,CPU可以根据实际情况来调整 3、预留多少合适:根据集群规模设置阶梯,如下(GKE建议): Allocatable = Capacity - Reserved - Eviction Threshold 对于内存资源:
对于 CPU 资源:
对于磁盘资源(不是正式特性,仅供参考):
image 效果:查看节点的可分配资源:
kubectl describe node [NODE_NAME] | grep Allocatable -B 4 -A 3
--eviction-hard=memory.available<5%,nodefs.available<10%,imagefs.available<10%
--eviction-soft=memory.available<10%,nodefs.available<15%,imagefs.available<15%
--eviction-soft-grace-period=memory.available=2m,nodefs.available=2m,imagefs.available=2m
--eviction-max-pod-grace-period=30
--eviction-minimum-reclaim=memory.available=0Mi,nodefs.available=500Mi,imagefs.available=500Mi
完整的一个kubelet配置如下:
[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/
[Service]
ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/pids/system.slice/kubelet.service
ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/cpu/system.slice/kubelet.service
ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/cpuacct/system.slice/kubelet.service
ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/cpuset/system.slice/kubelet.service
ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/memory/system.slice/kubelet.service
ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/systemd/system.slice/kubelet.service
ExecStart=/usr/bin/kubelet \
--enforce-node-allocatable=pods,kube-reserved \
--kube-reserved-cgroup=/system.slice/kubelet.service \
--kube-reserved=cpu=200m,memory=250Mi \
--eviction-hard=memory.available<5%,nodefs.available<10%,imagefs.available<10% \
--eviction-soft=memory.available<10%,nodefs.available<15%,imagefs.available<15% \
--eviction-soft-grace-period=memory.available=2m,nodefs.available=2m,imagefs.available=2m \
--eviction-max-pod-grace-period=30 \
--eviction-minimum-reclaim=memory.available=0Mi,nodefs.available=500Mi,imagefs.available=500Mi
Restart=always
StartLimitInterval=0
RestartSec=10
[Install]
WantedBy=multi-user.target
没有写system-reserved参数,是因为有些场景系统预留的资源不对,会报device busy的错误。 参考: https://github.com/rootsongjc/qa/issues/3
原文:https://www.jianshu.com/p/5dcf04e8d56b