Kubernetes节点资源耗尽状态的处理

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 今天上午一到工位,就收到来自同事的“投诉”:私有云上的Kubernetes cluster中的一个node似乎不工作了,因为专门部署于那个节点上的应用挂掉了,并且长时间没有恢复。这个公司私有云上Kubernetes集群是v1.7.5版本,部署于双节假期之前。

今天上午一到工位,就收到来自同事的“投诉”:私有云上的Kubernetes cluster中的一个node似乎不工作了,因为专门部署于那个节点上的应用挂掉了,并且长时间没有恢复。这个公司私有云上Kubernetes集群是v1.7.5版本,部署于双节假期之前。最近感觉K8s开发明显提速,连续发布版本,截至发稿时,最新发布的版本为v1.8.1了。这个集群一直运行相对稳定,今天这个异常到底是怎么一回事呢?于是 打开terminal,开始了问题的调查。

一、问题现象

我们这个小集群一共有三个Kubernetes Node。首先,我查看集群中的所有Pods状态,发现node1和node2上的Pods均正常(running状态),但位于node3上的三个Pods均为“Pending”状态,这三个pod是weave-net-rh6r4、kube-proxy-v4d1p以及portal-3613605798-txq4l,其中portal-3613605798-txq4l是我们的应用Pod。K8s自身的组件kube-proxy都异常了,显然node3节点出问题了。如果你此刻去尝试查看(kubectl describe) 这几个pod的状态,多半你会失败,因为Pod在频繁重启,1-2s钟新创建的Pod就会被kill掉,导致你无法查看其状态。

我直接查看一下node3的状态,果不其然,我得到了一些Warning events:

# kubectl describe ubuntu-k8s-3 ... ...
Events:
 FirstSeen LastSeen Count From SubObjectPath Type Reason Message
 --------- -------- ----- ---- ------------- -------- ------ -------
 51m 51m 1 kubelet, ubuntu-k8s-3 Normal NodeNotSchedulable Node ubuntu-k8s-3 status is now: NodeNotSchedulable
 9d 51m 49428 kubelet, ubuntu-k8s-3 Warning EvictionThresholdMet Attempting to reclaim nodefs
 5m 5m 1 kubelet, ubuntu-k8s-3 Normal Starting Starting kubelet.
 5m 5m 2 kubelet, ubuntu-k8s-3 Normal NodeHasSufficientDisk Node ubuntu-k8s-3 status is now: NodeHasSufficientDisk
 5m 5m 2 kubelet, ubuntu-k8s-3 Normal NodeHasSufficientMemory Node ubuntu-k8s-3 status is now: NodeHasSufficientMemory
 5m 5m 2 kubelet, ubuntu-k8s-3 Normal NodeHasNoDiskPressure Node ubuntu-k8s-3 status is now: NodeHasNoDiskPressure
 5m 5m 1 kubelet, ubuntu-k8s-3 Normal NodeAllocatableEnforced Updated Node Allocatable limit across pods
 5m 5m 1 kubelet, ubuntu-k8s-3 Normal NodeHasDiskPressure Node ubuntu-k8s-3 status is now: NodeHasDiskPressure
 5m 14s 23 kubelet, ubuntu-k8s-3 Warning EvictionThresholdMet Attempting to reclaim nodefs

两点有价值的内容:
1、Node ubuntu-k8s-3 status is now: NodeHasDiskPressure
2、Warning: “EvictionThresholdMet Attempting to reclaim nodefs”

从以上内容大致可以判断出node3处于磁盘空间不足的状态下,并且该node上的kubelet daemon判断达到了Eviction阀值,试图回收磁盘空间(通过某种杀Pod的方式,I Guess)。

既然提到了Kubelet,我们再来看看这一后台service的log:

# journalctl -u kubelet -f
10月 16 09:50:55 ubuntu-k8s-3 kubelet[17144]: W1016 09:50:55.056703 17144 eviction_manager.go:331] eviction manager: attempting to reclaim nodefs
10月 16 09:50:55 ubuntu-k8s-3 kubelet[17144]: I1016 09:50:55.057322 17144 eviction_manager.go:345] eviction manager: must evict pod(s) to reclaim nodefs
10月 16 09:50:55 ubuntu-k8s-3 kubelet[17144]: E1016 09:50:55.058307 17144 eviction_manager.go:356] eviction manager: eviction thresholds have been met, but no pods are active to evict
... ...
10月 16 09:54:14 ubuntu-k8s-3 kubelet[12844]: W1016 09:54:14.823152 12844 eviction_manager.go:142] Failed to admit pod weave-net-3svfg_kube-system(e5a5d474-b214-11e7-a98b-0650cc001a5b) - node has conditions: [DiskPressure]
10月 16 09:54:14 ubuntu-k8s-3 kubelet[12844]: W1016 09:54:14.824246 12844 eviction_manager.go:142] Failed to admit pod kube-proxy-d9lk0_kube-system(e5ff8fde-b214-11e7-a98b-0650cc001a5b) - node has conditions: [DiskPressure] 

kubelet日志也印证了上面的判断:node3因为磁盘不足不再参与pod调度,但尝试回收磁盘空间时却发现已经没有active pod可以kill了!

二、原因分析

既然提到了磁盘不足,我们就来看看磁盘占用情况:

# df -h

文件系统 容量 已用 可用 已用% 挂载点
udev 2.0G 0 2.0G 0% /dev
tmpfs 396M 46M 350M 12% /run
/dev/sda1 5.8G 5.1G 448M 92% /
tmpfs 2.0G 288K 2.0G 1% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
/dev/sdb1 99G 5.2G 89G 6% /data
tmpfs 396M 0 396M 0% /run/user/0
... ...

我们看到root分区的磁盘占用率已经达到了92%,仅剩下不到500M空间可以使用了。我们的私有云提供的ubuntu vm模板太过死板(无法定制),每个vm挂载的root分区只能是6G,多一点都不可以。这样在安装完一些必要的软件后,根分区占用率就很高了。为此,之前我们还特意挂载了一块专用盘(/dev/sdb1)用于存储docker的相关image和容器运行数据,并将原先的docker数据迁移到新位置(/data/docker)。

附:docker运行时数据迁移方法(适用于docker 1.12.x以后版本):
a) 创建/etc/docker/daemon.json

文件内容如下:
{
“graph”: “/data/docker”,
“storage-driver”: “aufs”
}

b) 停止docker并迁移数据
systemctl stop docker
mv /var/lib/docker /data

c) 重启docker
systemctl daemon-reload
systemctl restart docker

由于某些原因,我们的那个portal pod必须运行于该node上(通过nodeSelector选定node的方式)。在无法扩充根分区size的情况下,为了临时恢复pod运行,我们只能进一步“压榨”node了。于是我们的思路是:通过调整node的eviction threshold值来让node恢复healthy。

三、解决方案

要解决这一问题,我们需要阅读一下k8s官方的关于”Eviction Policy”的说明。大致意思就是:每个node上的kubelet都负责定期采集资源占用数据,并与预设的 threshold值进行比对,如果超过 threshold值,kubelet就会尝试杀掉一些Pod以回收相关资源,对Node进行保护。kubelet关注的资源指标threshold大约有如下几种:

- memory.available
- nodefs.available
- nodefs.inodesFree
- imagefs.available
- imagefs.inodesFree

每种threshold又分为eviction-soft和eviction-hard两组值。soft和hard的区别在于前者在到达threshold值时会给pod一段时间优雅退出,而后者则崇尚“暴力”,直接杀掉pod,没有任何优雅退出的机会。这里还要提一下nodefs和imagefs的区别:

  • nodefs: 指node自身的存储,存储daemon的运行日志等,一般指root分区/;
  • imagefs: 指docker daemon用于存储image和容器可写层(writable layer)的磁盘;

在我们的例子中,我们的imagefs是/dev/sdb1,磁盘占用率很低;而nodefs,即/分区占用率很高(92%)。

我们重启一次kubelet,查看一下这些threshold的当前值(通过journalctl -u kubelet -f查看):

10月 16 09:54:09 ubuntu-k8s-3 systemd[1]: Started kubelet: The Kubernetes Node Agent.
10月 16 09:54:09 ubuntu-k8s-3 kubelet[12844]: I1016 09:54:09.381711 12844 feature_gate.go:144] feature gates: map[]
10月 16 09:54:09 ubuntu-k8s-3 kubelet[12844]: I1016 09:54:09.437470 12844 client.go:72] Connecting to docker on unix:///var/run/docker.sock
10月 16 09:54:09 ubuntu-k8s-3 kubelet[12844]: I1016 09:54:09.438075 12844 client.go:92] Start docker client with request timeout=2m0s
1016 09:54:09 ubuntu-k8s-3 kubelet[12844]: I1016 09:54:09.471485 12844 manager.go:143] cAdvisor running in container: "/system.slice/kubelet.service"
... ...
1016 09:54:09 ubuntu-k8s-3 kubelet[12844]: I1016 09:54:09.615818 12844 container_manager_linux.go:246] container manager verified user specified cgroup-root exists: /
1016 09:54:09 ubuntu-k8s-3 kubelet[12844]: I1016 09:54:09.616263 12844 container_manager_linux.go:251] Creating Container Manager object based on Node Config: {RuntimeCgroupsName: SystemCgroupsName: KubeletCgroupsName: ContainerRuntime:docker CgroupsPerQOS:true CgroupRoot:/ CgroupDriver:cgroupfs ProtectKernelDefaults:false NodeAllocatableConfig:{KubeReservedCgroupName: SystemReservedCgroupName: EnforceNodeAllocatable:map[pods:{}] KubeReserved:map[] SystemReserved:map[] HardEvictionThresholds:[{Signal:memory.available Operator:LessThan Value:{Quantity:100Mi Percentage:0} GracePeriod:0s MinReclaim:} {Signal:nodefs.available Operator:LessThan Value:{Quantity: Percentage:0.1} GracePeriod:0s MinReclaim:} {Signal:nodefs.inodesFree Operator:LessThan Value:{Quantity: Percentage:0.05} GracePeriod:0s MinReclaim:}]} ExperimentalQOSReserved:map[]}
1016 09:54:09 ubuntu-k8s-3 kubelet[12844]: I1016 09:54:09.617680 12844 kubelet.go:263] Adding manifest file: /etc/kubernetes/manifests
1016 09:54:09 ubuntu-k8s-3 kubelet[12844]: I1016 09:54:09.618196 12844 kubelet.go:273] Watching apiserver
... ...

把涉及到threshold的信息重新格式化一下:

 HardEvictionThresholds: [
 {
 Signal: memory.availableOperator: LessThanValue: {
 Quantity: 100MiPercentage: 0
 }GracePeriod: 0sMinReclaim: <nil>
 }{
 Signal: nodefs.availableOperator: LessThanValue: {
 Quantity: <nil>Percentage: 0.1
 }GracePeriod: 0sMinReclaim: <nil>
 }{
 Signal: nodefs.inodesFreeOperator: LessThanValue: {
 Quantity: <nil>Percentage: 0.05
 }GracePeriod: 0sMinReclaim: <nil>
 }
 ]

我们看到初始情况下,kubelet并没有设置Soft Eviction,只是对memory和nodefs设置了hard eviction threshold值。这里最值得我们关注的是:nodefs.available percentage: 0.1。也就是说当nodefs的可用空间低于10%时,该node上的kubelet将会执行eviction动作。而我们的根分区剩余可用空间为8%,显然满足了这个条件,于是问题就发生了。

我们要做的就是临时修改这个值,可以将其设为<5%。

四、解决步骤

我们需要为kubelet重新设定nodefs.available的threshold值。怎么做呢?

kubelet是运行于每个kubernetes node上的daemon,它在system boot时由systemd拉起:

root@ubuntu-k8s-3:~# ps -ef|grep kubelet
root 5718 5695 0 16:38 pts/3 00:00:00 grep --color=auto kubelet
root 13640 1 4 10:25 ? 00:17:25 /usr/bin/kubelet --kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --authorization-mode=Webhook --client-ca-file=/etc/kubernetes/pki/ca.crt --cadvisor-port=0 

查看一下kubelet service的状态:

root@ubuntu-k8s-3:~# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
 Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
 Drop-In: /etc/systemd/system/kubelet.service.d
 └─10-kubeadm.conf
 Active: active (running) since 一 2017-10-16 10:25:09 CST; 6h ago
 Docs: http://kubernetes.io/docs/
 Main PID: 13640 (kubelet)
 Tasks: 18
 Memory: 62.0M
 CPU: 18min 15.235s
 CGroup: /system.slice/kubelet.service
 ├─13640 /usr/bin/kubelet --kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --
 └─13705 journalctl -k -f

.... ...

通过status的输出,我们看到关于kubelet service有两个systemd service配置文件与之启动相关:

- /lib/systemd/system/kubelet.service
Drop-In: /etc/systemd/system/kubelet.service.d
 └─10-kubeadm.conf

/lib/systemd/system/kubelet.service比较简单:

[Unit] Description=kubelet: The Kubernetes Node Agent
Documentation=http://kubernetes.io/docs/

[Service] ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0 RestartSec=10 
[Install] WantedBy=multi-user.target

/etc/systemd/system/kubelet.service.d/10-kubeadm.conf是systemd中用于override kubelet.service中部分配置的drop-in文件,kubelet的启动配置都在这里:

[Service] Environment="KUBELET_KUBECONFIG_ARGS=--kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true" Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true" Environment="KUBELET_NETWORK_ARGS=--network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin" Environment="KUBELET_DNS_ARGS=--cluster-dns=10.96.0.10 --cluster-domain=cluster.local" Environment="KUBELET_AUTHZ_ARGS=--authorization-mode=Webhook --client-ca-file=/etc/kubernetes/pki/ca.crt" Environment="KUBELET_CADVISOR_ARGS=--cadvisor-port=0" ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_NETWORK_ARGS $KUBELET_DNS_ARGS $KUBELET_AUTHZ_ARGS $KUBELET_CADVISOR_ARGS $KUBELET_EXTRA_ARGS 

systemd启动kubelet时会用10-kubeadm.conf中的ExecStart覆盖/lib/systemd/system/kubelet.service中的ExecStart,这样我们才能看到上面kubelet后面那一长溜命令行启动参数。我们要做的就是在这行启动参数后面添加上我们想设置的nodefs.available的threshold值。

出于配置风格一致的考量,我们定义一个新的Environment var,比如就叫:KUBELET_EVICTION_POLICY_ARGS:

Environment="KUBELET_EVICTION_POLICY_ARGS=--eviction-hard=nodefs.available<5%" ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_NETWORK_ARGS $KUBELET_DNS_ARGS $KUBELET_AUTHZ_ARGS $KUBELET_CADVISOR_ARGS $KUBELET_EXTRA_ARGS $KUBELET_EVICTION_POLICY_ARGS 

重启kubelet,我们通过日志看threshold的新值是否生效:

10月 16 16:56:10 ubuntu-k8s-3 kubelet[7394]: I1016 16:56:10.840914 7394 container_manager_linux.go:251] Creating Container Manager object based on Node Config: {
  RuntimeCgroupsName: SystemCgroupsName: KubeletCgroupsName: ContainerRuntime:docker CgroupsPerQOS:true CgroupRoot:/ CgroupDriver:cgroupfs ProtectKernelDefaults:false NodeAllocatableConfig:{KubeReservedCgroupName: SystemReservedCgroupName: EnforceNodeAllocatable:map[pods:{}] KubeReserved:map[] SystemReserved:map[] HardEvictionThresholds:[{Signal:nodefs.available Operator:LessThan Value:{Quantity: Percentage:0.05} GracePeriod:0s MinReclaim:}]} ExperimentalQOSReserved:map[]}

我们看到下面这一行,表明新配置已经生效:

Signal:nodefs.available Operator:LessThan Value:{
  Quantity: Percentage:0.05}

查看pods状态,原先处于pending状态的三个pod均变成了”running”状态,问题得以解决。

本文转自开源中国-Kubernetes节点资源耗尽状态的处理

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
Kubernetes 容器 Perl
Kubernetes 节点的预留资源
Kubernetes 节点的预留资源
241 1
|
Kubernetes Perl 容器
【kubernetes】修改集群节点中 Pod 数量限制
Kubernetes 默认每个节点只能启动 110 个 Pod,由于业务需要,将每个节点默认限制的 Pod 数量改为 200。
3197 0
|
5月前
|
存储 Kubernetes Docker
Kubernetes节点资源耗尽状态的处理
Kubernetes节点资源耗尽状态的处理
|
5月前
|
Kubernetes Linux 调度
在k8S中,Pod如何实现对节点的资源控制?
在k8S中,Pod如何实现对节点的资源控制?
|
5月前
|
资源调度 Kubernetes 调度
玩转Kubernetes集群:掌握节点和Pod自动扩缩容,让你的系统更智能、更高效!
【8月更文挑战第22天】Kubernetes的核心功能之一是自动扩缩容,确保系统稳定与高可用。节点自动扩缩容由调度器和控制器管理器协作完成,依据资源紧张程度动态调整。主要采用HPA、VPA及Cluster Autoscaler实现。Pod自动扩缩容通常通过HPA控制器按需调整副本数量。例如,设置HPA控制器监视特定部署的CPU使用率,在80%阈值上下自动增减副本数。合理利用这些工具可显著提升系统性能。
129 2
|
5月前
|
数据采集 监控 Kubernetes
在k8S中,kubelet监控Worker节点资源是使用什么组件来实现的?
在k8S中,kubelet监控Worker节点资源是使用什么组件来实现的?
|
5月前
|
Kubernetes 容器 Perl
Kubernetes(K8S) Node NotReady 节点资源不足 Pod无法运行
Kubernetes(K8S) Node NotReady 节点资源不足 Pod无法运行
88 0
|
7月前
|
Kubernetes API 调度
Pod无法调度到可用的节点上(K8s)
完成k8s单节点部署后,创建了一个pod进行测试,后续该pod出现以下报错: Warning FailedScheduling 3h7m (x3 over 3h18m) default-scheduler 0/1 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..
468 0
|
8月前
|
Kubernetes 应用服务中间件 调度
pod 之资源限制 与健康检查
pod 之资源限制 与健康检查
|
8月前
|
监控 Kubernetes Java
查看pod资源使用情况
使用`kubectl top pod -n $namespace|grep online`检查Kubernetes pod资源使用,例如CPU和内存。同样,通过`kubectl top nodes`查看节点资源。关注NMC的堆内存使用,防止溢出。监控实例请求和线程状态以确保正常运行。当遇到问题时,收集日志如jstack、jmap输出、操作系统日志和NMC线程监控数据。对于效率问题,依赖NMC线程监控和客户端SPR记录。错误日志可通过控制台获取。