线上被驱逐实例数据

最近在线上发现很多实例处于 Evicted 状态,通过 pod yaml 可以看到实例是因为节点资源不足被驱逐,但是这些实例并没有被自动清理,平台的大部分用户在操作时看到服务下面出现 Evicted 实例时会以为服务有问题或者平台有问题的错觉,影响了用户的体验。而这部分 Evicted 状态的 Pod 在底层关联的容器其实已经被销毁了,对用户的服务也不会产生什么影响,也就是说只有一个 Pod 空壳在 k8s 中保存着,但需要人为手动清理。本文会分析为什么为产生 Evicted 实例、为什么 Evicted 实例没有被自动清理以及如何进行自动清理。

kubernetes 版本:v1.17

$ kubectl get pod | grep -i Evicted
cloud-1023955-84421-49604-5-deploy-c-7748f8fd8-hjqsh        0/1     Evicted   0          73d
cloud-1023955-84421-49604-5-deploy-c-7748f8fd8-mzd8x        0/1     Evicted   0          81d
cloud-1237162-276467-199844-2-deploy-7bdc7c98b6-26r2r       0/1     Evicted   0          18d

Evicted 实例状态:

status:message: 'Pod The node had condition: [DiskPressure]. 'phase: Failedreason: EvictedstartTime: "2021-09-14T10:42:32Z"

实例被驱逐的原因

kubelet 默认会配置节点资源不足时驱逐实例的策略,当节点资源不足时 k8s 会停止该节点上实例并在其他节点启动新实例,在某些情况下也可通过配置 --eviction-hard= 参数为空来禁用驱逐策略,在之前的生产环境中我们也确实这么做了。

节点资源不足导致实例被驱逐

k8s 中产生 Evicted 状态实例主要是因为节点资源不足实例主动被驱逐导致的,kubelet eviction_manager 模块会定期检查节点内存使用率、inode 使用率、磁盘使用率、pid 等资源,根据 kubelet 的配置当使用率达到一定阈值后会先回收可以回收的资源,若回收后资源使用率依然超过阈值则进行驱逐实例操作。

Eviction Signal Description
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
pid.available pid.available := node.stats.rlimit.maxpid - node.stats.rlimit.curproc

kubelet 中 pod 的 stats 数据一部分是通过 cAdvisor 接口获取到的,一部分是通过 CRI runtimes 的接口获取到的。

  • memory.available:当前节点可用内存,计算方式为 cgroup memory 子系统中 memory.usage_in_bytes 中的值减去 memory.stat 中 total_inactive_file 的值;

  • nodefs.available:nodefs 包含 kubelet 配置中 --root-dir 指定的文件分区和 /var/lib/kubelet/ 所在的分区磁盘使用率;

  • nodefs.inodesFree:nodefs.available 分区的 inode 使用率;

  • imagefs.available:镜像所在分区磁盘使用率;

  • imagefs.inodesFree:镜像所在分区磁盘 inode 使用率;

  • pid.available:/proc/sys/kernel/pid_max 中的值为系统最大可用 pid 数;

kubelet 可以通过参数 --eviction-hard 来配置以上几个参数的阈值,该参数默认值为 imagefs.available<15%,memory.available<100Mi,nodefs.available<10%,nodefs.inodesFree<5%,当达到阈值时会驱逐节点上的容器。

kubelet 驱逐实例时与资源处理相关的已知问题

1、kubelet 不会实时感知到节点内存数据的变化

kubelet 定期通过 cadvisor 接口采集节点内存使用数据,当节点短时间内内存使用率突增,此时 kubelet 无法感知到也不会有 MemoryPressure 相关事件,但依然会调用 OOMKiller 停止容器。可以通过为 kubelet 配置 --kernel-memcg-notification 参数启用 memcg api,当触发 memory 使用率阈值时 memcg 会主动进行通知;

memcg 主动通知的功能是 cgroup 中已有的,kubelet 会在 /sys/fs/cgroup/memory/cgroup.event_control 文件中写入 memory.available 的阈值,而阈值与 inactive_file 文件的大小有关系,kubelet 也会定期更新阈值,当 memcg 使用率达到配置的阈值后会主动通知 kubelet,kubelet 通过 epoll 机制来接收通知。

2、kubelet memory.available 不会计算 active page

kubelet 通过内存使用率驱逐实例时,内存使用率数据包含了 page cache 中 active_file 的数据,在某些场景下会因 page cache 过高导致内存使用率超过阈值会造成实例被驱逐,

由于在内存紧张时 inactive_file 会被内核首先回收,但在内存不足时,active_file 也会被内核进行回收,社区对此机制也有一些疑问,针对内核回收内存的情况比较复杂,社区暂时还未进行回应,详情可以参考 kubelet counts active page cache against memory.available (maybe it shouldn’t?)[1]

kubelet 计算节点可用内存的方式如下:

#!/bin/bash
#!/usr/bin/env bash# This script reproduces what the kubelet does
# to calculate memory.available relative to root cgroup.# current memory usage
memory_capacity_in_kb=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
memory_capacity_in_bytes=$((memory_capacity_in_kb * 1024))
memory_usage_in_bytes=$(cat /sys/fs/cgroup/memory/memory.usage_in_bytes)
memory_total_inactive_file=$(cat /sys/fs/cgroup/memory/memory.stat | grep total_inactive_file | awk '{print $2}')memory_working_set=${memory_usage_in_bytes}
if [ "$memory_working_set" -lt "$memory_total_inactive_file" ];
thenmemory_working_set=0
elsememory_working_set=$((memory_usage_in_bytes - memory_total_inactive_file))
fimemory_available_in_bytes=$((memory_capacity_in_bytes - memory_working_set))
memory_available_in_kb=$((memory_available_in_bytes / 1024))
memory_available_in_mb=$((memory_available_in_kb / 1024))echo "memory.capacity_in_bytes $memory_capacity_in_bytes"
echo "memory.usage_in_bytes $memory_usage_in_bytes"
echo "memory.total_inactive_file $memory_total_inactive_file"
echo "memory.working_set $memory_working_set"
echo "memory.available_in_bytes $memory_available_in_bytes"
echo "memory.available_in_kb $memory_available_in_kb"
echo "memory.available_in_mb $memory_available_in_mb"

驱逐实例未被删除原因分析

源码中对于 Statefulset 和 DaemonSet 会自动删除 Evicted 实例,但是对于 Deployment 不会自动删除。阅读了部分官方文档以及 issue,暂未找到官方对 Deployment Evicted 实例未删除原因给出解释。

statefulset:pkg/controller/statefulset/stateful_set_control.go

// Examine each replica with respect to its ordinal
for i := range replicas {// delete and recreate failed podsif isFailed(replicas[i]) {ssc.recorder.Eventf(set, v1.EventTypeWarning, "RecreatingFailedPod","StatefulSet %s/%s is recreating failed Pod %s",set.Namespace,set.Name,replicas[i].Name)if err := ssc.podControl.DeleteStatefulPod(set, replicas[i]); err != nil {return &status, err}if getPodRevision(replicas[i]) == currentRevision.Name {status.CurrentReplicas--}if getPodRevision(replicas[i]) == updateRevision.Name {status.UpdatedReplicas--}......

daemonset:pkg/controller/daemon/daemon_controller.go

func (dsc *DaemonSetsController) podsShouldBeOnNode(......
) (nodesNeedingDaemonPods, podsToDelete []string) {......switch {......case shouldContinueRunning:......for _, pod := range daemonPods {if pod.DeletionTimestamp != nil {continue}if pod.Status.Phase == v1.PodFailed {// This is a critical place where DS is often fighting with kubelet that rejects pods.// We need to avoid hot looping and backoff.backoffKey := failedPodsBackoffKey(ds, node.Name)......

解决方案

1、团队里面有了一套 k8s 集群事件采集的链路,我们通过消费 k8s 中 pod 的相关事件来进行处理,消费事件时过滤 pod 中与 Evicted 实例相关的事件然后处理即可。

Evicted 实例判断逻辑:

const (podEvictedStatus = "Evicted"
)// 判断如果为 Evicted 状态的实例且 Pod 中容器数为 0 时直接删除 pod
if strings.ToLower(status) == strings.ToLower(podEvictedStatus) && len(pod.Status.ContainerStatuses) == 0 {}

2、社区有人提供通过在 kube-controller-manager 中配置 podgc controller –terminated-pod-gc-threshold 参数来自动清理:

Podgc controller flags:--terminated-pod-gc-threshold int32Number of terminated pods that can exist before the terminated pod garbage collector starts deleting terminated pods. If<= 0, the terminated pod garbage collector is disabled. (default 12500)

该参数配置的是保留的异常实例数,默认值为 12500,但 podgc controller 回收 pod 时使用强杀模式不支持实例的优雅退出,因此暂不考虑使用。

3、其他处理方式可以参考社区中提供的 Kubelet does not delete evicted pods[2]

总结

由于在之前的公司中对于稳定性的高度重视,线上节点并未开启驱逐实例的功能,因此也不会存在 Evicted 状态的实例,当节点资源严重不足时会有告警人工介入处理,以及还会有二次调度、故障自愈等一些辅助处理措施。本次针对 Evicted 相关实例的分析,发现 k8s 与操作系统之间存在了很多联系,如果要彻底搞清楚某些机制需要对操作系统的一些原理有一定的了解。

参考:

  • https://github.com/kubernetes/kubernetes/issues/55051

  • https://ieevee.com/tech/2019/05/23/ephemeral-storage.html

  • https://github.com/kubernetes/kubernetes/issues/43916

  • https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/

引用链接

[1]

kubelet counts active page cache against memory.available (maybe it shouldn’t?): https://github.com/kubernetes/kubernetes/issues/43916

[2]

Kubelet does not delete evicted pods: https://github.com/kubernetes/kubernetes/issues/55051

原文链接:https://blog.tianfeiyu.com/2021/03/01/k8s_evicted/

你可能还喜欢

点击下方图片即可阅读

保姆级别的 PromQL 教程

云原生是一种信仰 

Kubernetes 中 Evicted pod 是如何产生的相关推荐

  1. Kubernetes 中创建 Pod 时集群中到底发生了些什么?

    想象一下,如果我想将 nginx 部署到 Kubernetes 集群,我可能会在终端中输入类似这样的命令: $ kubectl run --image=nginx --replicas=3 然后回车. ...

  2. Kubernetes 中 设置pod不部署在同一台节点上

    在k8s中,节点的调度主要由亲和性和污点来进行控制的.   而在亲和性部分由分为了节点亲和性和节点反亲和性.   节点亲和性是指在pod部署时,尽量(软策略)或者必须满足(硬策略)部署在某些节点上. ...

  3. Kubernetes 学习总结(25)—— Kubernetes 中的 pod 与容器的区别和联系

    前言 容器本可以成为轻量级虚拟机的替代品.但是由于 Docker/OCI 的标准化,最广泛使用的容器形式是每个容器只有一个进程服务.这种方法有很多优点--增加隔离性.简化水平扩展.更高的可重用性等.但 ...

  4. Kubernetes 中的 Pod 是什么?

    前言 本文隶属于专栏<1000个问题搞定大数据技术体系>,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见1000个问题搞定大数据技 ...

  5. Kubernetes 中的 Pod 安全策略

    很多人分不清 SecurityContext 和 PodSecurityPolicy 这两个关键字的差别,其实很简单: •SecurityContext 是 Pod 中的一个字段,而 PSP 是一个独 ...

  6. kubernetes中给pod加hosts解析

    在一些环境中,有些服务会绑定一个域名,这个时候我们想去访问这个服务的域名就需要做下对应的解析,经常使用的做法是在主机的/etc/hosts文件里面加上域名对应的ip方可访问,如果给在k8s上运行的po ...

  7. 记一次 Kubernetes 集群 Pod Eviction 问题排查过程

    声明: 本博客欢迎转发,但请保留原作者信息! 新浪微博:@Lingxian_kong; 微信公众号:飞翔的尘埃; 内容系本人学习.研究和总结,如有雷同,实属荣幸! 现象:一个普通的 k8s 集群,3 ...

  8. Grafana 在 Kubernetes 中的使用

    安装 grafana 是一个可视化面板,有着非常漂亮的图表和布局展示,功能齐全的度量仪表盘和图形编辑器,支持 Graphite.zabbix.InfluxDB.Prometheus.OpenTSDB. ...

  9. Spring Cloud Kubernetes 中文文档

    本参考指南介绍了如何使用Spring Cloud Kubernetes. 1.为什么需要Spring Cloud Kubernetes? Spring Cloud Kubernetes提供了使用Kub ...

最新文章

  1. MySQL · 性能优化· CloudDBA SQL优化建议之统计信息获取
  2. 重磅:国拨概算5.34亿!“新一代人工智能”重大项目项目申报指南发布
  3. The Elements of Statistical Learning的笔记
  4. android字符显示流程图,Android应用层View绘制流程与源码分析
  5. Scala多线程:使用线程池Executors提交Runnable任务代码示例
  6. 判断JavaScript对象为null或者属性为空
  7. 一直苦于没有好的资产管理软件,GLPI能解决吗?
  8. 常用加密算法(Java实现)总结
  9. 双缓冲(Double Buffer)原理和使用【转】
  10. Oracle数据库awr报告使用与分析
  11. Java版扫雷小游戏
  12. mysql数据库修复工具 innodb表数据恢复 ibd文件恢复工具
  13. 天天向上答案python_天天向上的力量python(举一反三)
  14. docker启动mysql报错Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use
  15. 平板android rom下载地址,Android平板第三方ROM开放下载
  16. MKS Robin nano V3.0主板使用RRF 固件教程
  17. 三个基本的布尔逻辑算符是_布尔逻辑算符.ppt
  18. 关于内部用户通过easy-ip访问外网
  19. manifest文件linux,MANIFEST 文件扩展名: 它是什么以及如何打开它?
  20. Python实现商品价格监控,识破双十一的套路

热门文章

  1. k8s入坑之报错(9)k8s node节点加入到集群时卡住 “[preflight] Running pre-flight checks”...
  2. Jmeter beanshell语法
  3. 给陌生的你听-G.G张思源
  4. 【分享视频资源】React JS教程
  5. 本杰·格拉汉姆选股策略
  6. Qt框架与STL库之间的巅峰对决:差异、优缺点及适用场景
  7. 穿冰丝很凉快,是有降温作用吗?穿戴冰丝衣物要注意哪些?
  8. css3中单位px,em,rem,vh,vw,vmin,vmax的区别及浏览器支持情况
  9. 使用百度地图api制作地图名片
  10. PHP 递归函数的三种实现方式