适用场景

本文适用于 Kubernetes 集群中出现以下问题的场景:

  • 新发布的 Pod 长时间处于 Pending 状态;
  • kubectl describe pod 中看到 node(s) had disk pressure
  • 节点状态出现 DiskPressure=True
  • 容器镜像、日志或临时目录占满节点磁盘,导致调度失败;
  • 节点上已经运行的 Pod 被驱逐,事件中出现 Evicted

这类问题常见于镜像构建频繁、日志量突然变大、没有配置容器日志轮转、节点磁盘容量偏小、CI/CD 高峰期集中拉取大镜像的集群。

现象描述

业务发布后,Deployment 的副本数迟迟没有恢复:

kubectl get deploy -n prod
kubectl get pod -n prod -o wide

可能看到类似结果:

NAME                         READY   STATUS    RESTARTS   AGE   NODE
api-7d8d87d6f4-2qk9s         0/1     Pending   0          8m    <none>
api-7d8d87d6f4-hp8mv         1/1     Running   0          2d    node-10-0-1-21

查看 Pod 事件:

kubectl describe pod api-7d8d87d6f4-2qk9s -n prod

常见事件如下:

Warning  FailedScheduling  default-scheduler  0/5 nodes are available:
2 node(s) had disk pressure,
3 node(s) didn't match Pod's node affinity/selector.

此时问题不在应用代码本身,而是调度器认为可用节点不满足磁盘压力条件,新的 Pod 无法落到节点上。

可能原因

DiskPressure 通常由 kubelet 根据节点文件系统可用空间或 inode 情况判断。常见原因包括:

  • 容器运行时目录占用过高,例如 /var/lib/containerd/var/lib/docker
  • 镜像缓存长期未清理,大镜像和历史版本累积;
  • 容器标准输出日志持续增长,例如 /var/log/containers/var/log/pods
  • emptyDir、临时文件、导出文件写入节点本地磁盘;
  • 节点系统盘容量过小,业务盘和容器运行时目录没有分离;
  • inode 耗尽,磁盘容量看似没满但无法继续创建文件;
  • 日志采集异常,采集进程停滞后日志堆积。

排查思路

1. 确认 Pod Pending 是否由磁盘压力导致

先看 Pod 事件,不要只看 kubectl get pod

kubectl describe pod <pod-name> -n <namespace>

重点看 Events 中的 FailedScheduling。如果出现 had disk pressure,说明调度阶段已经被节点条件拦截。

也可以直接查看所有 Pending Pod:

kubectl get pod -A --field-selector=status.phase=Pending -o wide

如果多个命名空间同时出现 Pending,优先怀疑节点资源或调度约束,而不是单个业务配置。

2. 查看节点 DiskPressure 状态

kubectl get node
kubectl describe node <node-name>

Conditions 中重点看:

Type            Status   Reason
DiskPressure    True     KubeletHasDiskPressure

也可以快速筛选异常节点:

kubectl describe node | grep -E "Name:|DiskPressure|OutOfDisk"

字段解释:

  • DiskPressure=True:kubelet 判断节点磁盘或 inode 已低于驱逐阈值;
  • MemoryPressure=True:内存压力,不是本文重点;
  • PIDPressure=True:进程数压力;
  • Ready=FalseReady=Unknown:节点健康状态异常,需要结合 kubelet 和网络继续排查。

3. 登录节点查看磁盘与 inode

在异常节点执行:

df -h
df -ih

重点看这些挂载点:

/var/lib/containerd
/var/lib/docker
/var/log
/var
/

df -h 用于看容量,df -ih 用于看 inode。若 inode 使用率 100%,即使磁盘还有空间,也会导致文件创建失败。

继续定位大目录:

sudo du -xhd1 /var | sort -h
sudo du -xhd1 /var/lib | sort -h
sudo du -xhd1 /var/log | sort -h

参数说明:

  • -x:不跨文件系统,避免把挂载盘一起统计进去;
  • -h:人类可读单位;
  • -d1:只看当前目录下一层,适合逐层定位;
  • sort -h:按容量大小排序。

4. 区分镜像、容器日志和业务临时文件

如果使用 containerd:

sudo crictl images
sudo crictl ps -a
sudo du -xhd1 /var/lib/containerd | sort -h

如果使用 Docker:

docker system df
docker ps -a --size
sudo du -xhd1 /var/lib/docker | sort -h

查看容器日志目录:

sudo du -xhd1 /var/log/pods | sort -h | tail
sudo du -xhd1 /var/log/containers | sort -h | tail

定位单个超大日志文件:

sudo find /var/log/pods /var/log/containers -type f -size +500M -printf '%s %p\n' | sort -n

如果是 emptyDir 或应用临时文件,通常会落在 kubelet 管理目录中:

sudo du -xhd1 /var/lib/kubelet | sort -h
sudo find /var/lib/kubelet -xdev -type f -size +500M -printf '%s %p\n' | sort -n | tail

定位示例

一次发布后,业务 Pod 全部 Pending,事件如下:

0/6 nodes are available: 4 node(s) had disk pressure, 2 node(s) didn't match Pod's node affinity/selector.

查看节点:

kubectl describe node node-10-0-1-21 | grep -A5 Conditions

发现:

DiskPressure   True   KubeletHasDiskPressure

登录节点:

df -h

结果显示 /var 使用率 96%。继续定位:

sudo du -xhd1 /var | sort -h

发现 /var/log 占用 80G。继续查看:

sudo du -xhd1 /var/log/pods | sort -h | tail

定位到某个业务 Pod 的 stdout 日志持续刷屏,单个日志文件超过 30G。根因是应用在异常重试时每秒打印大量完整请求体,日志采集又因为配置错误没有及时上传和轮转。

修复方案

1. 先恢复调度能力

如果确认是无用镜像或已退出容器占用空间,可以清理容器运行时缓存。

containerd 环境优先使用 kubelet 兼容的清理方式:

sudo crictl ps -a
sudo crictl images
sudo crictl rmi --prune

Docker 环境可以执行:

docker system df
docker container prune
docker image prune -a

执行前要确认节点不是依赖本地镜像运行离线业务。生产环境不建议无脑清理所有镜像,避免后续扩容时重新拉取镜像变慢。

如果是超大容器日志,优先修复应用刷日志问题,再按日志文件路径处理。不要直接删除正在写入的文件后就结束排查,因为进程可能仍持有文件句柄。可以先截断:

sudo truncate -s 0 /var/log/pods/<namespace>_<pod>_<uid>/<container>/0.log

截断后重新查看:

df -h
kubectl describe node <node-name> | grep -A5 DiskPressure

kubelet 需要一段时间重新上报节点状态。状态恢复后,Pending Pod 通常会自动被调度。

2. 处理被驱逐的 Pod

如果出现大量 Evicted Pod:

kubectl get pod -A | grep Evicted

可以按命名空间清理:

kubectl get pod -n <namespace> | awk '/Evicted/{print $1}' | xargs -r kubectl delete pod -n <namespace>

这一步只清理 Kubernetes API 中的已驱逐对象,不解决磁盘压力根因。根因仍要回到节点磁盘占用、日志策略和业务写盘行为。

3. 给业务配置 ephemeral-storage 请求和限制

对于会写临时文件的业务,应配置 ephemeral-storage

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      containers:
        - name: api
          image: registry.example.com/api:2026-07-29
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
              ephemeral-storage: "1Gi"
            limits:
              cpu: "1"
              memory: "1Gi"
              ephemeral-storage: "4Gi"

关键点:

  • requests.ephemeral-storage 会参与调度,避免把大量写盘 Pod 调度到空间不足的节点;
  • limits.ephemeral-storage 可以限制单个容器无限制写本地盘;
  • 写入 emptyDir、容器日志、容器可写层都可能计入本地临时存储。

4. 配置容器日志轮转

如果节点使用 Docker,可以在 /etc/docker/daemon.json 中限制 json-file 日志:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  }
}

如果使用 containerd,应检查 kubelet 启动参数或配置文件中的日志限制,例如:

containerLogMaxSize: 100Mi
containerLogMaxFiles: 5

修改后需要按集群维护规范重启相关服务,并分批验证节点是否正常:

systemctl status kubelet
systemctl status containerd

预防措施

  • 为节点 /var/lib/containerd/var/lib/docker 单独挂载数据盘,避免挤占系统盘;
  • 为高日志量服务设置日志级别、采样和单条日志大小限制;
  • 配置容器日志轮转,避免 stdout 日志无限增长;
  • 为会写临时文件的服务配置 ephemeral-storage request 和 limit;
  • 监控节点磁盘容量、inode、containerd/docker 目录、DiskPressure 节点条件;
  • 发布系统中关注 Pending Pod 数量和调度失败事件;
  • 定期清理不用的镜像和已退出容器,但要避开业务高峰;
  • 对批处理、导出、压缩任务使用持久卷或对象存储,不要默认写节点系统盘。

Prometheus 中可以关注这些指标:

kube_node_status_condition{condition="DiskPressure",status="true"}

node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.15

node_filesystem_files_free{mountpoint="/"} / node_filesystem_files{mountpoint="/"} < 0.15

告警建议:

  • DiskPressure=True 持续 5 分钟立即告警;
  • 根分区或容器运行时分区可用空间低于 15% 告警;
  • inode 可用比例低于 15% 告警;
  • Pending Pod 数量突然升高时联动查看调度事件。

总结

Kubernetes 中 Pod Pending 不一定是资源请求过大,也可能是节点进入 DiskPressure 后被调度器排除。排查时应先看 Pod 事件,再看节点 Conditions,随后登录节点用 dfducrictl、日志目录逐层定位。临时清理只能恢复现场,长期治理要落到日志轮转、镜像清理、ephemeral-storage 限制、节点磁盘规划和监控告警上。这样才能避免一次日志刷屏或镜像堆积演变成集群级发布失败。