目录
一,Kubernetes存储概念
1,volume的概念
2,volume的类型
二,配置volume存储
1,通过emprydir共享数据
2,使用hostpath挂载宿主机文件
3,使用nfs挂载至容器
三,配置pv持久卷
1,pv回收策略
2,pv访问策略
3,pv的配置方式
4,PersistentVolumeclaim(Pvc,持久卷声明)
5,创建基于hostpath的pv
6,创建基于nfs的pv
在生产环境中,应用程序经常需要持久化存储数据,如用户上传的文件、数据库记录等。然而,在Kubernetes 中,由于容器本身具有高度的可扩展性和编排能力,传统的数据存储方式已无法满足需求。因此,Kubernetes 抽象出了 Volume 的概念,用于解决容器的数据存储问题。
Volume 在 Kubernetes 中扮演着至关重要的角色,它允许容器在重启或迁移时保持数据的持久性。本章将详细介绍 Volume 的概念、类型以及使用方法。从 emptyDir 的临时存储,到 HostPath 的宿主机文件挂载,再到 NFS 网络存储的共享,以及 PersistentVolume 和PersistentVolumeclaim 的动态存储管理。
对于大多数的项目而言,数据文件的存储是非常常见的需求,比如存储用户上传的头像、文件以及数据库的数据。在 Kubernetes 中,由于应用的部署具有高度的可扩展性和编排能力(不像传统架构部署在固定的位置),因此把数据存放在容器中是非常不可取的,这样也无法保障数据的安全。
在传统的架构中,如果要使用这些存储,需要提前在宿主机挂载,然后程序才能访问,在实际使用时,经常碰到新加节点忘记挂载存储导致的一系列问题。而 Kubernetes 在设计之初就考虑了这些问题,并抽象出 Volume 的概念用于解决数据存储的问题。
在容器中的磁盘文件是短暂的,当容器崩溃时,Kubect1 会重新启动容器,但是容器运行时产生的数据文件都会丢失,之后容器会以干净的状态启动。另外,当一个 Pod 运行多个容器时,各个容器可能需要共享一些文件,诸如此类的需求都可以使用 Volume 解决。
在传统架构中,企业内可能有自己的存储平台,比如NFS、Ceph、GlusterFs、Minio 等。如果所在的环境在公有云,也可以使用公有云提供的 NAS、对象存储等。在 Kubernetes 中,Volume 也支持配置这些存储,用于挂载到 Pod 中实现数据的持久化。Kubernetes Volume 文持的卷的类型有很多。
emptyDir:emptyDir 是一种临时存储卷,当 Pod 被调度到节点时创建,初始为空,当 Pod 从节点移除时,数据会被永久删除。
hostPath:hostPath 卷将节点的文件系统中的文件或目录挂载到 Pod 中,数据会保存在节点的文件系统上,当 Pod 被删除后,数据仍然保留在节点上。
nfs:nfs 卷允许将 NFS(网络文件系统)共享挂载到 Pod 中,多个 Pod 可以共享同一个 NFS 存储,适合需要跨节点共享数据的场景。
configmap:用于存储配置文件
secret:用于存储敏感数据
PersistentVolumeclaim:对PersistentVolume的申请
emptyDir 是一个特殊的 Volume 类型,与上述 Volume 不同的是,如果删除Pod,EmptyDir 卷中的数据也将被删除,所以一般 emptyDir 用于 Pod 中不同容器共享数据,比如一个 Pod 存在两个容器A和容器 B,容器A需要使用容器B产生的数据,此时可以采用 emptyir 共享数据,类似的使用如 Filebeat收集容器内程序产生的日志。
编写emprydir的deployment文件
[root@k8s-master ~]# vim nginx-empty.yaml
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent
name: nginx01
volumeMounts: ##这三行定义nginx01挂载的目录
- mountPath: /opt
name: share-volume
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent
name: nginx02
command:
- sh
- -c
- sleep 3600
volumeMounts: ##这三行定义nginx01挂载的目录
- mountPath: /mnt
name: share-volume
volumes: ##类型为emprydir的卷类型
- name: share-volume ##此处名字要和上面一样
emptyDir: {}
#medium: Memory
创建该deployment
[root@k8s-master ~]# ku create -f nginx-empty.yaml
deployment.apps/nginx created
查看创建的结果
[root@k8s-master ~]# ku get pod
NAME READY STATUS RESTARTS AGE
nginx-8f8dcfd8c-sc6xf 2/2 Running 0 4s
登陆到第一个容器创建文件
[root@k8s-master ~]# ku exec -it nginx-8f8dcfd8c-sc6xf -c nginx01 -- bash
root@nginx-8f8dcfd8c-sc6xf:/# touch /opt/aaa
root@nginx-8f8dcfd8c-sc6xf:/# touch /opt/bbb
登陆到第二个容器查看
[root@k8s-master ~]# ku exec -it nginx-8f8dcfd8c-sc6xf -c nginx02 -- bash
root@nginx-8f8dcfd8c-sc6xf:/# ls /mnt/
aaa bbb
HostPath 卷可以将节点上的文件或目录挂载到 Pod 上,用于实现 Pod 和宿主机之间的数据共享,常用的示例有挂载宿主机的时区至 Pod,或者将 Pod 的日志文件挂载到宿主机等。
编写deployment文件,实现hostpath挂载
[root@k8s-master ~]# vim nginx-hostPath.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent
name: nginx
volumeMounts:
- mountPath: /etc/localtime ##容器里面挂载的目录
name: timezone-time ##要和下面name一样
volumes:
- name: timezone-time
hostPath:
path: /etc/localtime ##宿主机的目录
type: File
注意:/etc/localtime 用于配置系统时区(此文件是一个链接文件,链接自/usr/share/zoneinfo/Asia/Shanghai)。
创建pod
[root@k8s-master ~]# ku delete -f nginx-empty.yaml
deployment.apps "nginx" deleted
查看创建结果
[root@k8s-master ~]# ku get pod
NAME READY STATUS RESTARTS AGE
nginx-59f95c99b5-rzf7t 1/1 Running 0 3s
进入容器测试(目的:配置容器和宿主机时间一致)
[root@k8s-master ~]# ku exec -it nginx-59f95c99b5-rzf7t -- bash
root@nginx-59f95c99b5-rzf7t:/# ls -l /etc/localtime
-rw-r--r--. 4 root root 561 Dec 11 2024 /etc/localtime
root@nginx-59f95c99b5-rzf7t:/# date
Wed Jul 9 11:49:38 CST 2025
在每个节点安装nfs dnf -y install nfs-utils
设置共享目录
[root@k8s-master ~]# mkdir /opt/wwwroot && echo "this is my test file">/opt/wwwroot/index.html
[root@k8s-master ~]# vim /etc/exports
/opt/wwwroot 192.168.10.0/24(rw,sync,no_root_squash)
开启nfs
[root@k8s-master ~]# systemctl start nfs
[root@k8s-master ~]# systemctl start rpcbind
编写deployment文件,挂载nfs
[root@k8s-master ~]# vim nginx-nfsVolume.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent
name: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html ##容器内的目录
name: nfs-volume
volumes:
- name: nfs-volume
nfs:
server: 192.168.10.101
path: /opt/wwwroot ##宿主机创建的挂载目录
创建此pod
[root@k8s-master ~]# ku create -f nginx-nfsVolume.yaml
deployment.apps/nginx created
[root@k8s-master ~]# ku get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-7bf6df4679-m8vh8 1/1 Running 0 8s 10.244.169.139 k8s-node2
直接访问容器查看挂载结果
[root@k8s-master ~]# curl 10.244.169.139
this is my test file
虽然 volume 已经可以接入大部分存储后端,但是实际使用时还有诸多的问题。比如:
为此,kubernetes 引入了两个新的 API 资源:PersistentVolume(持久卷,简称PV)和PersistentVolumeclaim(持久卷声明,简称PVC)。
当用户使用完卷时,可以从 API 中删除 PVC 对象,从而允许回收资源。回收策略会告诉 PV 如何处理改卷。目前回收策略可以设置为 Retain、Recycle 和 Delete。静态PV默认的为 Retain,动态 PV 默认为 Delete.
在实际使用 PV时,可能针对不同的应用会有不同的访问策略,比如某类 Pod 可以读写,某类 Pod 只能读,或者需要配置是否可以被多个不同的 Pod 同时读写等,此时可以使用 PV的访问策略进行简单控制,目前支持的访问策略如下:
虽然 PV 在创建时可以指定不同的访问策略,但是也要后端的存储支持才行。比如一般情况下,大部分块存储是不支持 ReadwriteMany 的。
1,静态配置
静态配置是手动创建 PV 并定义其属性,例如容量、访问模式、存储后端等。在这种情况下,Kubernetes管理员负责管理和配置 PV,然后应用程序可以使用这些 PV。静态配置通常用于一些固定的存储后端,如NFS
2,动态配置
动态配置允许 Kubernetes 集群根据 PVC 的需求自动创建 PV,在这种情况下,管理员只需为存储后端配置 storageclass,然后应用程序就可以通过 PVC 请求存储。Kubernetes 将自动创建与 PVC 匹配的PV,并将其绑定到 PVC上。这种方法使得存储管理更加灵活和可扩展,允许管理员在集群中动态添加、删除、和管理存储资源
当 kubernetes 管理员提前创建好了 PV,我们又应该如何使用它呢?这里介绍 kubernetes 的另一个概念 PersistentVolumeclaim(简称PVC)。PVC 是其他技术人员
在 kubernetes 上对存储的申请,他可以标明一个程序需要用到什么样的后端存储、多大的空间以及什么访问模式进行挂载。这一点和 Pod 的 Qos 配置类似,Pod 消耗节点资源,PVC 消耗 PV 资源,Pod 可以请求特定级别的资源(CPU和内存),PVC可以请求特定的大小和访问模式的PV。例如申请一个大小为5G且只能被一个 Pod 只读访问的存储。
在实际使用时,虽然用户通过 PVC获取存储支持,但是用户可能需要具有不同性质的 PV来解决不同的问题,比如使用 SSD 硬盘来提高性能。所以集群管理员需要根据不同的存储后端来提供各种 PV,而不仅仅是大小和访问模式的区别,并且无须让用户了解这些卷的具体实现方式和存储类型,打扫了存储的解耦,降低了存储使用的复杂度。
接下来我们来看看如何让 PVC 和前面创建的 PV 绑定。PVC和 PV进行绑定的前提条件是一些参数必须匹配,比如 accessModes、storageclassName、volumeMode 都需要相同,并且 PVC 的 storage 需要小于等于 PV 的 storage 配置。
由于不知道master会把那个容器调度到那个节点,所有需要在所有节点创建主机目录
[root@k8s-master ~]# mkdir /mnt/data
编写hostpath的yaml文件
[root@k8s-master ~]# vim hostpath-pv.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
name: mypv-hostpath
labels:
type: local
spec:
storageClassName: pv-hostpath
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
创建并查看此pv
[root@k8s-master ~]# ku create -f hostpath-pv.yaml
persistentvolume/mypv-hostpath created
[root@k8s-master ~]# ku get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv-hostpath 10Gi RWO Retain Available pv-hostpath 4s
为hostpath类型的pv创建pvc
[root@k8s-master ~]# vim pvc-hostpath.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mypvc-hostpath ##hostpv的名字
spec:
storageClassName: pv-hostpath ##host storageclass的名字
accessModes:
- ReadWriteOnce ##指定访问策略为单节点读写
resources:
requests:
storage: 3Gi ##指定可使用空间为3G
注意;我们创建了一个存储为10G的hostpv,但是在pvc中可以定义可使用的空间
创建此pvc
[root@k8s-master ~]# ku create -f pvc-hostpath.yaml
persistentvolumeclaim/mypvc-hostpath created
查看此pvc的详细信息
[root@k8s-master ~]# ku get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc-hostpath Bound mypv-hostpath 10Gi RWO pv-hostpath 4s
pvc的使用
创建pod绑定hostpath的pv
[root@k8s-master ~]# vim pvc-pv-pod-hostpath.yaml
kind: Pod
apiVersion: v1
metadata:
name: hostpath-pv-pod
spec:
volumes:
- name: task-pv-storage
persistentVolumeClaim:
claimName: mypvc-hostpath
containers:
- name: task-pv-container
image: nginx:1.7.9
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html" ##指定容器内绑定的路径
name: task-pv-storage
##宿主机要绑定的路径已经在host pv指定:/mnt/data
查看此pod在那个节点上运行
[root@k8s-master ~]# ku get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hostpath-pv-pod 1/1 Running 0 4s 10.244.169.140 k8s-node2
发现在node2节点上运行
在node2节点上创建要访问的内容
[root@k8s-node2 images]# cd /mnt/data
[root@k8s-node2 data]# echo "1111111111">index.html
验证,访问此容器
[root@k8s-master ~]# curl 10.244.169.140
1111111111
编写基于nfs的yaml文件
[root@k8s-master ~]# vim nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mypv-nfs
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: pvc-nfs
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /opt/wwwroot/ ##指定路径
server: 192.168.10.101 ##指定主机ip
创建pv
[root@k8s-master ~]# ku create -f nfs-pv.yaml
persistentvolume/mypv-nfs created
查看pv创建结果
[root@k8s-master ~]# ku get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
mypv-nfs 5Gi RWO Recycle Available pvc-nfs 2s
创建基于nfs的pvc
[root@k8s-master ~]# vim pvc-nfs.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mypvc-nfs ##名字要和pv一样
spec:
storageClassName: pvc-nfs
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
~
创建pvc
[root@k8s-master ~]# ku create -f pvc-nfs.yaml
persistentvolumeclaim/mypvc-nfs created
查看pvc
[root@k8s-master ~]# ku get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc-nfs Bound mypv-nfs 5Gi RWO pvc-nfs 5s
使用基于nfs的pvc,先创建pod并绑定nfs的pvc
[root@k8s-master ~]# vim pvc-pv-pod-nfs.yaml
kind: Pod
apiVersion: v1
metadata:
name: pvc-nfs
spec:
volumes:
- name: pvc-nfs01
persistentVolumeClaim:
claimName: mypvc-nfs ##这个名字是基于nfs创建的pvc的名字
containers:
- name: task-pv-container
image: nginx:1.7.9
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: pvc-nfs01
创建此pod
[root@k8s-master ~]# ku create -f pvc-pv-pod-nfs.yaml
pod/pvc-nfs created
验证
[root@k8s-master ~]# ku get pod -o wide ##查看此pod的IP
NAME READY STATUS RESTARTS AGE IP NODE
pvc-nfs 1/1 Running 0 35s 10.244.169.141 k8s-node2
[root@k8s-master ~]# curl 10.244.169.141
this is my test file
注意:这里访问的内容是之前创建的nfs
[root@k8s-master ~]# cd /opt/wwwroot/
[root@k8s-master wwwroot]# cat index.html
this is my test file