返回首页

5.1:Volume 类型:容器之外的数据如何保存

1. 为什么容器需要持久化存储

容器的一个重要特点是“可替换”。Pod 被重建、容器被拉起重启、应用被滚动升级,都是 Kubernetes 的日常操作。但容器文件系统本身通常是临时的:你把数据直接写进容器层,容器一旦销毁,这些数据往往也就没了。因此,应用只要涉及日志缓冲、缓存、中间结果、配置注入、证书文件,或者更关键的业务数据,就必须考虑 Volume。Kubernetes Volume 的本质,是把“数据生命周期”从“容器生命周期”中拆出来处理。::cite[311]

从工程实践看,容器需要存储通常有四类原因:

  • 多容器共享同一份数据目录
  • 将配置文件注入到应用目录中
  • 将敏感信息以文件方式挂载到容器中
  • 在 Pod 重建、节点漂移或应用升级后保留数据

这也是为什么我们会把 Volume 分成临时卷和持久卷两大思路:前者解决“Pod 活着时怎么共享/缓存/注入”,后者解决“Pod 没了以后数据怎么办”。v1.32 官方文档中也明确将 emptyDirconfigMapsecret 等归入临时卷场景,将持久化需求引导到 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:把敏感信息以文件方式挂进容器

SecretConfigMap 很像,但语义上它用于保存敏感信息,例如密码、证书、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 文档说明,当前可投影的来源包括 secretconfigMapdownwardAPIserviceAccountToken。::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 风险三:配置和密钥挂载权限过宽

configMapsecret 挂载时应尽量设置为只读,必要时配合 defaultMode、容器用户权限、最小化目录暴露范围,避免应用或脚本意外修改文件。敏感凭据不要写进镜像,也不要通过日志输出。::cite[313]

9.4 风险四:忽略节点资源消耗

emptyDir 默认使用节点本地存储;medium: Memory 时还会消耗内存。很多应用把缓存、临时文件、解压目录都放进去,结果占满节点磁盘或内存,引发驱逐(Eviction)或 Pod 异常。因此,临时卷也需要容量意识。::cite[313]

10. 实操建议:如何在项目里做出正确选择

你可以按下面这个顺序思考:

  1. 这份数据是不是 Pod 删除后还要保留?
  2. 这份数据是不是可以自动重建?
  3. 它是配置/凭据,还是业务数据?
  4. 它是否需要跨节点迁移和恢复?

如果答案偏向“配置、凭据、缓存、临时共享”,优先考虑 configMapsecretemptyDir。如果答案偏向“数据库、消息、文件、状态数据”,请直接规划 PV/PVC,而不是在临时卷上继续打补丁。

11. 小结

本章你需要真正掌握的,不只是几个 Volume 名词,而是它们背后的数据生命周期模型:

  • emptyDir 解决临时共享和缓存问题
  • hostPath 让容器访问宿主机,但风险很高
  • configMap 适合非敏感配置注入
  • secret 适合敏感数据注入
  • 临时卷不能替代持久卷

下一章我们继续进入 Kubernetes 存储体系的核心:PV / PVC / StorageClass,看看 Kubernetes 到底是如何把“应用要存储”与“底层存储怎么提供”解耦开的。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

1.4 第一个 Pod:从 YAML 到运行

下一篇

7.2 Logging:Loki + Promtail