6、持久化存储
六、持久化存储
1. Volume概述
Container(容器)中的磁盘文件是短暂的,当容器崩溃时,kubelet 会重新启动容器,但最初的文件将丢失,Container 会以最干净的状态启动。另外,当一个 Pod 运行多个 Container 时,各个容器可能需要共享一些文件。Kubernetes Volume 可以解决这两个问题。
Kubernetes 卷具有明确的生命周期,与使用它的 Pod 相同。因此,在 Kubernetes中的卷可以比 Pod 中运行的任何 Container 都长,并且可以在 Container 重启或者销毁之后保留数据。Kubernetes 支持多种类型的卷,Pod 可以同时使用任意数量的卷。
从本质上讲,卷只是一个目录,可能包含一些数据,Pod 中的容器可以访问它。要使用卷 Pod需要通过.spec.volumes 字段指定为 Pod 提供的卷,以及使用.spec.containers.volumeMounts 字段指定卷挂载的目录。从容器中的进程可以看到由 Docker 镜像和卷组成的文件系统视图,卷无法挂载其他卷或具有到其他卷的硬链接,Pod中的每个Container必须独立指定每个卷的挂载位置。
2. Volume类型
在传统架构中,企业内可能有自己的存储平台,比如NFS、Ceph、GlusterFS、Minio等。如果环境在公有云,可以使用公有云提供的NAS、对象存储等。在Kubernetes中,Volume也支持配置以上存储,用于挂载到Pod中实现数据的持久化。Kubernetes Volume 支持的卷的类型有很多,以下为常用的卷:
- CephFS
- GlusterFS
- ISCSI
- Cinder
- NFS
- RBD
- HostPath
当然,也支持一些Kubernetes独有的类型:
- ConfigMap:用于存储配置文件
- Secret:用于存储敏感数据
- EmptyDir:用于一个Pod内多个容器的数据共享
- PersistentVolumeClaim:对PersistentVolume的申请
3. 通过emptyDir共享数据
emptyDir 是一个特殊的 Volume 类型,如果删除 Pod,emptyDir 卷中的数据也将被删除,所以一般 emptyDir 用于Pod中的不同 Container 共享数据,比如一个 Pod 存在两个容器A和B,容器A需要使用容器B产生的数据,此时可以采用 emptyDir 共享数据,类似的使用如 Filebeat 收集容器内程序产生的日志。
默认情况下,emptyDir 卷支持节点上的任何介质,可能是 SSD、磁盘或网络存储,具体取 决于自身的环境!
定义一个deployment,配置emptyDir实现共享数据。
[root@k8s-master01 volumes]# cat test-emptydir.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: empty
name: deploy-emptydir
spec:
replicas: 1
selector:
matchLabels:
app: empty
template:
metadata:
labels:
app: empty
spec:
containers:
- name: test01
image: registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools
command:
- sh
- -c
- sleep 3600
volumeMounts:
- name: share-volume
mountPath: /dir01
- name: test02
image: registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools
command:
- sh
- -c
- sleep 3600
volumeMounts:
- name: share-volume
mountPath: /dir02
volumes:
- name: share-volume
emptyDir: {}
#medium: Memory 指定存储介质为内存(RAM),开启这个就需要将上面{}删除
4. 使用HostPath挂载宿主机文件
hostPath 卷可将节点上的文件或目录挂载到 Pod 上,用于 Pod 自定义日志输出或访问 Docker 内部的容器等。
hostPath常用类型有如下:
值 | 行为 |
---|---|
默认选项,意味着挂载 hostPath 卷之前不会执行任何检查 | |
DirectoryOrCreate | 如果给定的 path 不存在任何东西,那么将根据需要创建一个权限为 0755 的空目录,和 Kubelet 具有相同的组和权限 |
Directory | 目录必须存在于给定的路径下 |
FileOrCreate | 如果给定的路径不存储任何内容,则会根据需要创建一个空文件,权限设 置为 0644,和 Kubelet 具有相同的组和所有权。 |
File | 文件必须存在于给定路径中 |
Socket | 在给定路径上必须存在的 UNIX 套接字 |
CharDevice | 在给定路径上必须存在的字符设备 |
BlockDevice | 在给定路径上必须存在的块设备 |
Tips:下层主机上创建的文件或目录只能由 root 用户写入。 你需要在特权容器中以 root 身份运行进程,或者修改主机上的文件权限以便容器能够写入 hostPath 卷。
定义一个deployment,配置HostPath实现挂载宿主机文件。
[root@k8s-master01 volumes]# cat test-hostpath.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: hostpath
name: deploy-hostpath
spec:
replicas: 1
selector:
matchLabels:
app: hostpath
template:
metadata:
labels:
app: hostpath
spec:
containers:
- name: test01
image: registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools
command:
- sh
- -c
- sleep 3600
volumeMounts:
- name: share-volume
mountPath: /test01/test
- name: test02
image: registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools
command:
- sh
- -c
- sleep 3600
volumeMounts:
- name: share-volume
mountPath: /test02/test
volumes:
- name: share-volume
hostPath:
path: /etc/ssh/sshd_config
type: File
创建deployment资源,进入Pod查看挂载目录,并查看宿主机文件是否共享,下图测试无问题。

Tips:hostPath挂载类型会将宿主机上的指定路径挂载到Pod中,以供Pod中的应用程序访问。在Pod中修改挂载的文件只会影响到Pod内部的文件内容,并不会直接修改宿主机上的文件。
5.通过nfs挂载至Pod
在生产环境中,考虑到数据的高可用性,仍然不建议使用NFS作为后端存储。因为NFS存在单点故障,很多企业并非对其实现高可用,并且NFS的性能存在很大的瓶颈。所以生产中,建议使用分布式存储,在公有云中建议使用公有云提供的NAS存储来替代NFS,并且NAS性能更好,可用性更高。NFS作为一个比较流行的远端存储工具,在非生产环境中是一个比较好用的选择,可以快速地搭建使用测试。
安装NFS
yum install -y nfs-utils
Tips:Kubernetes所有的节点都需要安装上nfs-utils才可以正常挂载NFS。
Master01上配置NFS服务端进行测试
#以下都在Master01节点上进行
systemctl start nfs
systemctl enable nfs
#修改配置文件
[root@k8s-master01 ~]# cat /etc/exports
/nfsdata 10.0.0.0/24(rw,sync,no_subtree_check,no_root_squash)
#重新加载配置
systemctl reload nfs
#创建共享目录
mkdir -p /nfsdata
定义一个deployment,配置nfs实现网络挂载。
[root@k8s-master01 volumes]# cat deploy-nfs.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nfs
name: deploy-nfs
spec:
replicas: 1
selector:
matchLabels:
app: nfs
template:
metadata:
labels:
app: nfs
spec:
containers:
- name: test01
image: registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools
command:
- sh
- -c
- sleep 3600
volumeMounts:
- name: nfs-volume
mountPath: /nfs01
- name: test02
image: registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools
command:
- sh
- -c
- sleep 3600
volumeMounts:
- name: nfs-volume
mountPath: /nfs02
volumes:
- name: nfs-volume
nfs:
server: 10.0.0.104
path: /nfsdata
创建deployment资源,进入Pod中test01容器查看挂载情况,并写一个测试文件,进入另外一个test02容器中同样查看挂载情况,再看刚从创建的测试文件,下图测试无问题。
6. PV和PVC
6.1 Volume出现实际的问题
虽然Volume已经可以接入大部分存储后端,但是实际使用时还有诸多问题,比如:
- 当某个数据卷不再被挂载使用时,里面的数据如何处理?
- 如果想要实现只读挂载如何处理?
- 如果想要只能有一个Pod挂载如何处理?
- 如果只允许某个Pod使用10G的空间?
如上所述,对于很多复杂的需求Volume可能难以实现,并且无法对存储卷的生命周期进行管理。
6.2 PV、PVC概述
PV(持久卷)
PersistentVolume:简称PV,是由Kubernetes管理员设置的存储,可以配置Ceph、NFS、GlusterFS等常用存储配置,相对于Volume配置,提供了更多的功能,比如独立生命周期的管理、大小的限制。PV分为静态和动态。静态PV由管理员提前创建,动态PV无须提前创建。
Tips:PV没有命名空间限制!
PVC(持久卷申领)
PersistentVolumeClaim:简称PVC,是对存储PV的请求,表示需要什么类型的PV,需要存储的技术人员只需要配置PVC即可使用存储,或者Volume配置PVC的名称即可。 PVC 申领会耗用 PV 资源,可以设置请求特定的大小和访问模式。
Tips:PVC有命名空间限制!
6.3 PV回收策略
当用户使用完卷时,可以从API中删除PVC对象,从而允许回收资源。回收策略会告诉PV如何处理该卷。目前回收策略可以设置为Retain、Recycle和Delete:
-
Retain:保留,该策略允许手动回收资源,当删除PVC时,PV仍然存在,Volume被视为已释放,管理员可以手动回收卷。
-
Recycle:回收,如果Volume插件支持,Recycle策略会对卷执行rm -rf清理该PV,并使其可用于下一个新的PVC,但是本策略将来会被弃用,目前只有NFS和HostPath支持该策略。
-
Delete:删除,如果Volume插件支持,删除PVC时会同时删除PV,动态卷默认为Delete,目前支持Delete的存储后端包括AWS EBS、GCE PD、Azure Disk、OpenStack Cinder等。
6.4 PV访问策略
在实际使用PV时,可能针对不同的应用会有不同的访问策略,比如某类Pod可以读写,某类Pod只能读,或者需要配置是否可以被多个不同的Pod同时读写等,此时可以使用PV的访问策略进行简单控制,目前支持的访问策略如下:
- ReadWriteOnce:可以被单节点以读写模式挂载,命令行中可以被缩写为RWO。
- ReadOnlyMany:可以被多个节点以只读模式挂载,命令行中可以被缩写为ROX。
- ReadWriteMany:可以被多个节点以读写模式挂载,命令中可以被缩写为RWX。
- ReadWriteOncePod:只能被一个Pod以读写的模式挂载,命令行中可以被缩写为RWOP(1.22以上版本)。
Tips:一般情况下大部分块存储是不支持ReadWriteMany的,具体后端存储支持的访问模式可以参考官方文档:
https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes。
6.5 PV的状态
我们可以使用kubectl get pv
命令查看PV状态
常见的状态如下:
- Available 可用,没有被PVC绑定的空闲资源。
- Bound 已绑定,已经被PVC绑定。
- Released 已释放,PVC被删除,但是资源还未被重新使用。
- Failed 失败,自动回收失败。
6.6 创建NAS/NFS的PV
所有节点都需要安装NFS客户端的命令
yum install -y nfs-utils
Tips:Kubernetes所有的节点都需要安装上nfs-utils才可以正常挂载NFS。
找一台节点充当NFS服务端,这里就用Node01节点。
#启动nfs,就是启动服务端
[root@k8s-node01 ~]# systemctl start nfs
[root@k8s-node01 ~]# systemctl enable nfs
#创建NFS共享目录
[root@k8s-node01 ~]# mkdir -p /data/k8s
#修改NFS配置文件
[root@k8s-node01 ~]# cat /etc/exports
/data/k8s/ *(rw,sync,no_subtree_check,no_root_squash)
#重新加载NFS配置
[root@k8s-node01 ~]# systemctl reload nfs
创建一个临时目录进行挂载测试
#创建临时目录并挂载
[root@k8s-node01 ~]# mkdir /test
[root@k8s-node01 ~]# mount -t nfs 10.0.0.107:/data/k8s /test
#查看挂载情况
[root@k8s-node01 ~]# df -h | tail -n 1
10.0.0.107:/data/k8s 100G 8.0G 93G 8% /test
#创建测试文件并查看
[root@k8s-node01 ~]# echo 123 > /test/123.txt
[root@k8s-node01 ~]# cat /data/k8s/123.txt
123
#删除测试文件并取消挂载,删除测试目录
[root@k8s-node01 ~]# rm -rf /test/123.txt
[root@k8s-node01 ~]# umount /test
[root@k8s-node01 ~]# rm -rf /test
创建一个PV的yaml文件
[root@k8s-master01 volumes]# cat pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: nfs-zzb
nfs:
path: /data/k8s
server: 10.0.0.107
- capacity:容量配置
- volumeMode:卷的模式,目前支持Filesystem (文件系统) 和 Block(块),其中Block类型需 要后端存储支持,默认为文件系统。
- accessModes:该PV的访问模式,这里设置为RWO,可以被单节点以读写模式挂载。
- persistentVolumeReclaimPolicy:回收策略
- storageClassName:PV的类,一个特定类型的PV 只能绑定到特定类别的PVC。
- nfs:NFS服务配置,包括以下两个选项:NFS上的共享目录和NFS的IP地址
创建PV并查看
6.7 创建HostPath的PV
当公司目前没有可靠性的存储,但是想要部署k8s集群中应用的数据不丢失,这时把宿主机目录直接挂载到Pod,Pod的数据直接落在宿主机上。
创建测试目录
mkdir -p /mnt/data
创建一个PV的yaml文件
[root@k8s-master01 volumes]# cat pv-hostpath.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: hostpath-zzb
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
创建PV并查看
6.8 创建PVC并绑定PV
PVC是其他技术人员在Kubernetes上对存储的申请,它可以标明一个程序需要用到什么样的后端存储、多大的空间以及以什么访问模式进行挂载。
在实际使用时,虽然用户通过PVC获取存储支持,但是用户可能需要具有不同性质的PV来解决不同的问题,比如使用SSD硬盘来提高性能。所以集群管理员需要根据不同的存储后端来提供各种PV,而不仅仅是大小和访问模式的区别,并且无须让用户了解这些卷的具体实现方式和存储类型,达到了存储的解藕,降低了存储使用的复杂度。
以下是刚才已经创建的PV
[root@k8s-master01 volumes]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-hostpath 10Gi RWO Retain Available hostpath-yinjay 99m
pv-nfs 5Gi RWO Recycle Available nfs-yinjay 111m
[root@k8s-master01 volumes]# kubectl get pv pv-nfs -oyaml
apiVersion: v1
kind: PersistentVolume
metadata:
creationTimestamp: "2023-07-29T07:06:37Z"
finalizers:
- kubernetes.io/pv-protection
name: pv-nfs
resourceVersion: "1022980"
uid: 79232fd9-d2d3-4201-b386-a25b492f2bff
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 5Gi
nfs:
path: /data/k8s
server: 10.0.0.107
persistentVolumeReclaimPolicy: Recycle
storageClassName: nfs-zzb
volumeMode: Filesystem
status:
phase: Available
定义一个名为pvc-nfs的yaml文件
[root@k8s-master01 volumes]# cat pvc-nfs.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nfs-pvc
spec:
storageClassName: nfs-zzb #要求与PV定义的storageClassName一致
volumeMode: Filesystem #与PV配置一致
accessModes: #与PV配置一致
- ReadWriteOnce
resources:
requests:
storage: 3Gi #小于等于PV大小
创建PVC并绑定PV,结果验证,观察到状态已更改为Bound,但此时并不代表可用。
定义一个名为pvc-nfs-dp的yaml文件
[root@k8s-master01 volumes]# cat pvc-nfs-dp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: deploy-nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
volumes:
- name: nfs-storage #自定义volume资源名称
persistentVolumeClaim:
claimName: nfs-pvc #pvc资源名称
containers:
- name: deploy-nginx
image: nginx:1.14.2
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: nfs-storage #上面定义的volume资源名称
创建该deployment资源,进入某个Pod查看挂载情况。创建新的文件,到另外一个Pod底下查看是否共享。
6.9 PVC创建和挂载失败的原因
PVC一直Pending的原因
- PVC的空间申请大小大于PV的大小
- PVC的 StorageClassName 没有和PV的一致
- PVC的 accessModes 和PV的不一致
挂载PVC的Pod一直处于Pending
- PVC没有创建成功/PVC不存在
- PVC和Pod不在同一个 Namespace
注:本篇学习笔记内容参考杜宽的《云原生Kubernetes全栈架构师》,视频、资料文档等,大家可以多多支持!还有YinJayChen语雀、k8s训练营、“我为什么这么菜”知乎博主等资料文档,感谢无私奉献!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了