bigywater.tech
Kubernetes · 60

emptyDir:生命周期与 medium: Memory

emptyDir 到底存在哪?容器重启后文件还在吗?medium: Memory 占的是谁的内存?sizeLimit 是配额吗?这篇在真集群里读节点上的目录、/proc/mounts 和 cgroup 的 memory.stat,把这些问题都实际验证一遍。

2.3 秒
删除 Pod 后节点上 emptyDir 目录消失所用时间
51200 KiB
Memory 卷写入 50 MiB 后 cgroup 的 shmem 增量(磁盘卷是 0)
24 秒
磁盘卷写超 sizeLimit 后 Pod 被驱逐所用时间(多次运行在 24~45 秒之间)

生命周期跟着 Pod

实测 · 本机执行输出
  Pod life 在 worker2 上;节点上的目录 /var/lib/kubelet/pods/<uid>/volumes/kubernetes.io~empty-dir/v 里:['log', 'seen']
  第一个容器写完文件后自己 exit 1,kubelet 重启了它(restartCount=1);重启后 /v 里的文件:['log', 'seen'];/v/log 的内容:['第一次运行', '容器重启后又运行'](emptyDir 里的文件跨容器重启保留)
  同一个目录还在节点上:是
  删除 Pod 之后节点上的目录:消失了(等了 2.9 秒)
  同名 Pod 重新创建(同一个节点)后 /v/log:['新 Pod 写入'](是一个新的空目录)

节点上 emptyDir 的目录在 /var/lib/kubelet/pods/<uid>/volumes/kubernetes.io~empty-dir/<卷名>。容器自己 exit 1 被 kubelet 重启后,/v 里的 log 和 seen 都还在,日志里能看到“第一次运行”和“容器重启后又运行”两行——emptyDir 的文件跨容器重启保留。

删除 Pod 后 2.3 秒内节点上的目录消失;同名 Pod 重建后拿到的是一个空目录。它的生命周期跟 Pod(uid)绑定,不跟容器,也不跟名字。

medium: Memory 就是 tmpfs

实测 · 本机执行输出
  disk:/proc/mounts 里 /v 这一行:/dev/vda1 /v ext4 rw,relatime,discard 0 0
    df /v:/dev/vda1            474095688  15002816 434936664   3% /v
  mem:/proc/mounts 里 /v 这一行:tmpfs /v tmpfs rw,relatime,size=8124516k,noswap 0 0
    df /v:tmpfs                  8124516         0   8124516   0% /v
  节点内存 MemTotal=8124516 KiB(没写 sizeLimit 时 tmpfs 的大小就是整个节点内存)
  往 disk 的 /v 写 50 MiB:容器 cgroup 的 memory.current 340 KiB → 54292 KiB(增加 52 MiB);文件系统占用 51204 KiB
    memory.stat:anon=132 KiB;file(页缓存)=52556 KiB;shmem(tmpfs 页)=0 KiB;file_dirty=0 KiB
  往 mem 的 /v 写 50 MiB:容器 cgroup 的 memory.current 1744 KiB → 53068 KiB(增加 50 MiB);文件系统占用 51200 KiB
    memory.stat:anon=128 KiB;file(页缓存)=52616 KiB;shmem(tmpfs 页)=51200 KiB;file_dirty=0 KiB

默认的 emptyDir 是节点磁盘上的目录(/proc/mounts 里是 ext4,df 看到的是整块盘);medium: Memory 是 tmpfs,没写 sizeLimit 时大小是 8124516k,正好等于节点的 MemTotal。

各写 50 MiB:两个容器的 memory.current 都涨了约 50 MiB,区别在 memory.stat:Memory 卷的 shmem 是 51200 KiB,磁盘卷的 shmem 是 0,只是可回收的页缓存(file)。tmpfs 的页是共享内存,没有 swap 时不能直接丢掉,所以它是真正占着的内存。

写 Memory 卷也会 OOM

实测 · 本机执行输出
  limit 64Mi、往 Memory 卷写 100 MiB:容器状态(相对时间秒, 状态, restartCount)依次是 [(0.0, 'running', 0), (7.0, 'terminated:OOMKilled', 0), (8.0, 'waiting:RunContainerError', 1), (20.4, 'waiting:CrashLoopBackOff', 1), (21.0, 'waiting:RunContainerError', 2)]
  45 秒后 restartCount=2;state.waiting.reason=RunContainerError;lastState.terminated.reason=StartError;message=…: unable to start container process: container init was OOM-killed (memory limit too low?)
  这时 tmpfs 里的数据还在吗:Pod 级内存 memory.current=62 MiB(容器重启了,tmpfs 的页还留在 Pod 的 cgroup 里)

容器 memory limit 64Mi,往 Memory 卷写 100 MiB:约 7 秒时容器 OOMKilled;重启后新容器在 init 阶段就被 OOM 杀,RunContainerError,message 是 container init was OOM-killed (memory limit too low?),接着 CrashLoopBackOff。此时 Pod 级 memory.current 还有 62 MiB,tmpfs 里的数据没有随旧容器一起消失,新容器一启动就背着这些内存。

所以 Memory 卷要和容器的 memory limit 一起算,而且最好设 sizeLimit。

sizeLimit 的两种含义

实测 · 本机执行输出
  Memory 卷 sizeLimit 20Mi:df tmpfs                    20480         0     20480   0% /v
    写 30 MiB:["dd: error writing '/v/big': No space left on device", '21+0 records in', '20+0 records out']
    之后 Pod 状态:Running,du 20480 KiB
  磁盘卷 sizeLimit 20Mi:df /dev/vda1            474095688  15015296 434924184   3% /v(df 看到的仍是整块磁盘,sizeLimit 不是文件系统配额)
    写 30 MiB:31457280 bytes (30.0MB) copied, 0.041791 seconds, 717.9MB/s
  写完之后 24 秒:phase=Failed reason=Evicted;message=Usage of EmptyDir volume "v" exceeds the limit "20Mi". 
    事件:['Evicted: Usage of EmptyDir volume "v" exceeds the limit "20Mi". ', 'Killing: Stopping container c']

Memory 卷设 sizeLimit: 20Mi,tmpfs 的大小就是 20480 KiB,写 30 MiB 立刻 No space left on device,Pod 仍是 Running。磁盘卷的 sizeLimit 不是文件系统配额:df 仍是整块盘,写 30 MiB 毫无阻碍,要等 kubelet 下一轮检查才发现超限,24 秒后 Pod 变成 Failed、reason Evicted,message 是 Usage of EmptyDir volume "v" exceeds the limit "20Mi"。

这个检查周期不固定——多次运行里是 24~45 秒——不要依赖它做实时限制。

分步演示

emptyDir
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。独立的 5 节点 kind 集群 kx-sched(Kubernetes v1.37.0),节点是 cgroup v2;节点上的目录用 docker exec 进节点读;memory.current / memory.stat 读容器的 cgroup;“2.3 秒”“24 秒”是轮询得到的单次值,驱逐时间多次运行在 24~45 秒之间;第三节的容器状态序列用 0.15 秒间隔轮询得到;每种情形各一次
    • 没有验证:SizeMemoryBackedVolumes 与 Pod 的 memory limit 的关系(tmpfs 默认大小取节点内存还是 Pod limit,依版本与特性开关而定);hugepages 介质;emptyDir 里的 fsGroup 与权限;多个容器共享同一个 emptyDir;kubelet 检查 emptyDir 用量的周期(是否由某个参数控制);emptyDir 用量计入 ephemeral-storage 并触发节点压力驱逐(见后面的 ephemeral-storage 一篇)
    京ICP备2026067502号-1京公网安备11010502063487号