资讯中心

【Kubernetes从入门到精通】第19篇:Volume——容器数据的“不动产“

📅 2026/8/6 8:33:09
【Kubernetes从入门到精通】第19篇:Volume——容器数据的“不动产“
上一篇【第18篇】Ingress Controller选型和实战——Nginx Ingress完全指南下一篇【第20篇】ConfigMap——配置管理的正确姿势摘要容器有一个健忘症——重启就失忆删了就彻底消失。你费劲写进去的日志没了、用户上传的图片蒸发了、数据库的数据全丢了……这谁受得了K8s的解决方案是Volume——它是在Pod级别定义的外接存储跟容器生命周期解耦容器挂了Volume里的数据还在新容器起来接着用。这篇文章从如果没有Volume会怎样讲起拆解K8s里最常用的几种Volume类型emptyDir、hostPath、nfs用表格帮你对比选型然后演示两个实战利器——subPath把单个文件挂进去而不是覆盖整个目录和ConfigMap/Secret的挂载。读完这篇你就知道数据放哪儿这个问题的答案了。一、为什么容器需要Volume——“鱼的记忆只有七秒”先感受一下容器失忆的痛【没有Volume——容器重启 数据丢失】 Pod启动 │ ▼ ┌─────────────────────────────────────┐ │ Container: nginx │ │ ─────────────────────────────── │ │ /var/log/nginx/access.log │ 用户请求写入的日志 │ /usr/share/nginx/html/index.html │ 网站文件 │ /tmp/uploaded/pic.jpg │ 用户上传的图片 │ │ │ 这些数据存在容器的文件系统里 │ │ 容器文件系统是临时的——跟鱼一样 │ └─────────────────────────────────────┘ │ │ Pod重启/容器崩溃 ▼ ┌─────────────────────────────────────┐ │ Container: nginx (新容器) │ │ ─────────────────────────────── │ │ /var/log/nginx/access.log → 空的 │ │ /usr/share/nginx/html/index.html → 没了│ │ /tmp/uploaded/pic.jpg → 消失了 │ │ │ │ 我的数据呢 │ └─────────────────────────────────────┘有了Volume之后【有Volume——数据存在外部跟容器生命周期解耦】 ┌─────────────────────────────────────┐ │ Volume │ │ ┌─────────────────────────────┐ │ │ │ 真实的数据存储位置 │ │ │ │ • Node本地磁盘emptyDir │ │ │ │ • Node指定路径hostPath │ │ │ │ • NFS远程存储 │ │ │ │ • 云盘AWS EBS/阿里云盘 │ │ │ │ │ │ │ │ Volume跟Pod同生命周期 │ │ │ │ Pod删了Volume才没 │ │ │ │ 容器重启数据完好 │ │ │ └──────────┬──────────────────┘ │ │ │ 挂载到容器 │ │ ▼ │ │ ┌─────────────────────────────┐ │ │ │ Container │ │ │ │ /var/log/nginx/ → Volume │ │ │ │ 容器以为自己写的是本地磁盘 │ │ │ │ 实际上写的都是Volume │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────┘要点Volume的生命周期跟Pod绑定不是跟容器绑定。容器重启、重建——只要Pod没删Volume就在。这跟你租房一个道理容器是租客来来去去Volume是房子一直在那儿。租客搬走了房子里的东西不会跟着消失。但房子拆了Pod删除Volume也就没了至少emptyDir是这样。Volume和容器文件系统的对比维度容器文件系统Volume生命周期跟容器绑定重启就没了跟Pod绑定Pod删除才释放容器间共享❌ 各容器独立文件系统✅ 同一Pod的容器可以共享持久化❌ 临时的取决于Volume类型hostPath/NFS可持久性能容器层写时复制性能差直接写底层存储性能好大小限制受镜像层限制取决于底层存储二、常用Volume类型——从临时到永久K8s支持几十种Volume类型但日常用的就那几种。按持久化程度从低到高排列【Volume 持久化排行榜】 持久化程度 ▲ │ ┌─────────────────────────┐ │ │ 云盘 (AWS EBS/PD/disk) │ ← 最高Pod删了数据还在还能漂移到别的Node │ ├─────────────────────────┤ │ │ NFS / CephFS │ ← 高网络存储多Pod共享 │ ├─────────────────────────┤ │ │ hostPath │ ← 中绑定Node磁盘Pod删了数据还在(Node上) │ ├─────────────────────────┤ │ │ emptyDir │ ← 低Pod删了就没但容器重启数据还在 │ └─────────────────────────┘ │ └────────────────────────────────────────────► 共享能力2.1 emptyDir——“Pod级别的临时便签”Pod启动时创建一个空目录Pod内所有容器都能用。Pod删除时目录内容清空。【emptyDir 典型使用场景】 ┌─────────────────────────────────────────────┐ │ Pod │ │ │ │ ┌───────────────┐ ┌──────────────────┐ │ │ │ nginx │ │ filebeat │ │ │ │ 写日志到 │ │ 读日志从 │ │ │ │ /var/log/ │ │ /logs/ │ │ │ └───────┬───────┘ └────────┬─────────┘ │ │ │ │ │ │ │ ┌──────────────┐ │ │ │ └─►│ emptyDir │◄──┘ │ │ │ (共享卷) │ │ │ └──────────────┘ │ │ │ │ 容器间共享文件、临时缓存、中间结果 │ └─────────────────────────────────────────────┘apiVersion:v1kind:Podmetadata:name:nginx-with-loggerspec:volumes:-name:shared-logsemptyDir:{}# 啥也不用配创建个空目录containers:-name:nginximage:nginx:1.25volumeMounts:-name:shared-logsmountPath:/var/log/nginx# nginx日志写到这里-name:log-readerimage:busyboxcommand:[tail,-f,/logs/access.log]volumeMounts:-name:shared-logsmountPath:/logs# 另一个容器从这里读readOnly:true# emptyDir 也可以用内存做存储tmpfs——极速但Pod一挂全没volumes:-name:ram-diskemptyDir:medium:Memory# 用内存读写极快sizeLimit:256Mi# 限制大小防止把Node内存吃光要点emptyDir是Pod级别的——Pod删了就没了。它的典型场景有三个(1) 容器间共享文件如日志代理收集业务日志(2) 临时计算中间结果(3) 用medium: Memory做超高速读写缓存。但千万别拿emptyDir存数据库——Pod一删库就跑了2.2 hostPath——“直连Node磁盘”把Node上的一个目录直接挂载到Pod里。Pod删除后Node上的数据还在。【hostPath——直接挂载Node目录】 Node-1 文件系统 ┌──────────────────────────────────────┐ │ /data/k8s/ │ │ ├── logs/ │ │ │ ├── app.log │ │ │ └── error.log │ │ └── config/ │ │ └── nginx.conf │ └──────────────┬───────────────────────┘ │ hostPath 挂载 ▼ ┌──────────────────────────────────────┐ │ Pod-1 │ │ /var/log/app → hostPath:/data/logs │ │ /etc/nginx → hostPath:/data/config│ └──────────────────────────────────────┘apiVersion:v1kind:Podmetadata:name:hostpath-demospec:volumes:-name:host-logshostPath:path:/data/k8s/logs# Node上的绝对路径type:DirectoryOrCreate# 目录不存在就创建-name:host-confighostPath:path:/etc/kubernetes/ssltype:Directory# 必须已存在containers:-name:appimage:myapp:latestvolumeMounts:-name:host-logsmountPath:/var/log/app-name:host-configmountPath:/etc/ssl/certsreadOnly:truehostPath的type参数很重要type值含义安全性空不检查啥都能挂⚠️ 最低DirectoryOrCreate目录存在就用不存在就创建较安全Directory目录必须已存在✅ 推荐FileOrCreate文件存在就用不存在就创建较少用File文件必须已存在较少用Socket必须是Unix Socket特殊场景要点hostPath是一把双刃剑——好用但危险。同一个hostPath可能被多个Pod同时写造成文件冲突。更严重的是如果Pod被调度到另一个Node上新Pod访问不到原来Node上的数据。hostPath只适合两类场景(1) DaemonSet如日志收集agent每个Node一个(2) 要访问Node系统文件的监控/管理工具。2.3 NFS——“网络共享硬盘”NFS卷让多个Pod跨Node共享同一个文件系统——这是hostPath做不到的。【NFS——多Node多Pod共享】 ┌──────────────────┐ │ NFS Server │ │ 192.168.1.100 │ │ /exports/data │ └────────┬─────────┘ │ 网络挂载 ┌─────┼─────┐ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Node-1 │ │ Node-2 │ │ Node-3 │ │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │ │ │Pod-A │ │ │ │Pod-B │ │ │ │Pod-C │ │ │ │/data │ │ │ │/data │ │ │ │/data │ │ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────┴─────────────┘ 都挂载同一个 NFS 目录 /data 里看到的是同一份数据apiVersion:v1kind:Podmetadata:name:nfs-demospec:volumes:-name:shared-datanfs:server:192.168.1.100# NFS服务器地址path:/exports/data# NFS导出路径readOnly:falsecontainers:-name:appimage:myapp:latestvolumeMounts:-name:shared-datamountPath:/data三种常用Volume快速对比类型持久性多Pod共享跨Node性能生产就绪emptyDirPod删了就没✅ 同Pod内共享❌ 绑定Node高本地磁盘/内存临时数据 ✅hostPathPod删了还在⚠️ 同Node可共享❌ 绑定Node高本地磁盘仅DaemonSet ✅NFS独立于Pod✅ 任意Pod✅ 跨Node中受网络影响✅ 但需维护NFS服务器三、Volume的挂载方式——两种模式3.1 普通挂载——整个目录# Volume挂到容器的/mnt/data目录# 容器里 /mnt/data 之前的内容会被Volume覆盖看不到原来的了volumes:-name:myvolemptyDir:{}containers:-name:appvolumeMounts:-name:myvolmountPath:/mnt/data# 挂到这个路径3.2 subPath——“只挂一个文件别把整个目录盖了”这是Volume使用中最容易误解也最实用的技巧。默认挂载会把目标目录整个替换成Volume内容但有时候你只想把一个文件比如配置文件塞进去apiVersion:v1kind:Podmetadata:name:subpath-demospec:volumes:-name:config-volumeconfigMap:name:app-config# ConfigMap里有 nginx.confcontainers:-name:nginximage:nginx:1.25volumeMounts:-name:config-volumemountPath:/etc/nginx/nginx.conf# ❌ 错误会把整个/etc/nginx/覆盖subPath:nginx.conf# ✅ 正确只替换nginx.conf这一个文件【subPath 的作用——挂单个文件 vs 覆盖整个目录】 没有 subPath默认行为 有 subPath ┌────────────────────────┐ ┌────────────────────────┐ │ 容器 /etc/nginx/ │ │ 容器 /etc/nginx/ │ │ ├── nginx.conf ← 覆盖 │ │ ├── nginx.conf ← 替换 │ │ ├── mime.types ← 没了│ │ ├── mime.types ✅ 还在 │ │ ├── modules/ ← 没了│ │ ├── modules/ ✅ 还在 │ │ └── conf.d/ ← 没了│ │ └── conf.d/ ✅ 还在 │ └────────────────────────┘ └────────────────────────┘ 整个目录被Volume内容替换掉了 只替换了nginx.conf一个文件 其他文件全丢失 其他文件完好无损要点subPath是精准替换——它把Volume里的一个文件/目录挂到容器的指定路径不影响该路径下的其他文件。这在配置注入场景是标配用法你只想替换nginx.conf不想把整个/etc/nginx/目录清空。四、挂载ConfigMap和Secret——配置文件注入Volume的一大用途是把ConfigMap和Secret挂载成文件——应用不用改代码直接读文件就行。apiVersion:v1kind:Podmetadata:name:configmap-volume-demospec:volumes:-name:app-configconfigMap:name:myapp-configitems:# 选择性挂载某些key-key:app.propertiespath:application.properties# key→文件名的映射-key:log4j.xmlpath:log4j2.xmldefaultMode:0644# 文件权限containers:-name:appimage:myapp:latestvolumeMounts:-name:app-configmountPath:/app/config# ConfigMap内容变成文件夹里的文件readOnly:true# 挂载后容器里看到的文件结构# /app/config/# ├── application.properties ← app.properties 的内容# └── log4j2.xml ← log4j.xml 的内容# 应用代码里直接读 /app/config/application.properties 即可Secret的挂载一模一样——把configMap换成secretvolumes:-name:tls-certssecret:secretName:tls-secretitems:-key:tls.crtpath:cert.pem-key:tls.keypath:key.pemdefaultMode:0600# 私钥权限必须是600containers:-name:appvolumeMounts:-name:tls-certsmountPath:/etc/ssl/certsreadOnly:true要点用Volume挂载ConfigMap/Secret有一个好处——热更新。ConfigMap/Secret内容改了之后kubelet会在一定时间内同步更新挂载的文件默认约60秒。而用环境变量注入的配置项则不会自动更新必须重启Pod。五、PV/PVC简介——“动态存储管理”前面讲的emptyDir和hostPath都有个致命问题Pod删了数据可能就没了emptyDir或者换Node了数据访问不到hostPath。生产环境需要的是Pod删了数据还在、Pod漂到哪都能访问的存储——这就是PersistentVolumePV和PersistentVolumeClaimPVC。【PV/PVC 概念——存储的预售模式】 K8s管理员 K8s用户你 ──────── ─────────── 创建 PV仓库里的存储单元 创建 PVC申请存储 ┌─────────────────┐ ┌─────────────────┐ │ PV-1: 100Gi │◄────────│ 我要10Gi存储 │ │ NFS:/exports/pv1 │ 绑定 │ PVC: myapp-data │ ├─────────────────┤ └─────────────────┘ │ PV-2: 50Gi │ │ │ AWS EBS: vol-xxx│ │ Pod引用PVC ├─────────────────┤ ▼ │ PV-3: 200Gi │ ┌─────────────────┐ │ Ceph RBD: img-1 │ │ Pod │ └─────────────────┘ │ volumes: │ │ - name: data │ PV 预先准备的存储资源 │ persistentVolumeClaim: PVC 用户的存储需求单 │ claimName: myapp-data └─────────────────┘PVC模式的优势在于关注点分离管理员管存储PV开发者只管用PVC双方不用互相了解对方的配置细节。要点本节只是PV/PVC的一个预告——Volume的本质是给Pod挂存储PV/PVC把这个概念提升到集群级别的存储抽象层。后续文章会有专门的PV/PVC深入篇包括StorageClass动态创建、访问模式RWO/ROX/RWX等。六、Volume的常用命令# 查看Pod的Volume挂载情况kubectl describe pod my-pod|grep-A20Volumes:kubectl describe pod my-pod|grep-A10Mounts:# 进入容器看挂载点kubectlexec-itmy-pod --df-hkubectlexec-itmy-pod --mount|grep/data# 在容器里写文件测试Volume是否工作kubectlexec-itmy-pod --touch/data/test.txt kubectlexec-itmy-pod-clog-reader --ls/logs# 另一个容器看共享Volume# 删除Pod看数据是否还在kubectl delete pod my-pod# 如果是emptyDir → 数据没了# 如果是hostPath → 数据在Node上# 如果是PV/PVC → 数据在存储后端本篇小结Volume让容器从临时工变成有产者核心价值容器文件系统是临时的Volume把数据放在容器外部——容器重启数据还在Pod删除才释放三种常用类型emptyDir临时共享Pod删除即清、hostPath绑定Node持久但绑定节点、NFS网络共享多Node可用subPath妙用精准替换单个文件而不是覆盖整个目录——配置文件注入标配ConfigMap/Secret挂载配置和密钥变成文件应用直接读支持热更新PV/PVC预告生产环境存储管理的基础PVC让你把存储当资源申请下一篇咱们聊ConfigMap——配置管理的正确打开方式。应用配置不再硬编码在镜像里支持热更新、多环境切换、版本管理。上一篇【第18篇】Ingress Controller选型和实战——Nginx Ingress完全指南下一篇【第20篇】ConfigMap——配置管理的正确姿势