K8s学习笔记(十二) volume存储卷
在 Kubernetes 中,Volume(存储卷) 是解决容器存储问题的核心组件。它的核心作用是:为 Pod 中的容器提供 “持久化” 或 “共享” 的存储空间,弥补容器文件系统的临时性(容器重启后数据丢失)和隔离性(同一 Pod 内容器无法直接共享文件)的缺陷。
1 为什么需要 Volume?
容器的文件系统是 “临时的、隔离的”,这会导致 3 个问题:
- 数据丢失:容器重启后,内部文件会被重置(比如 Nginx 容器重启,之前上传的静态文件会消失)。
- 跨容器共享:同一 Pod 内的多个容器(如 “应用容器 + 日志收集容器”)需要共享数据(如日志文件),但容器间默认无法直接访问对方的文件系统。
- 持久化存储:需要将数据长期保存(如数据库数据),即使 Pod 被删除,数据也不能丢失。
Volume 正是为解决这些问题而生:
- 它是Pod 级别的存储资源(与 Pod 生命周期绑定,Pod 删除后 Volume 才会被清理,除非是持久化存储)。
- 可以被 Pod 内的多个容器同时挂载,实现数据共享。
- 支持多种存储类型(本地磁盘、网络存储、配置文件等),灵活满足不同场景。
2 Volume 的核心概念(必懂)
- 生命周期:与 Pod 一致(Pod 创建时 Volume 被创建,Pod 删除时 Volume 被清理)。但注意:若 Volume 使用的是外部存储(如 NFS、PV),则底层数据会保留(仅 K8s 的 Volume 对象被删除)。
- 定义方式:
- 在 Pod 的
spec.volumes
中定义 “卷”(说明卷的类型、来源等)。 - 在容器的
spec.containers.volumeMounts
中定义 “挂载”(说明将卷挂载到容器内的哪个路径)。
- 在 Pod 的
- 核心关联:
volumes.name
与volumeMounts.name
必须一致,用于绑定卷和挂载点。
3 常见 Volume 类型
K8s 支持数十种 Volume 类型,这里按 “使用场景” 挑出最常用的 6 种,掌握它们就能应对 80% 的需求。
3.1 emptyDir:Pod 内临时共享存储(最基础)
- 特点:在 Pod 创建时自动创建的临时目录,存储在节点的本地磁盘(或内存),Pod 删除后数据丢失。
- 适用场景:同一 Pod 内容器间临时共享数据(如日志缓存、临时计算结果)。
示例:两个容器共享 emptyDir
创建一个 Pod,包含 “写文件容器” 和 “读文件容器”,通过 emptyDir 共享数据:
# pod-emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-emptyDir
spec:containers:- name: writer # 写文件的容器image: busybox:1.35command: ["sh", "-c", "while true; do echo $(date) > /shared/data.txt; sleep 5; done"] # 每5秒写当前时间到文件volumeMounts:- name: shared-vol # 挂载名为shared-vol的卷mountPath: /shared # 挂载到容器内的/shared目录- name: reader # 读文件的容器image: busybox:1.35command: ["sh", "-c", "while true; do cat /shared/data.txt; sleep 5; done"] # 每5秒读取文件volumeMounts:- name: shared-vol # 必须和volumes.name一致mountPath: /shared # 挂载到容器内的/shared目录volumes: # 定义卷- name: shared-vol # 卷的名字,与上面的volumeMounts.name对应emptyDir:medium: "" # 存储介质:默认磁盘;设为"Memory"则使用内存(tmpfs,速度快但容量受限于内存)
操作与验证:
# 创建Pod
kubectl apply -f pod-emptyDir.yaml# 查看reader容器的输出(会每隔5秒显示当前时间)
kubectl logs -f pod-emptyDir -c reader
# 输出示例:
# Fri Oct 2 15:30:00 UTC 2025
# Fri Oct 2 15:30:05 UTC 2025# 验证数据共享:进入writer容器,查看/shared目录
kubectl exec -it pod-emptyDir -c writer -- sh
ls /shared # 会看到data.txt,内容与reader输出一致
3.2 hostPath:挂载节点本地目录(测试用)
- 特点:将 Pod 所在节点的本地文件或目录挂载到容器内,数据会保留在节点上(Pod 删除后数据不丢,但 Pod 调度到其他节点时无法访问)。
- 适用场景:测试环境中需要持久化数据(如本地数据库测试),或需要访问节点文件(如挂载节点的
/var/log
收集日志)。
示例:挂载节点的/opt/hostpath-data
目录
# pod-hostPath.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-hostPath
spec:containers:- name: test-containerimage: busybox:1.35command: ["sh", "-c", "while true; do echo $(date) >> /data/hostpath.log; sleep 10; done"]volumeMounts:- name: hostpath-volmountPath: /data # 容器内的/data目录映射到节点的/opt/hostpath-datavolumes:- name: hostpath-volhostPath:path: /opt/hostpath-data # 节点上的目录type: DirectoryOrCreate # 目录不存在则自动创建(支持File、Directory等类型)
操作与验证:
# 创建Pod
kubectl apply -f pod-hostPath.yaml# 找到Pod所在节点
kubectl get pod pod-hostPath -o wide
# 输出示例(NODE字段为节点名):
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
# pod-hostPath 1/1 Running 0 1m 10.200.166.182 192.168.121.111 <none># 登录节点k8s-node-1,查看/opt/hostpath-data目录
ssh node1
cat /opt/hostpath-data/hostpath.log # 会看到持续追加的时间日志(证明数据持久化到节点)
注意:hostPath 的局限性 —— 多节点集群中,若 Pod 调度到其他节点,会访问新节点的
/opt/hostpath-data
(数据不共享),生产环境慎用!
3.3 nfs:跨节点共享的网络存储
- 特点:挂载 NFS 服务器的共享目录,支持多节点 Pod 共享数据(无论 Pod 调度到哪个节点,都能访问同一 NFS 目录)。
- 适用场景:需要跨节点共享数据的场景(如多 Pod 写日志、静态资源共享)。
部署 NFS 服务器
NFS 服务器 IP 为192.168.121.109
,共享目录为/data/k8sdata/chenjun666
(需配置允许 K8s 节点访问)。
apt install nfs-servermkdit -p /data/k8sdata/chenjun666vim /etc/export
/data/k8sdata *(rw,no_root_squash)systemctl restart nfs-server.servicesystemctl enable nfs-server.servicesystemctl status nfs-server.service# 检查挂载
root@master1:~/yaml/volume_pod# showmount -e 192.168.121.109
Export list for 192.168.121.109:
/data/k8sdata *
示例:Pod 挂载 NFS 共享目录
# pod-nfs.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-nfs
spec:containers:- name: nfs-containerimage: busybox:1.35command: ["sh", "-c", "while true; do echo $(date) >> /nfs/data.log; sleep 10; done"]volumeMounts:- name: nfs-volmountPath: /nfs # 容器内挂载点volumes:- name: nfs-volnfs:server: 192.168.121.109 # NFS服务器IPpath: /data/k8sdata/chenjun666 # NFS共享目录
验证:
在 NFS 服务器上查看/nfs/shared/data.log
,会看到 Pod 持续写入的日志(即使 Pod 调度到其他节点,数据仍会写入同一文件)。
3.4 configMap:挂载配置文件(非敏感配置)
- 特点:将 ConfigMap(K8s 的配置资源)中的键值对或文件,以文件形式挂载到容器内,方便配置管理(无需修改镜像即可更新配置)。
- 适用场景:应用的配置文件(如 Nginx 的
nginx.conf
、应用的app.properties
)。
步骤 1:创建 ConfigMap(配置源)
# configmap-demo.yaml
apiVersion: v1
kind: ConfigMap
metadata:name: app-config
data:# 键值对形式(会被挂载为文件,文件名=键,内容=值)app.properties: |env=productionlog_level=info# 直接定义文件内容(如nginx.conf)nginx.conf: |server {listen 80;root /usr/share/nginx/html;}
kubectl apply -f configmap-demo.yaml
步骤 2:Pod 挂载 ConfigMap
# pod-configmap.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-configmap
spec:containers:- name: nginximage: nginx:alpinevolumeMounts:- name: config-vol # 卷名mountPath: /etc/config # 挂载到容器内的/etc/config目录readOnly: true # 配置文件通常设为只读volumes:- name: config-volconfigMap:name: app-config # 关联的ConfigMap名称# 可选:只挂载部分键(默认挂载所有)# items:# - key: nginx.conf# path: my-nginx.conf # 重命名为my-nginx.conf
验证:
kubectl apply -f pod-configmap.yaml# 进入容器,查看挂载的配置文件
kubectl exec -it pod-configmap -- sh
ls /etc/config # 会看到app.properties和nginx.conf
cat /etc/config/app.properties # 输出配置内容
注意:ConfigMap 更新后,挂载的文件会在几秒内自动同步(无需重启 Pod)。
3.5 secret:挂载敏感信息(密码、证书)
- 特点:与 ConfigMap 类似,但用于存储敏感信息(密码、Token、证书),数据会被 Base64 编码(非加密,需配合 RBAC 控制访问)。
- 适用场景:数据库密码、API 密钥、TLS 证书等。
步骤 1:创建 Secret(敏感信息)
# secret-demo.yaml
apiVersion: v1
kind: Secret
metadata:name: app-secret
type: Opaque # 通用类型
data:# 注意:值必须是Base64编码(echo -n "mypass123" | base64 生成)db-password: bXlwYXNzMTIz # 原始值:mypass123tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tC... # 证书内容的Base64编码
kubectl apply -f secret-demo.yaml
步骤 2:Pod 挂载 Secret
# pod-secret.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-secret
spec:containers:- name: appimage: busybox:1.35command: ["sleep", "3600"]volumeMounts:- name: secret-volmountPath: /etc/secret # 挂载到容器内的/etc/secretreadOnly: true # 敏感信息通常只读volumes:- name: secret-volsecret:secretName: app-secret # 关联的Secret名称
验证:
kubectl apply -f pod-secret.yaml# 进入容器,查看解密后的敏感信息
kubectl exec -it pod-secret -- sh
cat /etc/secret/db-password # 输出:mypass123(自动解码Base64)
4 pv/pvc
在 Kubernetes 中,PV(PersistentVolume,持久卷) 和 PVC(PersistentVolumeClaim,持久卷声明) 是解决 “持久化存储” 问题的核心机制,核心目标是解耦存储资源的 “供应” 和 “使用”—— 让运维人员负责提供存储(PV),开发人员只需声明存储需求(PVC),无需关心底层存储细节。
4.1 一句话理清关系
- PV:相当于 “提前准备好的仓库”(由运维创建),是集群中的存储资源实体(比如一块云硬盘、一个 NFS 目录),属于 “集群级资源”(不绑定命名空间)。
- PVC:相当于 “仓库使用申请单”(由开发创建),是 Pod 对存储的需求声明(比如 “要 10GB 空间、可读写”),属于 “命名空间级资源”(和 Pod 在同一命名空间)。
- 绑定:PVC 会自动匹配满足条件的 PV(容量、访问模式等一致),绑定后一对一专属使用,其他 PVC 无法占用。
4.2 PV:运维视角的 “存储资源包”
PV 是对底层存储(如本地磁盘、NFS、云硬盘)的 “封装”,每个 PV 都对应一块实际存储。创建时必须明确其核心属性,这些属性直接决定能否被 PVC 匹配。
4.2.1 PV 核心属性(绑定的关键)
属性 | 作用 | 重点值 / 示例 |
---|---|---|
capacity | 存储容量(最基础的匹配条件) | storage: 10Gi (支持 Gi、Ti) |
accessModes | 访问模式(决定 PV 能被如何挂载,核心匹配条件) | 3 种模式(下文详解) |
persistentVolumeReclaimPolicy | 回收策略(PV 释放后如何处理数据,防数据泄露) | Retain (保留数据)、Delete (删除) |
storageClassName | 存储类(关联 StorageClass,用于动态供应;空则为 “裸 PV”) | fast (自定义存储类)或空 |
claimRef | 已绑定的 PVC(自动生成,无需手动设置) | - |
关键:accessModes
(访问模式)
决定 PV 的 “共享能力”,PVC 的访问模式必须是 PV 的子集(比如 PV 支持RWO
,PVC 只能请求RWO
)。
模式 | 含义(核心!) | 适用场景 | 支持存储类型举例 |
---|---|---|---|
ReadWriteOnce (RWO) | 只允许1 个节点以 “读写” 挂载(同一节点的多个 Pod 可共享) | 单实例应用(MySQL、Redis 主节点) | 云硬盘(EBS)、本地磁盘 |
ReadOnlyMany (ROX) | 允许多个节点以 “只读” 挂载 | 多节点共享静态资源(如图片) | NFS、GlusterFS |
ReadWriteMany (RWX) | 允许多个节点以 “读写” 挂载(最灵活,但支持的存储少) | 分布式应用(Hadoop、多 Pod 写日志) | NFS、CephFS |
注意:
RWO
的 “One” 指 “一个节点”,不是 “一个 Pod”—— 同一节点的多个 Pod 可以共享挂载。
4.2.2 静态供应 PV 示例(手动创建)
用hostPath
(仅测试用,生产不推荐)创建 PV:
# pv-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:name: pv-demo
spec:capacity:storage: 10Gi # 容量10GBaccessModes:- ReadWriteOnce # 单节点读写persistentVolumeReclaimPolicy: Retain # 释放后保留数据storageClassName: "" # 不关联存储类(裸PV)hostPath: # 实际存储是节点的/opt/pv-data目录path: /opt/pv-datatype: DirectoryOrCreate # 目录不存在则自动创建
操作:
kubectl apply -f pv-demo.yaml
kubectl get pv pv-demo # 状态为Available(等待PVC绑定)
4.3 PVC:开发视角的 “存储需求单”
PVC 是对存储的 “需求声明”,开发只需定义 “要什么”(容量、访问模式等),K8s 会自动匹配合适的 PV。
4.3.1 PVC 核心属性(匹配 PV 的条件)
属性 | 作用 | 示例 |
---|---|---|
resources.requests | 请求的容量(必须≤PV 的 capacity) | storage: 10Gi |
accessModes | 请求的访问模式(必须是 PV 访问模式的子集) | - ReadWriteOnce |
storageClassName | 请求的存储类(必须和 PV 的一致;空则匹配 “裸 PV”) | fast (匹配对应存储类的 PV) |
4.3.2 PVC 绑定 PV 示例
创建 PVC 匹配上面的pv-demo
:
# pvc-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: pvc-demonamespace: default # 必须和Pod同命名空间
spec:accessModes:- ReadWriteOnce # 和PV的访问模式一致resources:requests:storage: 10Gi # 和PV的容量一致storageClassName: "" # 和PV的存储类一致(空)
操作:
kubectl apply -f pvc-demo.yaml
kubectl get pvc pvc-demo # 状态变为Bound(绑定成功)
kubectl get pv pv-demo # PV状态也变为Bound,CLAIM列显示绑定的PVC
4.3.3 Pod 如何使用 PVC?
Pod 通过volumes
引用 PVC,将 PV 挂载到容器内:
# pod-using-pvc.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-using-pvcnamespace: default
spec:containers:- name: nginximage: nginx:alpinevolumeMounts:- name: data-vol # 卷名(和下面volumes.name对应)mountPath: /usr/share/nginx/html # PV挂载到容器内的目录volumes:- name: data-volpersistentVolumeClaim:claimName: pvc-demo # 引用的PVC名称
验证:在 Pod 内创建文件,会同步到 PV 对应的节点目录(/opt/pv-data
)。
4.4 动态供应:用 StorageClass 自动创建 PV
静态供应(手动创建 PV)适合少量存储,生产环境更推荐动态供应—— 通过 StorageClass 自动创建 PV,无需运维手动干预。
4.4.1 StorageClass 的作用
- 作为 “PV 模板”:定义存储类型(如 NFS、云盘)、访问模式、回收策略等。
- 动态创建 PV:当 PVC 请求某个 StorageClass 时,K8s 自动调用存储插件(如 NFS-Provisioner)创建 PV 并绑定。
4.4.2 动态供应示例(NFS 为例)
假设已部署 NFS 服务器(192.168.1.100:/nfs/data
)和 NFS 插件(nfs-subdir-external-provisioner
)。
创建 StorageClass:
# storageclass-nfs.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: nfs-sc
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # NFS插件的provisioner
parameters:archiveOnDelete: "true" # 删除PV时归档数据
reclaimPolicy: Delete # 动态PV的回收策略
allowVolumeExpansion: true # 允许PVC扩容
创建 PVC 引用 StorageClass:
# pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: pvc-nfs
spec:accessModes:- ReadWriteMany # NFS支持RWXresources:requests:storage: 5GistorageClassName: nfs-sc # 引用上面的StorageClass
操作:
kubectl apply -f storageclass-nfs.yaml
kubectl apply -f pvc-nfs.yaml
kubectl get pvc pvc-nfs # 会快速Bound(自动创建PV)
kubectl get pv # 能看到一个自动生成的PV(名称类似pvc-xxx)
4.5 PV/PVC 生命周期(从创建到释放)
- 供应:静态(手动创 PV)或动态(StorageClass 自动创 PV),状态为
Available
。 - 绑定:PVC 匹配到 PV 后,二者状态变为
Bound
(一对一绑定)。 - 使用:Pod 引用 PVC 挂载 PV,数据写入底层存储。
- 释放:删除 PVC 后,PV 状态变为Released,数据处理由reclaimPolicy决定:
Retain
:保留数据,PV 需手动清理后才能复用。Delete
:自动删除 PV 和底层存储(慎用,数据会丢失)。
4.6 常见问题与排错
- PVC 一直 Pending:
- 原因:无匹配的 PV(容量不足、访问模式不兼容、存储类不匹配)。
- 排查:
kubectl describe pvc 你的PVC名
,看 Events(如 “no PV matches access modes [RWX]”)。
- PV 绑定后无法使用:
- 原因:底层存储不可用(如 NFS 服务器宕机、云盘未挂载)。
- 排查:检查存储后端状态,或查看 Pod 事件(
kubectl describe pod 你的Pod名
)。
- PVC 扩容失败:
- 前提:StorageClass 开启
allowVolumeExpansion: true
,且底层存储支持扩容。 - 操作:编辑 PVC 的
resources.requests.storage
(如从 10Gi 改为 20Gi)。
- 前提:StorageClass 开启
总结
- 核心分工:运维管 PV/StorageClass(供应存储),开发管 PVC/Pod(使用存储)。
- 绑定关键:容量、访问模式、存储类必须匹配。
5 Volume vs 容器文件系统 vs PV/PVC(关键区别)
对比项 | 容器文件系统 | Volume(如 emptyDir、hostPath) | PV/PVC(通过 persistentVolumeClaim 类型) |
---|---|---|---|
生命周期 | 与容器一致(容器重启丢失) | 与 Pod 一致(Pod 删除后清理) | 独立于 Pod(PV 由集群管理,数据长期保留) |
数据持久化能力 | 无 | 有限(hostPath 仅节点内持久化) | 强(依赖外部存储,如 NFS、云盘) |
跨节点共享 | 不支持 | hostPath 不支持,nfs 支持 | 支持(取决于 PV 的存储类型) |
适用场景 | 临时缓存(无需持久化) | 同一 Pod 内共享、节点级临时持久化 | 生产环境持久化存储(数据库、业务数据) |
6 常见问题与注意事项
- Volume 挂载后权限问题:容器内挂载目录的权限可能与预期不符(如 root 权限),可通过
volumeMounts.readOnly: true
设为只读,或在 Pod 的securityContext
中调整用户 ID(runAsUser
)。 - emptyDir 占满磁盘:emptyDir 默认使用节点磁盘,若写入大量数据可能占满磁盘,建议重要数据用 PV 或 NFS,临时数据可设
medium: Memory
(但受限于内存大小)。 - ConfigMap/Secret 更新延迟:更新后通常 10 秒内同步到容器,但如果是 “子路径挂载”(
subPath
),则不会自动同步(需重启 Pod)。
7 总结
- 临时共享数据(同一 Pod 内)→ emptyDir
- 节点级测试持久化 → hostPath
- 跨节点共享数据 → nfs
- 非敏感配置文件 → configMap
- 敏感信息 → secret
- 生产环境持久化存储 → persistentVolumeClaim(关联 PV)