1. 为什么容器需要持久化存储
容器的一个重要特点是“可替换”。Pod 被重建、容器被拉起重启、应用被滚动升级,都是 Kubernetes 的日常操作。但容器文件系统本身通常是临时的:你把数据直接写进容器层,容器一旦销毁,这些数据往往也就没了。因此,应用只要涉及日志缓冲、缓存、中间结果、配置注入、证书文件,或者更关键的业务数据,就必须考虑 Volume。Kubernetes Volume 的本质,是把“数据生命周期”从“容器生命周期”中拆出来处理。::cite[311]
从工程实践看,容器需要存储通常有四类原因:
- 多容器共享同一份数据目录
- 将配置文件注入到应用目录中
- 将敏感信息以文件方式挂载到容器中
- 在 Pod 重建、节点漂移或应用升级后保留数据
这也是为什么我们会把 Volume 分成临时卷和持久卷两大思路:前者解决“Pod 活着时怎么共享/缓存/注入”,后者解决“Pod 没了以后数据怎么办”。v1.32 官方文档中也明确将 emptyDir、configMap、secret 等归入临时卷场景,将持久化需求引导到 PersistentVolume 体系。::cite[313]
2. 先建立一个整体认知
可以先把常见 Volume 理解成下面这张图:
| 类型 | 数据生命周期 | 典型用途 | 是否适合业务数据 |
|---|---|---|---|
emptyDir |
跟随 Pod | 缓存、临时文件、容器间共享目录 | 不适合 |
hostPath |
跟随节点目录 | 节点调试、采集宿主机日志 | 一般不建议 |
configMap |
来自 API 对象 | 配置文件挂载 | 不适合 |
secret |
来自 API 对象 | 密码、Token、证书挂载 | 不适合 |
| 持久卷(PV/PVC) | 独立于 Pod | 数据库、消息队列、业务数据 | 适合 |
这张表最重要的结论不是记住名字,而是记住边界:只要你的数据需要“Pod 删除后仍然存在”,就不要停留在普通临时 Volume 上。::cite[311]
3. emptyDir:最常见的临时卷
emptyDir 是 Pod 启动时自动创建的一块目录空间。它在 Pod 创建时为空,Pod 里的多个容器都可以挂载它;当 Pod 从节点上被移除时,其中的数据也会一起被清空。官方文档特别指出,emptyDir 的底层存储通常来自节点磁盘,也可以配置成内存。::cite[313]
3.1 典型使用场景
- Web 容器与 Sidecar 共享生成后的静态文件
- 应用运行期间的缓存目录
- 临时解压目录
- 需要高性能临时空间时,使用内存型
emptyDir
3.2 实操:两个容器共享 emptyDir
下面的示例中:
writer容器持续写入文件reader容器持续读取同一个目录- 两个容器通过
emptyDir实现共享
apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
containers:
- name: writer
image: busybox:1.36
command:
- /bin/sh
- -c
- |
i=0
while true; do
echo "log-$i $(date)" >> /cache/app.log
i=$((i+1))
sleep 5
done
volumeMounts:
- name: cache-volume
mountPath: /cache
- name: reader
image: busybox:1.36
command:
- /bin/sh
- -c
- |
while true; do
tail -n 5 /cache/app.log || true
sleep 10
done
volumeMounts:
- name: cache-volume
mountPath: /cache
volumes:
- name: cache-volume
emptyDir: {}
执行步骤:
kubectl apply -f emptydir-demo.yaml
kubectl logs -f pod/emptydir-demo -c reader
kubectl delete pod emptydir-demo
你会发现,Pod 删除以后,/cache/app.log 中的内容也随之消失。这正是 emptyDir 的边界:适合临时共享,不适合持久数据。::cite[313]
3.3 内存型 emptyDir
如果你希望临时文件直接放在内存里,可以使用 medium: Memory:
apiVersion: v1
kind: Pod
metadata:
name: emptydir-memory-demo
spec:
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: mem-cache
mountPath: /usr/share/nginx/html/cache
volumes:
- name: mem-cache
emptyDir:
medium: Memory
sizeLimit: 128Mi
这种模式适合高速缓存,但也要注意:它消耗的是节点内存,不是“免费空间”。如果缓存打爆内存,可能影响 Pod 稳定性。::cite[313]
4. hostPath:能用,但要非常谨慎
hostPath 会把节点上的某个真实目录或文件直接挂载到 Pod 中。它看起来很方便,但风险也很大,因为它让容器直接接触宿主机文件系统。官方文档将它列为 Volume 类型之一,但实际生产中通常只在节点级运维、日志采集、设备插件、特殊调试场景下使用。::cite[311]
4.1 适合的场景
- 采集节点上的日志目录
- 节点本地代理读取宿主机系统信息
- 特定 DaemonSet 需要访问宿主机路径
4.2 不适合的场景
- 把它当作数据库持久化方案
- 在多节点集群里期望数据自动迁移
- 普通业务 Pod 直接操作宿主机敏感目录
4.3 实操:挂载宿主机日志目录
apiVersion: v1
kind: Pod
metadata:
name: hostpath-demo
spec:
containers:
- name: log-reader
image: busybox:1.36
command:
- /bin/sh
- -c
- sleep 3600
volumeMounts:
- name: host-logs
mountPath: /host-logs
readOnly: true
volumes:
- name: host-logs
hostPath:
path: /var/log
type: Directory
执行后进入容器查看:
kubectl apply -f hostpath-demo.yaml
kubectl exec -it hostpath-demo -- ls /host-logs
请注意,hostPath 强依赖节点本地路径。如果 Pod 被调度到另一台机器,这个路径里的内容可能完全不同,甚至不存在。因此它天然不具备跨节点迁移能力。::cite[311]
5. configMap:把配置以文件方式挂进容器
ConfigMap 用于保存非敏感配置。它可以作为环境变量注入,也可以作为文件挂载成 Volume。官方文档明确指出,ConfigMap 适合解耦环境相关配置,使镜像保持通用。::cite[312]
5.1 实操:挂载 Nginx 配置文件
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
namespace: default
data:
default.conf: |
server {
listen 80;
server_name _;
location / {
return 200 'hello from configmap';
add_header Content-Type text/plain;
}
}
---
apiVersion: v1
kind: Pod
metadata:
name: configmap-volume-demo
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
readOnly: true
volumes:
- name: config-volume
configMap:
name: nginx-config
执行步骤:
kubectl apply -f configmap-volume-demo.yaml
kubectl port-forward pod/configmap-volume-demo 8080:80
curl http://127.0.0.1:8080
如果返回 hello from configmap,说明配置文件已经通过 Volume 成功挂进容器。对于应用配置来说,这比把配置直接打进镜像更灵活。::cite[312]
6. secret:把敏感信息以文件方式挂进容器
Secret 与 ConfigMap 很像,但语义上它用于保存敏感信息,例如密码、证书、API Token。Kubernetes 文档把 secret 也列为一种可注入 Pod 的临时卷来源。它适合解决“安全数据如何进容器”的问题,但并不意味着它本身就是完整的密钥管理体系。::cite[313]
6.1 实操:挂载数据库密码文件
apiVersion: v1
kind: Secret
metadata:
name: db-secret
namespace: default
type: Opaque
stringData:
username: admin
password: S3cret-Passw0rd
---
apiVersion: v1
kind: Pod
metadata:
name: secret-volume-demo
spec:
containers:
- name: app
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "username=$(cat /etc/creds/username)"
echo "password loaded"
sleep 3600
volumeMounts:
- name: secret-volume
mountPath: /etc/creds
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-secret
查看运行结果:
kubectl apply -f secret-volume-demo.yaml
kubectl logs secret-volume-demo
实际业务里,建议把 Secret 以只读文件方式挂载,而不是直接打印内容;调试时也不要把敏感数据写进日志。::cite[313]
7. projected:把多种数据源合并为一个挂载目录
当你既想挂载 configMap,又想挂载 secret,还想把 Downward API 信息统一放进一个目录时,可以使用 projected Volume。v1.32 文档说明,当前可投影的来源包括 secret、configMap、downwardAPI 和 serviceAccountToken。::cite[314]
apiVersion: v1
kind: Pod
metadata:
name: projected-volume-demo
spec:
containers:
- name: app
image: busybox:1.36
command:
- /bin/sh
- -c
- sleep 3600
volumeMounts:
- name: app-config
mountPath: /projected
readOnly: true
volumes:
- name: app-config
projected:
sources:
- configMap:
name: nginx-config
- secret:
name: db-secret
- downwardAPI:
items:
- path: pod-name
fieldRef:
fieldPath: metadata.name
这个能力很适合做“统一配置目录”,减少应用对多个挂载路径的处理复杂度。::cite[314]
8. 临时卷与持久卷的使用边界
这一节非常关键。很多存储事故,并不是因为不会写 YAML,而是因为一开始就选错了 Volume 类型。
8.1 什么时候用临时卷
如果数据满足下面任意一点,通常可以优先考虑临时卷:
- 数据只在 Pod 生命周期内有价值
- 数据可以随时重建
- 数据只是配置、凭据或运行时缓存
- 数据不需要跨 Pod、跨节点保留
例如:
- Web 应用把模板渲染结果放到
emptyDir - 容器启动时把配置文件从
configMap挂进去 - 应用把数据库密码从
secret读到内存中
8.2 什么时候必须上持久卷
如果数据具有下面特征,就应当进入 PV/PVC 体系:
- Pod 删除后数据仍需保留
- 节点故障后数据仍需恢复
- 业务数据无法轻易重建
- 应用依赖稳定的数据目录
例如:
- MySQL、PostgreSQL 数据目录
- Kafka、RabbitMQ 消息数据目录
- Elasticsearch、MinIO 等有状态服务数据
Kubernetes 官方文档将“临时卷”和“持久卷”明确区分,本质上就是在提醒你:配置不是数据,缓存不是数据,真正的业务数据不能寄希望于 Pod 级别的临时存储。::cite[313]
9. Volume 挂载的常见风险与注意事项
9.1 风险一:把 hostPath 当通用持久化方案
这是最常见的误用之一。hostPath 只是在某一台节点上有数据,并不能保证换节点后还能访问同一份数据。更严重的是,容器可能借此读写宿主机敏感文件。除非你非常清楚自己在做什么,否则不要把它作为普通业务存储方案。::cite[311]
9.2 风险二:临时卷里放业务数据
有些同学会把 SQLite、MySQL 或业务上传文件直接放进 emptyDir,测试时一切正常,Pod 一重建数据全没。结论很简单:临时卷解决的是“运行时目录问题”,不是“数据可靠性问题”。::cite[313]
9.3 风险三:配置和密钥挂载权限过宽
configMap 和 secret 挂载时应尽量设置为只读,必要时配合 defaultMode、容器用户权限、最小化目录暴露范围,避免应用或脚本意外修改文件。敏感凭据不要写进镜像,也不要通过日志输出。::cite[313]
9.4 风险四:忽略节点资源消耗
emptyDir 默认使用节点本地存储;medium: Memory 时还会消耗内存。很多应用把缓存、临时文件、解压目录都放进去,结果占满节点磁盘或内存,引发驱逐(Eviction)或 Pod 异常。因此,临时卷也需要容量意识。::cite[313]
10. 实操建议:如何在项目里做出正确选择
你可以按下面这个顺序思考:
- 这份数据是不是 Pod 删除后还要保留?
- 这份数据是不是可以自动重建?
- 它是配置/凭据,还是业务数据?
- 它是否需要跨节点迁移和恢复?
如果答案偏向“配置、凭据、缓存、临时共享”,优先考虑 configMap、secret、emptyDir。如果答案偏向“数据库、消息、文件、状态数据”,请直接规划 PV/PVC,而不是在临时卷上继续打补丁。
11. 小结
本章你需要真正掌握的,不只是几个 Volume 名词,而是它们背后的数据生命周期模型:
emptyDir解决临时共享和缓存问题hostPath让容器访问宿主机,但风险很高configMap适合非敏感配置注入secret适合敏感数据注入- 临时卷不能替代持久卷
下一章我们继续进入 Kubernetes 存储体系的核心:PV / PVC / StorageClass,看看 Kubernetes 到底是如何把“应用要存储”与“底层存储怎么提供”解耦开的。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!