kubernetes(k8s)存储方案(认识PV/PVC/StorageClass)- NFS

本文深入解析Kubernetes中PV/PVC的概念及其与NFS的结合使用,探讨了持久化存储的实现方法,包括PV和PVC的创建、NFS的配置与挂载,以及解决PVC待命状态和NFS挂载错误等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、认识PV/PVC/StorageClass

PersistentVolume(pv)和PersistentVolumeClaim(pvc)是k8s提供的两种API资源,用于抽象存储细节,用于实现持久化存储.

  • PersistentVolume(PV)是集群中由管理员配置的一段网络存储。
    是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PV 是 Volume 之类的卷插件,但具有独立于使用 PV 的 Pod 的生命周期。此 API 对象包含存储实现的细节,即 NFS、iSCSI 或特定于云供应商的存储系统.

    • PV 是集群级别的资源,不属于任何命名空间
    • 可以是静态配置(管理员手动创建)或动态分配(通过 StorageClass 动态创建)
  • PersistentVolumeClaim(PVC)是由用户进行存储的请求。
    它与 Pod 相似。Pod 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存)。PVC声明可以请求特定的大小和访问模式(如:读写或只读)

    • PVC 是命名空间级别的资源。
    • 用户无需关心底层存储细节,只需声明所需容量和访问模式。
    • 如果匹配的 PV 不存在,PVC 可能处于 Pending 状态。

PV支持的类型 https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes

总结:Persistent Volume(持久化卷)简称PV, 是一个K8S资源对象,我们可以单独创建一个PV, 它不和Pod直接发生关系, 而是通过Persistent Volume Claim, 简称PVC来实现动态绑定, 我们会在Pod定义里指定创建好的PVC, 然后PVC会根据Pod的要求去自动绑定合适的PV给Pod使用。

访问模式(accessModes)

  • ReadWriteOnce – PV以 read-write 挂载到一个节点
  • ReadWriteMany – PV以 read-write方式挂载到多个节点
  • ReadOnlyMany – PV以 read-only方式挂载到多个节点
  • StorageClass
    StorageClass 是动态分配 PV 的模板,定义了存储的类型、后端配置和动态卷分配的行为。
    • 允许动态创建 PV,无需管理员手动干预。
    • 支持多种存储后端(如云存储、本地存储等)。
      示例 YAML:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cbs
provisioner: com.tencent.cloud.csi.cbs
reclaimPolicy: Delete
volumeBindingMode: Immediate

总结
PV:存储资源。
PVC:请求存储资源。
StorageClass:动态分配存储资源的模板。
确保 PVC 中指定的 storageClassName 在集群中存在,否则 PVC 会处于 Pending 状态。

三者的关系

  1. 静态分配:
    管理员手动创建 PV。
    用户创建 PVC,Kubernetes 将 PVC 绑定到匹配的 PV。
  2. 动态分配:
    用户创建 PVC 并指定 StorageClass
    如果 StorageClass 支持动态分配,Kubernetes 会自动创建 PV 并绑定到 PVC

二、PV/PVC结合NFS使用实践(持久存储)

1. 安装NFS

  1. 安装NFS
#安装依赖包
yum -y install nfs-utils rpcbind

#创建要存放文件的目录
mkdir -p  /data/app/share
chown -R nfsnobody.nfsnobody /data/app/share


#开机启动,
systemctl enable rpcbind.service 
systemctl enable nfs-server.service
systemctl start rpcbind.service #端口是111
systemctl start nfs-server.service # 端口是 2049 
  1. 修改配置文件
# 将要共享的目录输入到 /etc/exports 配置文件
echo "/data/app/share 0.0.0.0/0(rw,sync,all_squash)" >> /etc/exports

#root_squash:在登入 NFS 主机使用分享之目录的使用者如果是 root 时,那么这个使用者的权限将被压缩成为匿名使用者,通常他的 UID 与 GID 都会变成 nobody 那个系统账号的身份。
#no_root_squash:登入 NFS 主机使用分享目录的使用者,如果是 root 的话,那么对于这个分享的目录来说,他就具有 root 的权限!这个项目『极不安全』,不建议使用!
#all_squash:登入 NFS 主机使用分享目录的使用者,所有用户均被压缩成为匿名使用者,即已nobody用户的身份登录。
#anonuid和anongid:明确指定匿名使用者使用指定的id值用户的权限,访问分享的目录。
#secure:限制客户端只能从小于1024的tcp/ip端口连接nfs服务器(默认设置)。
#insecure:允许客户端从大于1024的tcp/ip端口连接服务器。

  1. 重新加载配置
exportfs -rv

# 查看配置
exportfs

上面是linux搭建nfs过程,这里我们需要测试k8s pv,所以改一下共享目录名,重新共享出去:

mkdir /data/pvdata
chown nfsnobody:nfsnobody /data/pvdata

echo "/data/pvdata  0.0.0.0/0(rw,sync,all_squash)" >> /etc/exports
exportfs -rv
cat /etc/exports
exportfs
# 修改完成后再重启一下nfs
systemctl start rpcbind.service   #端口是111
systemctl start nfs-server.service   # 端口是 2049 
# 查看共享文件
[root@storage1 ~]# showmount -e 127.0.0.1
Export list for 127.0.0.1:
/data/pvdata    0.0.0.0/0
/data/app/share 0.0.0.0/0
[root@storage1 ~]# 

查看挂载相关日志

cat /var/log/messages | grep mount

查看当前client端挂载的配置信息,可以通过如下命令:

$ mount|grep nfs

取消mount挂载:

$ umount /xxx/xxxx(client端的挂载目录)

2. 建立PV

vi pv.yaml 创建yaml文件

# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-data  #pv的名称
spec:
  capacity: #容量
    storage: 10Gi  #pv可用的大小
  accessModes: #访问模式
    - ReadWriteOnce  #PV以read-write挂载到一个节点
  persistentVolumeReclaimPolicy: Recycle #持久卷回收策略
  storageClassName: nfs  #存储类名称  重要:需将来的PVC中定义的一样
  nfs:
    path: /data/pvdata      #NFS的路径
    server: 172.22.22.215   #NFS的IP地址
[root@kubernetes-master ~]# vi pv.yaml
[root@kubernetes-master ~]# kubectl apply -f pv.yaml 
persistentvolume/pv-data created
[root@kubernetes-master ~]# kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv-data   10Gi       RWO            Recycle          Available           nfs                     10s
[root@kubernetes-master ~]# 

3. 建立PVC

在使用 PVC 之前,我们还得把其他节点上的 nfs 客户端给安装上,使用kubectl get nodes查看所有节点

kubectl get nodes
yum -y install  nfs-utils rpcbind

vi pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs  #PVC的名称
spec:
  accessModes:
    - ReadWriteOnce #PVC以read-write挂载到一个节点
  resources:
    requests:
      storage: 10Gi #PVC允许申请的大小
# kubectl apply  -f pvc.yaml 
persistentvolumeclaim/pvdata created

# kubectl get pvc
NAME        STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nginx-log   Bound    pv-data   10Gi       RWO                           18m

经过测试,发现在k8s,PVC 也是namespace隔离的。这里没有指定,就是默认的namespace。pvc是命名空间隔离的,pv可以全局共享,不用指定命名空间。

pv是全局的,pvc可以指定namespace,在如下位置加入 namespace

metadata:
  name: pvc-nfs  #PVC的名称
  namespace: mongo

4. 过程问题整理

1) pvc一直处于Pending状态
[root@kubernetes-master ~]# kubectl get pvc
NAME            STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mongo-storage   Pending   

使用命令查看详细信息

[root@kubernetes-master ~]# kubectl get pvc
NAME            STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mongo-storage   Pending                                                     10m
[root@kubernetes-master ~]#  kubectl describe pvc mongo-storage
Name:          mongo-storage
Namespace:     default
StorageClass:  
Status:        Pending
Volume:        
Labels:        <none>
Annotations:   kubectl.kubernetes.io/last-applied-configuration:
                 {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"mongo-storage","namespace":"default"},"spec":{"acce...
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      
Access Modes:  
VolumeMode:    Filesystem
Mounted By:    <none>
Events:
  Type    Reason         Age                  From                         Message
  ----    ------         ----                 ----                         -------
  Normal  FailedBinding  118s (x42 over 12m)  persistentvolume-controller  no persistent volumes available for this claim and no storage class is set
[root@kubernetes-master ~]# 

原因分析:
理论上,根据网上信息
并没有在 pvc-nfs 中指定关于 pv 的什么标志,它们之间是怎么就关联起来了的呢?
解答:其实这是系统自动帮我们去匹配的,它会根据我们的声明要求去查找处于 Available 状态的 PV,如果没有找到的话那么PVC 就会一直处于 Pending 状态,找到了的话当然就会把当前的 PVC 和目标 PV 进行绑定,这个时候状态就会变成 Bound 状态了。

根据网上描述:
创建PV、PVC二者后,如果能够自动绑定,说明NFS系统工作正常。在 PVC 绑定 PV 时通常根据两个条件来绑定,一个是存储的大小,另一个就是访问模式。

这里我一直是Pending。

k8s 如何关联pvc到特定的pv?
参考URL: https://www.jianshu.com/p/99e610067bc8

通过上面参考URL的分析,我们可以看到PVC和PV的绑定,不是简单的通过Label来进行。而是要综合storageClassName,accessModes,matchLabels以及storage来进行绑定。

根据PVC和PV绑定原理分析,我这里是PV指定了storageClassName: nfs #存储类名称,PVC里面又没有指定storageClassName导致。

因此,

[root@kubernetes-master ~]# vi pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongo-storage #PVC的名称 如 nginx-log 一般指出用途
spec:
  accessModes:
    - ReadWriteOnce #PVC以read-write挂载到一个节点
  storageClassName: nfs
  resources:
    requests:
      storage: 10Gi #PVC允许申请的大小
[root@kubernetes-master ~]# 

先删除刚才没有绑定的pvc,在用新yaml生成新的pvc,命令如下:

[root@kubernetes-master ~]# kubectl get pvc
NAME            STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mongo-storage   Pending                                                     76m
[root@kubernetes-master ~]# kubectl delete pvc mongo-storage
persistentvolumeclaim "mongo-storage" deleted
[root@kubernetes-master ~]# kubectl apply  -f pvc.yaml 
persistentvolumeclaim/mongo-storage created
[root@kubernetes-master ~]# kubectl get pvc
NAME            STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mongo-storage   Bound    pv-data   10Gi       RWO            nfs            2s
[root@kubernetes-master ~]# 

2) nfs挂载报错unmatched host

查看nfs日志

cat /var/log/messages | grep mount

报错
Feb 4 03:19:26 storage1 dockerd: time=“2020-02-04T03:19:26.861842395+08:00” level=warning msg=“Using pre-4.0.0 kernel for overlay2, mount failures may require kernel update” storage-driver=overlay2
Feb 4 03:22:32 storage1 rpc.mountd[960]: refused mount request from 192.168.13.101 for /data/app/share (/data/app/share): unmatched host

三、PV的动态创建

Kubernetes对象之PersistentVolume,StorageClass和PersistentVolumeClaim
参考URL: https://www.jianshu.com/p/99e610067bc8
k8s使用nfs动态存储
参考URL: https://www.cnblogs.com/cuishuai/p/9152277.html

有两种PV提供的方式:静态和动态。

  • 静态
      集群管理员创建多个PV,它们携带着真实存储的详细信息,这些存储对于集群用户是可用的。它们存在于Kubernetes API中,并可用于存储使用。

  • 动态
    当管理员创建的静态PV都不匹配用户的PVC时,集群可能会尝试专门地供给volume给PVC。这种供给基于StorageClass:PVC必须请求这样一个等级,而管理员必须已经创建和配置过这样一个等级,以备发生这种动态供给的情况。请求等级配置为“”的PVC,有效地禁用了它自身的动态供给功能。

上文中我们通过PersistentVolume描述文件创建了一个PV。这样的创建方式我们成为静态创建。这样的创建方式有一个弊端,那就是假如我们创建PV时指定大小为50G,而PVC请求80G的PV,那么此PVC就无法找到合适的PV来绑定。因此产生了了PV的动态创建。

PV的动态创建依赖于StorageClass对象。我们不需要手动创建任何PV,所有的工作都由StorageClass为我们完成

一个例子如下:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
  type: io1
  zones: us-east-1d, us-east-1c
  iopsPerGB: "10"

这个例子使用AWS提供的插件( kubernetes.io/aws-ebs)创建了一个基于AWS底层存储的StorageClass。这意味着使用这个StorageClass,那么所有的PV都是AWSElasticBlockStore类型的。

StorageClass的定义包含四个部分:

  • provisioner:指定 Volume 插件的类型,包括内置插件(如 kubernetes.io/aws-ebs)和外部插件(如 external-storage 提供的 ceph.com/cephfs)。
  • mountOptions:指定挂载选项,当 PV 不支持指定的选项时会直接失败。比如 NFS 支持 hard 和 nfsvers=4.1 等选项。
  • parameters:指定 provisioner 的选项,比如 kubernetes.io/aws-ebs 支持 type、zone、iopsPerGB 等参数。
  • reclaimPolicy:指定回收策略,同 PV 的回收策略。

手动创建的PV时,我们指定了storageClassName=slow的配置项,然后Pod定义中也通过指定storageClassName=slow,从而完成绑定。而通过StorageClass实现动态PV时,我们只需要指定StorageClass的metadata.name。

回到上文中创建PVC的例子,此时PVC指定了storageClassName=slow。那么Kubernetes会在集群中寻找是否存在metadata.name=slow的StorageClass,如果存在,此StorageClass会自动为此PVC创建一个accessModes = ReadWriteOnce,并且大小为8GB的PV。

通过StorageClass的使用,使我们从提前构建静态PV池的工作中解放出来。

四、工作中常见报错

报错“Pod has unbound immediate PersistentVolumeClaims”

报错“Pod has unbound immediate PersistentVolumeClaims”

问题分析:
“Pod has unbound immediate PersistentVolumeClaims” 的意思是:Pod 的 PersistentVolumeClaims (PVC) 未被绑定到任何 PersistentVolume (PV),导致 Pod 无法调度到节点上运行。

“Unbound immediate”:
Unbound:表示 PVC 没有被绑定到任何 PV。
Immediate:表示 PVC 的绑定模式是“立即绑定”(即创建 PVC 时就需要绑定 PV,而不是延迟绑定)。

PersistentVolumeClaims (PVC):
是 Kubernetes 中用于申请存储资源的对象,类似于 Pod 对存储的需求声明。
Pod 通过 PVC 来请求存储资源(如磁盘空间)。

PersistentVolume (PV):
是集群中的实际存储资源,由管理员预先配置或动态分配。
PVC 需要绑定到 PV 才能提供存储。

PV 不足:集群中没有足够的 PV 满足 PVC 的请求(例如,PV 的容量、访问模式或存储类不匹配)。

解决方法:

检查 PVC 和 PV 状态:

kubectl get pvc -n xxx
kubectl get pv -n xxx

查看是否有未绑定的 PVC 或可用的 PV。

例如:从输出结果来看,xxx-local-storage 这个 PersistentVolumeClaim (PVC) 的状态是 Pending,而其他 PVC 的状态都是 Bound。这表明 xxx-local-storage 未能成功绑定到可用的 PersistentVolume (PV),从而导致 Pod 无法调度

查看 PVC 的详细信息

kubectl describe pvc xxx-local-storage -n your-namespace

StorageClass: cfs 表示该 PersistentVolumeClaim (PVC) 请求使用的存储类型为 cfs(Cloud File Storage,通常指云文件存储服务)
发现关键报错:

Warning  ProvisioningFailed  3m34s (x102 over 28m)  persistentvolume-controller  storageclass.storage.k8s.io "cfs" not found

问题分析:
分布式文件存储(如腾讯云 CFS、NFS 或其他),cfs 存储类未找到,cfs 通常用于共享文件存储(如 NFS 或云厂商提供的共享文件系统),允许多个 Pod 同时读写同一个卷。
例如:可以从 Used By 字段可以看到,该 PVC 被多个 Pod引用,说明这是一个需要共享访问的存储场景。

StorageClass: cfs 是指 Kubernetes 集群中定义的一种存储类(StorageClass),用于动态分配持久卷(Persistent Volume, PV)。然而,从你的 kubectl get storageclass 输出中,集群中并没有名为 cfs 的存储类

根本问题:PVC 引用了不存在的存储类 cfs。你定义的 PersistentVolumeClaim (PVC) 中指定了 storageClassName: cfs,但集群中并没有名为 cfs 的 StorageClass。

总结:VC 处于 Pending 状态可能的原因是:
StorageClass 不存在会导致 PVC 处于 Pending 状态,
检查 StorageClass 是否存在:kubectl get storageclass

创建 cfs 存储类(推荐)

前提:确保你的集群支持分布式文件存储(如腾讯云 CFS、NFS 或其他)。
步骤:

  1. 安装 CFS 驱动(以腾讯云为例):
    安装腾讯云 CFS CSI 驱动
kubectl apply -f https://raw.githubusercontent.com/TencentCloud/kubernetes-csi-tencentcloud/master/deploy/cfs/kubernetes/cfs-csi-driver.yaml
  1. 创建 StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cfs
provisioner: com.tencent.cloud.csi.cfs
reclaimPolicy: Retain
volumeBindingMode: Immediate
  1. 重新创建 PVC,指定 storageClassName: cfs。

  2. 检查并安装 CFS 存储类

如果确实需要使用腾讯云文件存储(CFS),确认 CFS 插件是否安装:

kubectl get pods -n kube-system | grep csi-cfs

如果没有输出,说明插件未安装。
Kubernetes-csi-tencentloud CFS 插件实现 CSI 的接口,可帮助您在容器集群中使用腾讯云文件存储。通过 CFS-CSI 扩展组件,您可以快速在容器集群中通过标准原生 Kubernetes 使用 CFS
安装腾讯云 CFS CSI 插件: 参考腾讯云官方文档安装
创建 CFS 存储类: 安装插件后,通常会自动创建 cfs 存储类。如果没有,可以手动创建:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cfs
provisioner: com.tencent.cloud.csi.cfs
reclaimPolicy: Retain
volumeBindingMode: Immediate

参考

Linux安装配置NFS方法
https://jingyan.baidu.com/article/e5c39bf5f3a8df39d660335e.html

03-21
### Kubernetes 使用 NFS 配置存储卷的最佳实践 #### 1. 动态配置 NFS 存储卷 为了在 Kubernetes 中动态配置 NFS 存储卷,通常会使用 `NFS-Subdir-External-Provisioner` 或类似的工具来实现自动化的 PV(Persistent Volume)创建过程。这种方案允许用户基于现有的 NFS 共享资源,在不手动干预的情况下完成存储分配。 以下是具体的操作流程: - **安装 NFS-Subdir-External-Provisioner**: 用户可以通过 Helm Chart 或 YAML 文件部署该组件到 Kubernetes 集群中[^2]。 - **定义 StorageClass**: 创建一个自定义的 `StorageClass` 对象,用于指定 NFS 提供程序的相关参数以及默认选项。例如: ```yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage provisioner: example.com/nfs-subdir-external-provisioner parameters: archiveOnDelete: "false" ``` - **验证部署状态**: 确认 `nfs-client-provisioner` 是否正常运行,可通过命令检查 Pod 的健康状况[^3]: ```bash kubectl get pods -n nfs-client-provisioner ``` #### 2. 手动挂载 NFS 卷 如果不需要动态配置功能,则可以选择静态方式绑定 NFS 资源至容器实例。这种方式适合于固定不变的工作负载或者测试环境下的简单集成需求。 操作步骤如下所示: - **准备 PersistentVolume (PV)**: 编写一份描述目标路径和服务地址的 YAML 定义文档并应用之: ```yaml apiVersion: v1 kind: PersistentVolume metadata: name: manual-nfs-pv spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: path: /exports/data server: nfsserver.example.com ``` - **关联 PersistentVolumeClaim (PVC)**: 接下来声明 PVC 请求对应大小的空间并与上述 PV 进行匹配连接: ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: manual-nfs-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 5Gi ``` 最后一步是在工作负载模板里引用这个 PVC 实现数据持久化支持。 --- ### 结合 Spark 和 Kubernetes 场景中的最佳实践 当涉及到像 Apache Spark 这样的大数据处理框架时,合理规划分布式计算节点之间的共享文件访问机制尤为重要。借助 NFS 可以为临时中间结果保存区域提供统一视图从而提升整体性能表现[^1]。 推荐做法包括但不限于以下几个方面: - 尽量减少单次读写的块尺寸以适应频繁的小规模 I/O 操作模式; - 利用多副本冗余策略增强可靠性防止因单一磁盘故障引发的任务失败风险; - 根据实际业务特点调整缓存命中率优化算法降低延迟开销; ```python from pyspark.sql import SparkSession spark = SparkSession.builder \ .appName("K8s-NFSSample") \ .config("spark.hadoop.fs.defaultFS", "file:///mnt/nfsshare/") \ .getOrCreate() ``` 以上代码片段展示了如何设置 Hadoop FileSystem 属性指向已挂接好的 NFS 目录位置作为输入/输出基址。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西京刀客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值