限制 Kubernetes 本地临时存储的容量
本文结合生产环境(K8s 1.28 + Docker)实际配置,深入讲解 ephemeral storage 的原理与最佳实践。
1. 问题背景
作为 Kubernetes 平台的提供方,必须对”流氓”应用做出限制,防止它们滥用 CPU、内存、磁盘等资源。
- Kubernetes 提供了对 CPU、内存 的限制(requests/limits),可以防止应用无限制使用系统资源
- Kubernetes 提供的 PVC(如 cephfs、RBD)也支持容量限制
- 但早期版本没有限制 container 的 rootfs 容量
- 容器 log 默认存储在
/var/lib/kubelet/,rootfs 在/var/lib/docker,两者默认在宿主机 node 的根分区 - 恶意应用可在容器内大量写入(如
dd),迅速造成宿主机 node 根分区文件系统满 - Linux 根分区使用率达 100% 时非常危险,可能导致整个节点不可用
2. 概念与覆盖范围
什么是本地临时存储(Local Ephemeral Storage)
Kubernetes 1.8 引入新 resource:local ephemeral storage(本地临时存储),对应特性 LocalStorageCapacityIsolation。1.10 开始转入 beta 状态并默认开启。到 K8s 1.28,该特性已稳定成熟。
临时存储覆盖范围
ephemeral-storage 限制覆盖以下内容:
| 类型 | 说明 | 存储位置 |
|---|---|---|
| emptyDir volumes | Pod 内共享的临时卷 | 节点本地磁盘 |
| container logs | 容器标准输出/错误日志(kubectl logs 看到的) | /var/log/containers/ 及 /var/log/pods/ |
| image layers | 容器镜像的只读层 | 节点本地存储 |
| container writable layers | 容器可写层(rootfs,容器内写入的文件) | 节点本地磁盘 |
⚠️ 重要注意: 本地临时存储管理只对 root 分区 有效。如果定制了相关参数(如 kubelet
--root-dir指向非根分区),则不会生效。
3. 配置方法
3.1 基本配置
每个 container 可配置以下字段:
spec.containers[].resources.limits.ephemeral-storage
spec.containers[].resources.requests.ephemeral-storage
单位说明: 默认为 byte,也可使用:
- 十进制:
E,P,T,G,M,K(如129M= 129,000,000 bytes) - 二进制:
Ei,Pi,Ti,Gi,Mi,Ki(如123Mi= 123 × 2²⁰ bytes)
实际使用中建议用二进制单位(
Gi,Mi),与磁盘容量计算一致。
3.2 配置示例
设置临时存储最大为 2Gi:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
selector:
matchLabels:
run: nginx
template:
spec:
containers:
- image: nginx
name: nginx
resources:
limits:
ephemeral-storage: 2Gi
requests:
ephemeral-storage: 2Gi
3.3 验证方法
进入容器执行 dd if=/dev/zero of=/test bs=4096 count=1024000,尝试创建超过限制的文件:
# Pod 被 Evict,controller 重新创建新 Pod
nginx-75bf8666b8-89xqm 1/1 Running 0 1h
nginx-75bf8666b8-pm687 0/1 Evicted 0 2h
4. 驱逐机制详解
4.1 Kubelet Evict Manager
Evict Pod 的动作由 kubelet 完成。每个节点上的 kubelet 启动一个 evict manager,每 10秒(evictionMonitoringPeriod)检查一次,ephemeral storage 的检查也在此时完成。
4.2 检查顺序
kubelet 按以下顺序依次检查:
- Pod 的 emptyDir →
emptyDirLimitEviction - Pod 级临时存储 →
podEphemeralStorageLimitEviction - Container 级临时存储 →
containerEphemeralStorageLimitEviction
4.3 Container 级检查
比较简单:依次检查每个 container 的临时存储使用量和设置的 limits,超过则将 Pod 加入 evicted 列表。
4.4 Pod 级检查
限制值计算:
max(sum(所有container的ephemeral-storage limits), initContainer1, initContainer2, ...)
- 统计 Pod 所有 container(不包括 init container)的 ephemeral storage limits 之和
- init container 指定的是 Pod 配额的最低需求
- 当所有 container 的配额之和超过 init container 指定的配额时,忽略 init container 的值
实际用量计算:
- 指定了 ephemeral-storage 的 container 的使用量
- 未指定 ephemeral-storage 的 container 的使用量(⚠️ 注意:仍计入 Pod 总用量)
- emptyDir 的使用量
关键点: 当实际用量超过限制值时,kubelet 将 Pod Evict,等待 controller 重新创建并调度。
5. Requests 的作用
设置的 local ephemeral storage requests 在 evict manager 处理过程中没有用到,但它不是没用的。
创建 Pod 后,scheduler 将 Pod 调度到集群中某个 node 上。scheduler 保证该 node 上所有 Pod 的 local ephemeral storage requests 总和不会超过 node 的根分区容量。
简单理解:
– limits → kubelet 用,决定何时 Evict Pod
– requests → scheduler 用,决定 Pod 调度到哪个 node
6. Inode 保护
问题现象
磁盘写入报磁盘满,但 df -h 查看容量并未 100%,可能是因为 inode 耗尽。
现状
podLocalEphemeralStorageUsage统计了 container 或 pods 使用的 inode 数量- 当前 Kubernetes 不支持对 Pod 的临时存储设置 inode 的 limits/requests
- 如果 node 进入 inode 紧缺状态,kubelet 将 node 设置为
DiskPressure,不再接收新的 Pod 请求
生产环境中,大量小文件(如日志碎片、临时缓存文件)可能耗尽 inode,需关注。
7. emptyDir sizeLimit
emptyDir 也是一种临时存储,需要限制使用。
Pod 级别检查时
emptyDir 的使用量计入 Pod 总用量,过量使用会导致 Pod 被 kubelet Evict。
emptyDir 独立限制配置
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- emptyDir:
medium: Memory # 使用内存作为存储介质,获得极好的读写性能
sizeLimit: 64Mi # 容量上限,超过后 Pod 被 kubelet evict
name: cache-volume
medium: Memory表示使用 tmpfs(内存文件系统),数据不写入磁盘但占用容器内存,需同时考虑 memory limits。
8. 生产配置有效性分析
以下基于实际 Helm values 配置进行分析:
8.1 当前配置摘要
# 主容器资源配置
resources:
requests:
cpu: 500m
memory: 8192Mi
ephemeral-storage: 10Gi
limits:
cpu: 2000m
memory: 8192Mi
ephemeral-storage: 10Gi
# skywalking agent sidecar 资源配置
skywalking:
agent:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
# ⚠️ 未设置 ephemeral-storage
# 日志目录挂载到 PVC
mount:
volumeMounts:
- mountPath: /AppHome/logs
subPath: '{{ .Release.Name }}'
name: log-data
- mountPath: /AppLogs
subPath: '{{ .Release.Name }}'
name: log-data
volumes:
- name: log-data
persistentVolumeClaim:
claimName: jar-log-data-pvc
8.2 有效性判断
| 配置项 | 有效性 | 说明 |
|---|---|---|
主容器 ephemeral-storage: 10Gi |
✅ 有效 | 能限制主容器的可写层(rootfs)和容器日志写入量 |
| 日志挂 PVC | ✅ 好实践 | /AppHome/logs、/AppLogs 写入 PVC,不消耗临时存储 |
JVM -Dcsp.sentinel.log.dir=/tmp |
⚠️ 风险 | Sentinel 日志写入容器 /tmp,属于临时存储,长期运行会占用空间 |
| skywalking sidecar 未设 ephemeral-storage | ⚠️ 风险 | sidecar 的临时存储用量计入 Pod 总用量,但不计入 Pod 限制值 |
| 容器日志(stdout/stderr) | ⚠️ 风险 | 存储在节点 /var/log/containers/,属于临时存储,需关注日志量 |
8.3 ⚠️ 核心风险详解:sidecar 未设 ephemeral-storage
这是一个重要但容易被忽视的问题:
Pod 级临时存储限制值计算:
Pod限制 = sum(所有设置了ephemeral-storage limits的container)
Pod 级临时存储实际用量计算:
Pod用量 = 所有container的临时存储用量(无论是否设置了limits) + emptyDir用量
也就是说:
- skywalking sidecar 未设 ephemeral-storage → 其用量不计入限制值
- skywalking sidecar 未设 ephemeral-storage → 其用量仍计入 Pod 总用量
- 结果:Pod 实际用量可能超过限制值,触发 Evict
如果 skywalking agent 在
/agent目录(initMountPath)写入大量数据,这些数据会占用临时存储但不被限制值覆盖。
8.4 K8s 1.28 + Docker 环境说明
Kubernetes 1.28 已移除内置 dockershim(v1.24 起移除),使用 Docker 作为底层运行时需部署 cri-dockerd 作为 CRI 适配器。
但 ephemeral-storage 功能不受 CRI 影响,仍由 kubelet 管理:
– kubelet 通过 CRI 获取容器 stats
– kubelet 自己计算临时存储用量并做 eviction 判断
– 无论底层是 docker、containerd 还是其他 CRI,ephemeral-storage 限制行为一致
9. 优化建议
9.1 为所有 sidecar 补齐 ephemeral-storage
skywalking:
agent:
resources:
requests:
cpu: 100m
memory: 128Mi
ephemeral-storage: 1Gi # 补齐
limits:
cpu: 200m
memory: 256Mi
ephemeral-storage: 2Gi # 补齐
建议 sidecar 的 ephemeral-storage limits 设为
1Gi~2Gi,根据实际观察调整。
9.2 修改 Sentinel 日志路径
将 Sentinel 日志写入已挂 PVC 的目录:
jvm:
opts: ...
-Dcsp.sentinel.log.dir=/AppHome/logs/sentinel # 从 /tmp 改为 PVC 挂载路径
9.3 配置容器日志轮转
在 kubelet 配置中设置日志轮转参数(节点级别):
# /var/lib/kubelet/config.yaml
maxLogSize: 10Mi # 单个日志文件最大大小
logRotationPolicy: Rotate # 启用日志轮转
或通过 containerRuntimeConfig 调整:
apiVersion: machineconfiguration.openshift.io/v1
kind: ContainerRuntimeConfig
spec:
containerRuntimeConfig:
logSizeMax: "50Mi"
logRotationMaxFiles: 5
9.4 合理设置 ephemeral-storage requests
确保 requests 值合理,让 scheduler 能正确调度:
resources:
requests:
ephemeral-storage: 5Gi # 不需要和 limits 一致,按实际需求设置
limits:
ephemeral-storage: 10Gi # 限制最大用量
9.5 监控临时存储使用
# 查看节点临时存储容量
kubectl describe node <node-name> | grep -A5 "Allocatable"
# 查看 Pod 临时存储使用量
kubectl top pod <pod-name> --containers
# 查看节点磁盘压力状态
kubectl describe node <node-name> | grep -i "DiskPressure"
10. 总结
| 要点 | 说明 |
|---|---|
| ephemeral-storage 有效 | K8s 1.28 下该特性稳定可用,能限制容器可写层和容器日志 |
| limits → Evict | kubelet 每 10s 检查,超过 limits 触发 Pod Evict |
| requests → 调度 | scheduler 保证节点上 Pod requests 总和不超过根分区容量 |
| sidecar 必须补齐 | 未设 ephemeral-storage 的 sidecar 用量计入 Pod 总量但不计入限制值 |
| 日志挂 PVC | 大量日志写入 PVC 是最佳实践,避免消耗临时存储 |
| 只对 root 分区有效 | kubelet --root-dir 等参数可能影响生效范围 |
| inode 需关注 | 当前不支持 inode limits,大量小文件可能耗尽 inode |
相关笔记:
– kubernetes临时存储限制参数-ephemeral storage(偏实操测试)
– 理解Kubernetes驱逐Pod(Pod 驱逐机制详解)