在使用Rancher RKE部署的Kubernetes集群中,AI/ML负载(如训练任务、推理服务)经常需要精确调度到特定的GPU节点上。我们通常使用nodeSelector或nodeAffinity来实现这一目标。然而,当这些负载被集中调度到少数几个节点上时,运维人员可能会发现大量的Pod进入Evicted状态,而不是预期的CrashLoopBackOff或Pending。本文将深入分析这一现象的根源,并提供具体的诊断和解决步骤。
1. 理解Kubernetes Eviction机制
Pod状态为Evicted意味着Kubernetes Kubelet主动将Pod驱逐(删除)以回收节点资源,而不是因为应用程序自身的错误崩溃。Kubelet执行驱逐的根本原因在于节点资源压力过大,最常见的压力类型包括:
- Disk Pressure (磁盘压力): 节点的磁盘使用率超过阈值。
- Inode Pressure (Inode压力): 节点上的文件数量过多,Inodes耗尽。
- Memory Pressure (内存压力): 节点内存使用率超过硬性驱逐阈值。
- PID Pressure (进程ID压力): 节点上进程数量过多。
对于AI/ML负载,特定的调度策略往往导致局部资源热点,其中最容易被忽视和触发Evicted状态的是临时存储(Ephemeral Storage)。
2. 为什么特定节点调度会引发Eviction?
当您将所有高I/O或日志密集的AI负载调度到少数几个节点时,这些节点上的以下资源消耗会急剧增加:
- 日志和Cache堆积: 容器的标准输出和标准错误(stdout/stderr)会被Kubelet收集并存储在本地磁盘上(通常是/var/log/pods或/var/lib/docker/containers)。如果负载日志量巨大且没有设置ephemeral-storage限制,它将迅速耗尽节点的根文件系统或专门的Kubelet存储目录。
- 镜像层解压和存储: 虽然镜像层通常不计入临时存储限制,但如果节点频繁拉取新镜像或更新,存储空间会快速消耗。
当Kubelet检测到磁盘利用率(通常是根目录或/var/lib/kubelet)达到硬性驱逐阈值(如默认的95%)或软性驱逐阈值时,它会开始驱逐Pod,首先驱逐的是不设置Request/Limit的Pod,或占用资源最大的Pod。
3. 诊断:定位Evicted的真正原因
第一步是检查具体的Pod和Node状态,找出Kubelet报告的压力类型。
步骤 3.1 检查Pod的详细状态
# 替换为你的命名空间和Pod名称
kubectl describe pod <pod_name> -n <namespace>
在输出的Events部分,你会看到明确的驱逐原因,通常是类似如下的描述:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Evicted 5m kubelet The node was low on resource: ephemeral-storage. Container <container_name> was using 80Gi, which exceeds its request of 5Gi.
步骤 3.2 检查目标节点的资源状况
检查被驱逐Pod所在的节点,查看其Conditions。
kubectl describe node <node_name>
查找Conditions字段:
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
MemoryPressure False Thu, 1 Jan 1970 00:00:00 +0000 Thu, 1 Jan 1970 00:00:00 +0000 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure True Thu, 1 Jan 1970 00:00:00 +0000 Thu, 1 Jan 1970 00:00:00 +0000 KubeletHasDiskPressure kubelet has less than 10% of ephemeral-storage available
... (其他条件)
如果DiskPressure: True,则确认问题出在磁盘空间或临时存储。
4. 解决与缓解方案
方案 4.1 实施临时存储(Ephemeral Storage)限制
这是解决问题的最根本和最推荐的方法。通过在Pod Spec中明确设置ephemeral-storage的Requests和Limits,可以防止单个Pod耗尽整个节点的本地存储。
apiVersion: v1
kind: Pod
metadata:
name: ai-workload-limited
spec:
nodeSelector:
gpu: 'true' # 假设这是特定调度标签
containers:
- name: heavy-logging-container
image: my-ai-image:latest
resources:
limits:
# 限制CPU和内存
cpu: "2"
memory: "4Gi"
# 限制临时存储的上限(硬限制)
ephemeral-storage: "20Gi"
requests:
# 预留临时存储(软请求)
cpu: "1"
memory: "2Gi"
ephemeral-storage: "10Gi"
关键点: 如果Pod实际使用的临时存储超过了Limits.ephemeral-storage,Kubelet会立即驱逐该Pod,从而保护节点资源。
方案 4.2 调整日志策略或存储位置
对于日志量巨大的应用,考虑以下策略:
- Log Rotation: 确保Docker/Containerd配置了合理的日志轮换策略(RKE通常会配置默认值,但需确认其有效性)。
- 外部日志系统: 将日志直接发送到Elasticsearch或Loki等外部系统,而不是依赖本地存储。
- 配置单独的存储卷: 如果可以,配置ReadWriteOnce或ReadWriteMany的持久化存储卷(PV/PVC)来存储AI任务的中间结果,避免占用临时存储。
方案 4.3 调整Kubelet驱逐阈值 (RKE Kubelet Extra Args)
对于RKE集群,您可以通过cluster.yml配置Kubelet的额外参数,调整驱逐的软硬阈值。但这通常是最后的手段,需要谨慎操作。
例如,如果希望在磁盘占用达到90%才触发硬驱逐,可以修改Kubelet配置:
# cluster.yml (Rancher RKE 配置)
services:
kubelet:
extra_args:
eviction-hard: 'imagefs.available<10%,nodefs.available<10%,nodefs.inodesFree<5%'
eviction-soft: 'imagefs.available<15%,nodefs.available<15%,nodefs.inodesFree<10%'
eviction-soft-grace-period: 'nodefs.available=2m'
在修改RKE配置后,需要运行 rke up 更新集群配置。
通过以上诊断和配置优化,可以有效地管理特定AI节点上的临时存储压力,从而消除Pod的频繁Evicted状态。
汤不热吧