现象:
观察到磁盘空间不足的节点有大量pod处于Evicted 0/1状态,但并未进行重新调度。
查看到我的集群中有如下事件:
failed to garbage collect required amount of images
原因描述:
当容器集群中的节点(宿主机)磁盘使用率达到85%之后,会触发自动的容器镜像回收策略,以便于释放足够的宿主机磁盘。该事件发生于当触发镜像回收策略之后,磁盘空间仍然不足以达到健康阈值(默认为80%)。通常该错误是由于宿主机磁盘被占用太多导致。当磁盘空间占用率持续增长(超过90%),会导致该节点上的所有容器被驱逐,也就是当前节点由于磁盘压力不再对外提供服务,直到磁盘空间释放。
磁盘空间不足是由于一些历史原因导致的。在我的K8S集群中采用EFK的日志架构去收集日志,但是由于历史原因(从虚拟机转型到K8S),日志文件通过hostpath挂载到主机目录,并且通过在K8S node上通过systemd启动flume logtail组件去抓取挂载到宿主机的日志。这就导致两个问题:
- pod不是固定于某个节点上的,而是会随机调度,这也就造成有可能多个pod调度到一个主机上,并打印日志到同一个文件中去。
- hostpath的主机目录中的日志文件会不断堆积,引发本文中遇到的问题。
pod驱逐但未被删除的原因可以参考以下链接:
Kubelet does not delete evicted pods · Issue #55051 · kubernetes/kubernetes · GitHub
这样设计作可能是为了方便定位问题原因,而且如果大面积的pod被驱逐导致重启,这将对apiserver和etcd造成不小的冲击压力。
值得一提的是,当statefulset进入failed状态会自动重启,而deployment和damonset就不会。这里不作深入讨论。
解决方法:
我们从多个纬度来看待和处理这个问题:
- 从日志系统角度看,更优雅的做法应该是通过logtail sidecar和业务容器共同挂载emptydir的方式去抓取日志,emptydir属于临时存储(ephemeral-storage),占用的是kubelet根目录下的空间或者内存空间,可以考虑在podtemlate下的ephemeral-storage limit为其加入最大存储用量来防止超出预期的用量。此外当pod重启时,这部分空间会自动回收,不会保留日志文件,也就不会有大文件堆积的担忧。
- kubelet驱逐条件默认磁盘可用空间在10%以下,我们会更倾向调整云监控磁盘告警阈值以提前告警,如果是部署在集群内的prometheus,必须在prometheus被驱逐前通过各种告警手段通知到运维。
- kube-controller-manager可以设置--terminated-pod-gc-threshold为1,这个参数的意义表示controller manager可以容忍的最大的被驱逐的pod数量,如果超出该值,则会触发pod gc,将这些pod删除,以达到pod被驱逐后自动重启的目的。
- kube-scheduler配置了--policy-config-file下的CheckNodeDiskPressure策略项,当pod在调度时将磁盘使用率作为调度的前提条件之一,如果某个Node的磁盘使用率已经超过了阈值,则该Node将被标记为不可调度的。这意味着,Scheduler将不会将新的Pod绑定到该Node上,直到磁盘使用率恢复到安全水平为止。这个配置默认是true。
- 引入自动化脚本来定时删除被驱逐的pod也是个不错的选择。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: delete-failed-pods
spec:
schedule: "*/30 * * * *"
failedJobsHistoryLimit: 1
successfulJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: kubectl-runner
image: wernight/kubectl
command: ["sh", "-c", "kubectl get pods --all-namespaces --field-selector 'status.phase==Failed' -o json | kubectl delete -f -"]
restartPolicy: OnFailure