前提条件
- 拥有Kubernetes集群环境,可参考:Kubernetes集群搭建
- 理解Kubernetes部署知识,可参考:使用Kubernetes部署第一个应用 、Deloyment控制器
- 拥有NFS服务,可参考:Linux环境搭建NFS服务
概述
- Persistent Volume(持久卷,简称PV):是 Kubernetes 中对存储资源的抽象,它独立于使用存储的 Pod。PV 是集群中的一块存储,可以由管理员预先配置或者通过存储类(Storage Class)动态分配。它类似于传统存储中的磁盘卷。
- Persistent Volume Claim(持久卷声明,简称PVC):是用户对存储的请求。Pod 通过 PVC 来请求所需的存储资源,而不是直接访问 PV,实现了Pod和PV的解耦。PVC 就像是用户向存储系统 “下单”,请求一定规格的存储,比如指定存储大小、访问模式等,一定规格的存储指的就是PV。
PV(持久卷)
- 访问模式(Access Modes):
- ReadWriteOnce(RWO):这种模式下,存储卷可以被单个节点以读写方式挂载。例如,一个有状态的应用(如 MySQL 数据库)可能需要这种模式,因为数据库文件需要在一个地方进行读写操作,以保证数据的一致性。
- ReadOnlyMany(ROX):存储卷可以被多个节点以只读方式挂载。适用于存储配置文件等只读数据,比如多个 Web 服务器节点可以同时挂载一个包含网站配置的存储卷,但只能读取其中的配置信息。
- ReadWriteMany(RWX):存储卷能够被多个节点以读写方式挂载。在一些分布式文件系统(如 CephFS)中可以支持这种模式,适用于多个节点需要同时读写共享数据的场景,比如集群中的共享存储。
- 存储类型(Storage Class):用于动态分配存储。不同的存储类型可能对应不同的后端存储系统,如网络文件系统(NFS)、网络附加存储(NAS)、云存储(如 AWS EBS、Azure Disk 等)或者分布式存储系统(如 GlusterFS、Ceph)。
- 回收策略(Reclaim Policy):
- Recycle:(已弃用)旧版本的 Kubernetes 有回收策略,会对存储卷进行格式化后重新使用,但是由于安全和效率等问题已经不推荐使用。
- Delete:当 PVC 被删除后,PV 及其数据也会被自动删除。这在一些临时存储场景下比较有用,比如用于存储临时测试数据的存储卷。
- Retain:当 PVC 被删除后,PV 仍然保留数据,需要管理员手动清理。这种策略适用于数据很重要,不能轻易删除的情况。例如,存储了重要数据库备份的 PV,在 PVC 不再使用后,管理员可能需要先检查备份数据是否还需要,再决定是否删除 PV。
- 生命周期管理
- 静态分配:管理员手动创建 PV,并将其与后端存储系统(如物理磁盘、网络存储等)进行绑定。例如,在一个本地数据中心部署的 Kubernetes 集群中,管理员可以先将服务器上的一块硬盘格式化为合适的文件系统,然后创建一个 PV 来表示这块硬盘的存储空间,并且配置好容量、访问模式等属性。
- 动态分配:通过存储类(Storage Class)来实现。存储类定义了存储的提供方、参数等信息。当用户创建一个 PVC 时,Kubernetes 会根据 PVC 的要求和存储类的定义,自动创建一个合适的 PV 来满足需求。比如,在一个云环境中的 Kubernetes 集群,使用云存储服务提供商的存储类,可以让集群自动分配云硬盘来满足 PVC 的存储请求。
创建PV对象
新建一个 PV 对象,使用NFS类型的后端存储(如果还没有NFS服务,需要先搭建NFS服务,可参考 :Linux环境搭建NFS服务),1G 的存储空间,访问模式为 ReadWriteOnce,回收策略为 Recyle。
目录准备
[root@k8s-master01 test]# mkdir pvpvctest [root@k8s-master01 test]# cd pvpvctest/ [root@k8s-master01 pvpvctest]#
创建
[root@k8s-master01 pvpvctest]# vi pv1-demo.yaml
内容如下
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
nfs:
path: /data/k8s
server: k8s-node02
Kubernetes 支持的 PV 类型有很多,比如常见的 Ceph、GlusterFs、NFS,甚至 HostPath也可以,不过 HostPath 我们之前也说过仅仅可以用于单机测试
创建PV及查看PV
[root@k8s-master01 pvpvctest]# kubectl create -f pv1-demo.yaml Warning: spec.persistentVolumeReclaimPolicy: The Recycle reclaim policy is deprecated. Instead, the recommended approach is to use dynamic provisioning. persistentvolume/pv1 created [root@k8s-master01 pvpvctest]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pv1 1Gi RWO Recycle Available <unset> 66s
看到 pv1 已经创建成功了,状态是 Available,表示 pv1 就绪,可以被 PVC 申请。
一个 PV 的生命周期中,可能会处于4中不同的阶段:
-
Available(可用):表示可用状态,还未被任何 PVC 绑定
-
Bound(已绑定):表示 PVC 已经被 PVC 绑定
-
Released(已释放):PVC 被删除,但是资源还未被集群重新声明
-
Failed(失败): 表示该 PV 的自动回收失败
PVC(持久卷声明)
PV(Persistent Volume)是对存储资源的抽象,代表了一块存储,而 PVC(Persistent Volume Claim)是用户(通过 Pod)对存储资源的请求。PVC 用于从集群中获取合适的 PV,然后将获取到的存储挂载到 Pod 中,为 Pod 中的应用提供持久化存储服务。
创建PVC
创建yaml文件
[root@k8s-master01 pvpvctest]# vi pvc-nfs.yaml
内容如下
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-nfs
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
在新建 PVC 之前,查看之前创建的 PV 的状态:
[root@k8s-master01 pvpvctest]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pv1 1Gi RWO Recycle Available <unset> 14m
看到当前 pv-nfs 是在 Available 的一个状态,所以 PVC 可以和这个 PV 进行绑定
创建PVC
[root@k8s-master01 pvpvctest]# kubectl create -f pvc-nfs.yaml persistentvolumeclaim/pvc-nfs created [root@k8s-master01 pvpvctest]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE pvc-nfs Bound pv1 1Gi RWO <unset> 10s
看到 pvc-nfs 创建成功了,状态是 Bound 状态了,此时再看 PV 的状态:
[root@k8s-master01 pvpvctest]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pv1 1Gi RWO Recycle Bound default/pvc-nfs <unset> 15m
看到 PV 也是 Bound 状态了,对应的声明是 default/pvc-nfs,就是 default 命名空间下面的 pvc-nfs,证明刚刚新建的 pvc-nfs 和 pv-nfs 绑定成功了。
此前并没有在 PVC中指定PV,PVC和PV之间是系统自动匹配的,根据PVC的要求去查找处于 Available 状态的 PV,如果没有找到的话那么 PVC 就会一直处于 Pending 状态,找到会把当前的 PVC 和目标 PV 进行绑定,这个时候状态就会变成 Bound 状态了。
再新建一个 PVC验证,创建pvc2-nfs.yaml
[root@k8s-master01 pvpvctest]# vi pvc2-nfs.yaml
内容如下
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc2-nfs
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
selector:
matchLabels:
app: nfs
先查看下当前系统的所有 PV:
[root@k8s-master01 pvpvctest]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pv1 1Gi RWO Recycle Bound default/pvc-nfs <unset> 21m
查到的pv为 Bound 状态,并没有 Available 状态的 PV
创建PVC:
[root@k8s-master01 pvpvctest]# kubectl create -f pvc2-nfs.yaml persistentvolumeclaim/pvc2-nfs created [root@k8s-master01 pvpvctest]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE pvc-nfs Bound pv1 1Gi RWO <unset> 7m56s pvc2-nfs Pending <unset> 25s
名为pvc2-nfs的PVC状态是 Pending ,因为没有合适的 PV 给PVC绑定。
接下来新建一个 PV,让PVC 有合适的 PV 使用
创建pv2-nfs.yaml
vi pv2-nfs.yaml
内容如下
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2-nfs
labels:
app: nfs
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
nfs:
server: k8s-node02
path: /data/k8s
新建PV及查看PV状态:
[root@k8s-master01 pvpvctest]# kubectl create -f pv2-nfs.yaml Warning: spec.persistentVolumeReclaimPolicy: The Recycle reclaim policy is deprecated. Instead, the recommended approach is to use dynamic provisioning. persistentvolume/pv2-nfs created [root@k8s-master01 pvpvctest]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pv1 1Gi RWO Recycle Bound default/pvc-nfs <unset> 25m pv2-nfs 2Gi RWO Recycle Bound default/pvc2-nfs <unset> 12s
创建完 pv2-nfs 后,就发现该 PV 是 Bound 状态了,对应的 PVC 是 default/pvc2-nfs,证明 pvc2-nfs 终于找到合适的 PV 绑定上
查看PVC状态也是Bound状态了
[root@k8s-master01 pvpvctest]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE pvc-nfs Bound pv1 1Gi RWO <unset> 10m pvc2-nfs Bound pv2-nfs 2Gi RWO <unset> 3m14s
注意:PV 的容量必须大于等于 PVC 请求的容量,例如:PVC的容量为1Gi, 而PV的容量为2Gi,PVC和PV是可以的绑定成功,但查到PVC的容量不是1Gi而是2Gi(可自行验证)。
使用 PVC
创建yaml
[root@k8s-master01 pvpvctest]# vi nfs-pvc-deploy.yaml
内容如下
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-pvc
spec:
replicas: 3
selector:
matchLabels:
app: nfs-pvc
template:
metadata:
labels:
app: nfs-pvc
spec:
containers:
- name: nginx
image: nginx:1.16.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumes:
- name: www
persistentVolumeClaim:
claimName: pvc2-nfs
---
apiVersion: v1
kind: Service
metadata:
name: nfs-pvc
labels:
app: nfs-pvc
spec:
type: NodePort
ports:
- port: 80
targetPort: web
selector:
app: nfs-pvc
将容器的 /usr/share/nginx/html 目录通过 volume 挂载到名为 pvc2-nfs 的 PVC 上面,然后创建一个 NodePort 类型的 Service 来暴露服务:
[root@k8s-master01 pvpvctest]# kubectl create -f nfs-pvc-deploy.yaml deployment.apps/nfs-pvc created service/nfs-pvc created [root@k8s-master01 pvpvctest]# kubectl get pods NAME READY STATUS RESTARTS AGE nfs-pvc-5b4d9797d6-9pk2b 1/1 Running 0 21s nfs-pvc-5b4d9797d6-tbqgl 1/1 Running 0 21s nfs-pvc-5b4d9797d6-vx4k6 1/1 Running 0 21s [root@k8s-master01 pvpvctest]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 82d nfs-pvc NodePort 10.15.179.232 <none> 80:30444/TCP 46s
上面命令查看到nfs-pvc service的节点端口为30444,通过任意节点的 IP:30444 端口来访问的 Nginx 服务了,但是这个时候访问会出现403
这是为什么?在nfs服务端查看/data/k8s目录
[root@k8s-node02 ~]# ls /data/k8s test.txt
发现没有index.html文件,因为把容器目录/user/share/nginx/html和挂载到了pvc2-nfs PVC 上, PVC 就是对应着的 nfs 的共享数据目录的,但该目录下面还没有任何数据,所以访问就出现了403
接下来在/data/k8s目录下面新建一个 index.html 的文件:
[root@k8s-node02 ~]# echo "<h1>Hello Kubernetes~</h1>" >> /data/k8s/index.html [root@k8s-node02 ~]# ls /data/k8s index.html test.txt
访问下服务,任一节点IP:30444:
现在看到数据了nds index.html的数据。
但是容器中的数据是直接放到共享数据目录根目录下面的,如果以后有一个新的 nginx 容器也做了数据目录的挂载,就不太好区分了,这个时候可以在 Pod 中使用一个新的属性:subPath,该属性可以来解决这个问题,只需要更改上面的 Pod 的 YAML 文件即可:
[root@k8s-master01 pvpvctest]# vi nfs-pvc-deploy.yaml
添加subPath: nginxpvc-test
语句,作为nfs的子目录,部分yaml内容如下
... volumeMounts: - name: www subPath: nginxpvc-test mountPath: /usr/share/nginx/html ...
更改完 YAML 文件后,我们重新更新即可:
$ kubectl apply -f nfs-pvc-deploy.yaml Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply deployment.extensions "nfs-pvc" configured Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply service "nfs-pvc" configured [root@k8s-master01 pvpvctest]# kubectl apply -f nfs-pvc-deploy.yaml Warning: resource deployments/nfs-pvc is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically. deployment.apps/nfs-pvc configured Warning: resource services/nfs-pvc is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically. service/nfs-pvc configured
刷新浏览器,访问到403
更新完后,再去看看 nfs 的数据共享目录:
# 查看/data/k8s目录,多了一个目录nginxpvc-test,这就是之前yaml文件添加的subPath [root@k8s-node02 ~]# ls /data/k8s/ index.html nginxpvc-test test.txt # 查看nginxpvc-test目录,没有index.html内容所以浏览器访问不到 [root@k8s-node02 ~]# ls /data/k8s/nginxpvc-test/ # 将index.html移动到nginxpvc-test目录 [root@k8s-node02 ~]# mv /data/k8s/index.html /data/k8s/nginxpvc-test/ # 查看nginxpvc-test目录,看到index.html [root@k8s-node02 ~]# ls /data/k8s/nginxpvc-test/ index.html
刷新浏览器,又看到数据了