【K8s运维实战】Kubernetes临时存储卷实战:emptyDir核心用法与gitRepo弃用迁移指南

一、先说背景:什么时候该用临时存储卷?

我见过不少团队上来就想给每个Pod挂PV,其实很多场景根本不需要持久化。缓存服务、排序时的临时文件、多容器间共享的临时数据------这些东西Pod一删就没了,用持久卷完全是杀鸡用牛刀。

Kubernetes的临时卷(Ephemeral Volume) 就是干这个的:它跟Pod同生命周期,Pod创建它创建,Pod删除它删除。容器重启不影响卷里的数据,但Pod被删就什么都没了。

典型适用场景

  • 同Pod内多个容器共享临时文件
  • 缓存空间(比如磁盘归并排序)
  • 耗时计算任务的检查点存储
  • 高频读写的临时数据

临时卷里最常用的两种,就是emptyDir 和已经被弃用的gitRepo。下面一个一个讲。

二、emptyDir:最基础的临时存储卷

2.1 核心概念

emptyDir这名字起得很直白------一开始是空的目录。Pod被调度到节点上时,kubelet会创建一个空目录,Pod里的容器通过volumeMounts挂上去用。

生命周期就一句话:Pod在,卷就在;Pod删,卷就删

有个细节容易搞混:容器崩溃重启不会丢数据,因为容器重启不等于Pod删除。只有Pod被彻底移除,emptyDir才会被清理。

2.2 基础配置:最小可用示例

复制代码
apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis:7.2
    volumeMounts:
    - name: redis-storage
      mountPath: /data/redis    # 容器内挂载路径
  volumes:
  - name: redis-storage
    emptyDir: {}                # 空配置,使用节点默认存储(通常是磁盘)

这是官方文档里的标准示例。emptyDir: {}表示使用节点磁盘,不限大小(实际受节点磁盘容量限制)。

2.3 进阶配置:sizeLimit限制容量

这是我强烈建议生产环境必配的参数。不设sizeLimit的emptyDir能把节点磁盘写爆,触发DiskPressure导致节点上所有Pod被驱逐。

复制代码
volumes:
- name: cache
  emptyDir:
    sizeLimit: 10Gi   # 限制最大10GB

超过限制会怎样?kubelet会在其周期性资源回收检测(约1分钟一个周期) 中发现超限,随后触发Pod驱逐。实测通常在超限后1分钟内看到Pod变成Evicted,报错类似:

Usage of EmptyDir volume "cache" exceeds the limit "10Gi"

社区里因为这个被坑的人不少------Redpanda、Trino、Crossplane等项目都有过类似issue。

2.4 内存模式:medium: Memory

如果你需要极高性能的临时存储,可以把emptyDir放到内存(tmpfs) 里:

复制代码
volumes:
- name: fast-cache
  emptyDir:
    medium: Memory
    sizeLimit: 2Gi    # 内存模式下强烈建议设limit

三个关键注意事项

  1. 数据存在内存里。如果Pod被驱逐或所在节点重启,tmpfs里的数据会彻底丢失;但单纯的容器进程崩溃重启不影响数据
  2. tmpfs占用的是Pod的内存配额,会计入Pod的memory limit。
  3. sizeLimit必须小于Pod的memory limit,否则可能OOM。

我一般只在两种场景用内存模式:超高频率的缓存 ,或者需要修改/dev/shm大小(有些应用对共享内存有要求)。

补充说明 :默认情况下emptyDir使用节点的/var/lib/kubelet所在磁盘。如果你希望将emptyDir放到特定存储介质(如SSD),可以通过调整kubelet的--root-dir参数改变整体路径,但原生不支持按卷粒度指定不同的存储介质。

2.5 调度陷阱:sizeLimit不影响Pod调度

这是一个很容易被忽略的坑

Kubernetes调度器(Scheduler)在决定Pod调度到哪个节点时,默认完全不检查节点剩余磁盘空间是否大于 emptyDir.sizeLimit,它只检查CPU和内存是否充足。

举个例子:节点A只剩1Gi可用磁盘,但你的Pod声明了sizeLimit: 10Gi。调度器一看CPU和内存都够,嗖一下就把Pod调度到节点A上了。然后kubelet发现磁盘空间不足,立刻把Pod驱逐(Evicted)。Pod进入Pending状态,重新调度,如果集群里所有节点磁盘都不够,这Pod就永远Pending在那里。

特别澄清 :上述说法有一个例外------如果Pod同时显式声明了resources.requests.ephemeral-storage,调度器 将此请求值纳入节点磁盘容量的评估。但emptyDir.sizeLimit本身不作为调度依据,两者是独立配置的。

怎么破? 目前只能靠外部手段:

  • 监控节点磁盘使用率,及时扩容或清理
  • 使用StorageClass + 动态PV替代大容量emptyDir(注意:标准PV调度仅检查PV是否存在,不检查容量匹配,需配合外部调度扩展或容量感知调度特性)
  • 在应用层控制写入量,避免打满磁盘

想自己验证这个行为? 可以在测试集群里创建一个sizeLimit: 100Gi的Pod,然后观察它是否能调度到一个实际磁盘剩余空间远小于100Gi的节点上。如果节点能成功运行该Pod(尽管随后会被驱逐),就说明调度时确实没把sizeLimit作为筛选条件。这是一个很好的动手实验,能帮你直观理解调度器的行为边界。

2.6 两个容易混淆的概念

|--------------------------------------|------------------------------------------------------------|
| 概念 | 说明 |
| emptyDir.sizeLimit | 单个emptyDir卷的容量上限,超了Pod被驱逐 |
| resources.limits.ephemeral-storage | 容器级别临时存储总限额,包含:容器可写层(rootfs的写入)、容器日志、以及挂载的emptyDir卷的总和 |

两个可以同时用。如果配了ResourceQuota,用户必须在Pod里显式声明ephemeral-storage limit,否则配额不生效。


emptyDir虽然好用,但它本身是空的。早期Kubernetes为了让开发者省事,搞了一个gitRepo卷,想帮你自动把代码clone到emptyDir里。听上去很美好,但接下来我要讲的,是这个功能为何成为Kubernetes历史上最大的"坑"之一。

三、gitRepo:已被弃用的存储卷类型(重要!)

3.1 它曾经是什么

gitRepo卷本质上就是emptyDir + 自动git clone。Pod启动时,Kubernetes帮你把Git仓库克隆到一个emptyDir里挂载给容器。

配置长这样(注意:这是 错误示例,别抄):

复制代码
# ❌ 已弃用,不要再用了
volumes:
- name: config
  gitRepo:
    repository: "https://github.com/example/config.git"
    revision: "abc123"

3.2 为什么弃用?(三个硬伤)

第一,严重安全漏洞 。gitRepo在执行git clone时用的是节点上的root权限。攻击者可以通过构造恶意Git仓库的hooks目录,执行容器外的任意命令。

相关CVE:

  • CVE-2024-10220:任意命令执行,影响kubelet 1.28.11及以下、1.29.0-1.29.6、1.30.0-1.30.2
  • CVE-2025-1767:可访问同节点其他Pod的本地Git仓库

第二,功能残缺。不支持SSH密钥认证、不支持私有仓库、不支持分支选择、不支持稀疏检出、不支持LFS,克隆完就再也不更新。

第三,官方已放弃维护 。自Kubernetes v1.33起默认禁用v1.36将永久移除,无法重新启用。API层面仍然接受带gitRepo的Pod,但kubelet会直接拒绝运行并报错。

3.3 如何发现集群中还在用gitRepo

先用命令扫一遍,心里有个数:

复制代码
# 查找所有使用gitRepo的Pod
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.volumes[]?.gitRepo != null) | "\(.metadata.namespace)/\(.metadata.name)"'

# 在YAML文件中搜索
grep -rn "gitRepo:" --include="*.yaml" --include="*.yml" .

找到的赶紧改,别等v1.36集群升级时Pod起不来才慌。

3.4 迁移方案:InitContainer + emptyDir(✅推荐)

官方推荐的替代方案是InitContainer + emptyDir

复制代码
apiVersion: v1
kind: Pod
metadata:
  name: app-with-git
spec:
  initContainers:
  - name: git-clone
    image: bitnami/git:2.45        # 轻量Git镜像
    command:
    - git
    - clone
    - --depth=1                    # 浅克隆,省空间省时间
    - --branch=main
    - https://github.com/example/config.git
    - /repo                        # 克隆目标目录,InitContainer执行后代码落在这里
    volumeMounts:
    - name: git-repo
      mountPath: /repo
    securityContext:
      runAsNonRoot: true
      runAsUser: 1000              # 非root运行,更安全
  containers:
  - name: app
    image: registry.example.com/app:v2.0
    volumeMounts:
    - name: git-repo
      mountPath: /config
      readOnly: true               # 生产环境建议只读
  volumes:
  - name: git-repo
    emptyDir: {}                   # 底层还是emptyDir

这个方案的核心逻辑:InitContainer先跑,把仓库克隆到emptyDir里;主容器启动时,emptyDir里已经有代码了。

私有仓库认证也不怕,用Secret存OAuth Token或个人访问令牌(PAT)即可:

复制代码
initContainers:
- name: git-clone
  image: bitnami/git:2.45
  command:
  - /bin/sh
  - -c
  - git clone --depth=1 https://${GIT_TOKEN}@github.com/example/private-config.git /repo
  env:
  - name: GIT_TOKEN
    valueFrom:
      secretKeyRef:
        name: git-credentials
        key: token
  # volumes和volumeMounts同上

认证小贴士 :强烈建议使用OAuth Token或PAT拼接到URL中。如果必须使用含@#等特殊字符的密码,直接拼接会导致Illegal character报错,此时应改用GIT_ASKPASS环境变量方式传递。

四、验证方法

4.1 验证emptyDir是否正常挂载

复制代码
# 创建测试Pod
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-test
spec:
  containers:
  - name: writer
    image: busybox
    command: ["/bin/sh", "-c", "echo 'hello' > /data/test.txt && sleep 3600"]
    volumeMounts:
    - name: data
      mountPath: /data
  - name: reader
    image: busybox
    command: ["/bin/sh", "-c", "sleep 3600"]
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    emptyDir: {}
EOF

# 验证reader容器能看到writer写的文件
kubectl exec emptydir-test -c reader -- cat /data/test.txt
# 预期输出: hello

4.2 验证sizeLimit是否生效

复制代码
# 创建带sizeLimit的Pod
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: size-limit-test
spec:
  restartPolicy: Never
  containers:
  - name: writer
    image: busybox
    command: ["/bin/sh", "-c", "dd if=/dev/zero of=/data/bigfile bs=1M count=600 && sleep 3600"]
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    emptyDir:
      sizeLimit: 500Mi
EOF

# 观察Pod状态------约1分钟内会被驱逐
kubectl get pod size-limit-test -w
# 最终会看到状态变为Evicted

4.3 验证gitRepo迁移方案

复制代码
# 用上面3.4节的YAML创建Pod
kubectl apply -f app-with-git.yaml

# 检查InitContainer是否成功
kubectl logs app-with-git -c git-clone
# 预期输出: Cloning into '/repo'...

# 检查主容器是否能看到文件
kubectl exec app-with-git -- ls -la /config

五、常见问题(真实报错)

Q1:Pod被驱逐,报"Usage of EmptyDir volume exceeds the limit"

Usage of EmptyDir volume "cache" exceeds the limit "10Gi"

原因:emptyDir.sizeLimit设得太小,或者应用写入了超出预期的数据量。kubelet在周期性检测(约1分钟)时发现超限,触发了驱逐。

解决

  1. 检查应用是否有日志/缓存无限增长的问题
  2. 适当调大sizeLimit
  3. 如果无法预估,考虑换用持久卷或增加日志轮转策略

Q2:内存模式下Pod被OOM Kill

原因medium: Memory的emptyDir计入Pod内存配额。sizeLimit + 容器本身内存使用 > Pod memory limit。

解决 :确保sizeLimit < Pod memory limit,预留足够内存给应用本身。

Q3:Pod一直Pending,节点磁盘明明还有空间

原因 :这就是我在2.5节提到的调度陷阱 。调度器只看CPU和内存,不看emptyDir.sizeLimit。但kubelet在启动Pod时发现磁盘空间不足,将其驱逐,导致Pod反复Pending-Evicted。

解决

  1. 清理节点上的无用镜像和日志:docker system prune -f(或nerdctl对应命令)
  2. 扩容节点磁盘
  3. 减小emptyDir的sizeLimit
  4. 考虑使用PVC代替大容量emptyDir(需配合外部调度方案)

Q4:升级到Kubernetes v1.33+后,带gitRepo的Pod起不来

The gitRepo volume plugin is deprecated and is disabled by default

原因:v1.33起gitRepo默认禁用,v1.36彻底移除。

解决:按3.4节方案迁移到InitContainer。

Q5:gitRepo安全漏洞扫描报CVE-2024-10220

The Kubernetes kubelet component allows arbitrary command execution via specially crafted gitRepo volumes

原因:集群中还有Pod在使用gitRepo卷。

解决:立即按3.4节迁移。如果暂时无法迁移,至少升级到已修复的kubelet版本(1.28.12+、1.29.7+、1.30.3+)。

六、总结

三个核心要点:

  1. emptyDir是临时存储的首选 ------轻量、简单、跟Pod走。生产环境务必配sizeLimit,别让一个Pod把节点磁盘写爆。同时记住:sizeLimit默认不影响调度 ,别指望它帮你做节点磁盘容量筛选(除非配合ephemeral-storage request使用)。
  2. medium: Memory是把双刃剑------性能极高但占内存配额,sizeLimit必须小于Pod memory limit。容器崩溃重启不影响数据,但Pod被驱逐或节点重启数据就没了。
  3. gitRepo已死,有事烧纸------安全漏洞+功能残缺+官方弃用,赶紧迁移到InitContainer+emptyDir方案。先用命令扫一遍集群,把还在用gitRepo的Pod找出来,按本文方案逐个整改。别等到v1.36升级时才发现Pod起不来。

如果觉得有用,欢迎分享给更多被gitRepo坑过的同事。

有问题或想补充?欢迎在评论区交流。

相关推荐
珠海西格电力1 小时前
云边端协同架构:零碳园区管理系统的技术底座
大数据·运维·人工智能·算法·架构·能源
阿成学长_Cain2 小时前
Linux dirs命令详解|Bash目录堆栈管理快速切换目录实战教程
linux·运维·前端·数据库
小羊Yveesss2 小时前
2026年微信小程序后端怎么搭建?后台、数据、接口和运维怎么选
运维·微信小程序·小程序
想你依然心痛4 小时前
嵌入式Linux安全加固:SELinux、Capabilities与Seccomp——强制访问控制与沙箱
linux·运维·安全
AAA@峥5 小时前
容器数据不丢失:Docker 分层存储 + Volume 共享、备份迁移完整指南
运维·docker·容器
MDM.Plus5 小时前
苹果MDM技术演进:从远程控制到设备信任体系的构建
运维·服务器·安全·ios·mdm·手机店
REDcker5 小时前
macOS 挂载 Linux 远程目录详解
linux·运维·macos
2601_961593425 小时前
Mac 上搭建Linux环境吗?VMware + CentOS Stream 9 镜像快速部署
linux·运维·ide·macos·centos
写代码的学渣6 小时前
Linux systemd 开机启动日志逐行详细解析报告
linux·运维·服务器