K8S实战:NFS+StorageClass+PV/PVC+Deployment

在Kubernetes生产环境中,Pod本身是无状态的,一旦重启或漂移到其他节点,容器内的数据就会丢失。

如何为Pod提供持久化存储,并且做到多副本共享、动态扩容、安全可靠,是每个K8s运维人员必须掌握的技能。

本文将带你从零开始手把手实操,通过NFS + StorageClass + PV/PVC + Deployment的组合拳,搭建一套完整的K8s持久化存储方案。**从NFS服务配置、StorageClass策略定义,到PV/PVC绑定、Deployment挂载验证,再到扩容测试和故障排查,全程干货,每一行命令都有详解,每一个坑都帮你踩过。**无论你是K8s新手还是想深入理解存储机制的老兵,这篇实战都能让你对K8s存储体系有一个清晰、直观的认知。

了解内容:

可以帮助更好理解后面的配置

  1. NFS (Network File System): NFS 用于在网络上共享文件。它允许多个 Pod 访问同一个文件系统,适合需要共享存储的场景。
  2. StorageClass: StorageClass 是 Kubernetes 中用来定义存储类型的一种资源定义。它为 PersistentVolume (PV) 提供了动态配置的能力。通过 StorageClass,可以指定使用 NFS 作为存储后端,并定义存储的属性(如存储类别、访问模式、回收策略等)。
  3. PersistentVolume (PV): PV 是一个具体的存储资源,它是集群中的一个物理存储。管理员创建 PV 资源以定义可用的存储卷,这些卷不依赖于 Pod 的生命周期。
  4. PersistentVolumeClaim (PVC): PVC 是用户对 PV 的请求。它相当于一个租赁协议,用户可以声明所需的存储大小、访问模式等。Kubernetes 会根据 PVC 的要求,绑定一个合适的 PV。
  5. Deployment: Deployment 是用于管理应用程序副本的 Kubernetes 控制器。它可以保证在任何时候都运行所需数量的 Pod,确保应用的高可用性。Deployment 可以通过 PVC 来挂载所需的存储,使得应用可以利用管理的数据。

关系:

StorageClass 定义了一种存储策略。

PV 是具体的存储实现,依据 StorageClass 中的定义来创建。

PVC 用于请求所需的存储,Kubernetes 会查找匹配的 PV 并进行绑定。

Deployment 可通过 PVC 挂载所请求的存储,以供容器应用使用。

这种组合提供了一种灵活的存储管理方式,能够动态地供应和管理存储卷。

配置:

1.NFS

bash 复制代码
[root@k8s-master01 20]# cat /etc/exports
/opt/nfs	192.168.64.0/24(rw,no_root_squash,sync,no_subtree_check)#之前的
/data/nfs	192.168.64.0/24(rw,sync,no_subtree_check)#这一行
[root@k8s-master01 20]# showmount -e
Export list for k8s-master01:
/data/nfs 192.168.64.0/24
/opt/nfs  192.168.64.0/24


[root@k8s-master01 20]# systemctl is-active nfs-server.service 
active

2.StorageClass

bash 复制代码
[root@k8s-master01 20]# cat 1.nfs-storageclass.yml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storageclass
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer

no-provisioner让PV必须手动创建,Retain保证数据安全不自动删除,WaitForFirstConsumer让PVC等到Pod使用时才绑定,三者配合实现"静态供给+延迟绑定+数据保护"的存储管理策略。

应用&验证

bash 复制代码
[root@k8s-master01 20]# kubectl apply -f 1.nfs-storageclass.yml 
storageclass.storage.k8s.io/nfs-storageclass created

[root@k8s-master01 20]# kubectl get sc
NAME               PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
nfs-storageclass   kubernetes.io/no-provisioner   Retain          WaitForFirstConsumer   false                  27s

3.PV

bash 复制代码
[root@k8s-master01 20]# cat 2.nfs-pv.yml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  accessModes: [ReadWriteMany]
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  storageClassName: nfs-storageclass
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /data/nfs
    server: 192.168.64.11

应用&验证

bash 复制代码
[root@k8s-master01 20]# kubectl apply -f 2.nfs-pv.yml 
persistentvolume/nfs-pv created

[root@k8s-master01 20]# kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS       VOLUMEATTRIBUTESCLASS   REASON   AGE
nfs-pv   10Gi       RWX            Retain           Available           nfs-storageclass   <unset>   

4.PVC

bash 复制代码
[root@k8s-master01 20]# cat 3.nfs-pvc.yml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes: [ReadWriteMany]
  resources:
    requests:
      storage: 4Gi
  volumeMode: Filesystem
  storageClassName: nfs-storageclass

storageClassName 是 PV 和 PVC 的"接头暗号",用于将存储请求精准路由到特定类型的存储后端(如 NFS、SSD、Ceph),防止不同存储类型或不同租户的 PV 被错误绑定,同时支持动态供给和静态供给的区分。这个是针对生产环境

针对该实验,这里就是指定了该 PV/PVC 属于 nfs-storageclass 这个存储类别,只有相同类别的 PV 和 PVC 才能互相绑定,从而实现存储类型的精准匹配与隔离。

如果不写一定要都不写,就是也应该可以匹配到。但是为了贴合现实还是写。

应用&验证

bash 复制代码
[root@k8s-master01 20]# kubectl apply -f 3.nfs-pvc.yml 
persistentvolumeclaim/nfs-pvc created

[root@k8s-master01 20]# kubectl get pvc
NAME      STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS       VOLUMEATTRIBUTESCLASS   AGE
nfs-pvc   Pending                                      nfs-storageclass   <unset>                 8s

由于在声明 StorageClass 时,将 volumeBindingMode 设置为 WaitForFirstConsumer ,则需要等待

Pod 使用 PVC 时才绑定 PV,所以现在的PVC是Pending状态,没有绑定对应的Pod。

但如果 volumeBindingMode 设置为 Immediate ,则会立即绑定,显示为Bound

5.Deployment

bash 复制代码
[root@k8s-master01 20]# cat 4.deploy.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-deploy
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx-app
          image: hb.reg.com/k8s/nginx:1.27.4
          volumeMounts:
            - name: nfs-volume
              mountPath: /usr/share/nginx/html
      volumes:
        - name:  nfs-volume #要和挂载的卷名字一样
          persistentVolumeClaim:	# 固定关键字,表示"这个卷来自PVC"
            claimName: nfs-pvc

应用&验证

bash 复制代码
[root@k8s-master01 20]# kubectl apply -f 4.deploy.yml 
deployment.apps/nfs-deploy created

[root@k8s-master01 20]# kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS       VOLUMEATTRIBUTESCLAS
nfs-pv   10Gi       RWX            Retain           Bound    default/nfs-pvc   nfs-storageclass   <unset>             
[root@k8s-master01 20]# kubectl get pod
NAME                         READY   STATUS              RESTARTS   AGE
nfs-deploy-f4d77f4b7-n62wb   0/1     ContainerCreating   0          14s

可以看到pv已经成功绑定,但是pod没有成功running

6.解决问题

最方便的是:kubectl describe 来查看,看events就行

bash 复制代码
[root@k8s-master01 20]# kubectl describe pod nfs-deploy-f4d77f4b7-n62wb
。。。。。。
Events:
  Type     Reason       Age   From               Message
  ----     ------       ----  ----               -------
  Normal   Scheduled    115s  default-scheduler  Successfully assigned default/nfs-deploy-f4d77f4b7-n62wb to k8s-node0
  Warning  FailedMount  115s  kubelet            MountVolume.SetUp failed for volume "nfs-pv" : mount failed: exit sta
Mounting command: mount
Mounting arguments: -t nfs 192.168.64.11:/data/nfs /var/lib/kubelet/pods/8fdfec88-c4c9-4bab-83cb-95451ba83537/volumes/
Output: Created symlink '/run/systemd/system/remote-fs.target.wants/rpc-statd.service' → '/usr/lib/systemd/system/rpc-
mount.nfs: mounting 192.168.64.11:/data/nfs failed, reason given by server: No such file or directory
  Warning  FailedMount  51s (x7 over 114s)  kubelet  MountVolume.SetUp failed for volume "nfs-pv" : mount failed: exit
Mounting command: mount
Mounting arguments: -t nfs 192.168.64.11:/data/nfs /var/lib/kubelet/pods/8fdfec88-c4c9-4bab-83cb-95451ba83537/volumes/
Output: mount.nfs: mounting 192.168.64.11:/data/nfs failed, reason given by server: No such file or directory

可以看到问题就是没有/data/nfs 这个目录导致挂载不上

bash 复制代码
[root@k8s-master01 20]# mkdir -p /data/nfs

7.验证nfs,pod等资源

验证

bash 复制代码
[root@k8s-master01 20]# kubectl delete -f 4.deploy.yml 
deployment.apps "nfs-deploy" deleted from default namespace
[root@k8s-master01 20]# kubectl apply -f 4.deploy.yml 
deployment.apps/nfs-deploy created
[root@k8s-master01 20]# kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
nfs-deploy-f4d77f4b7-djvpq   1/1     Running   0          11s

现在就可行了。

curl验证:

bash 复制代码
[root@k8s-master01 20]# kubectl get pod -owide
NAME                         READY   STATUS    RESTARTS   AGE     IP              NODE         NOMINATED NODE   READINESS GATES
nfs-deploy-f4d77f4b7-djvpq   1/1     Running   0          7m48s   10.244.85.254   k8s-node01   <none>           <none>

[root@k8s-master01 20]# curl 10.244.85.254  
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.27.4</center>
</body>
</html>

验证nfs:

自定义首页在共享目录,看能不能自己加载到容器

bash 复制代码
[root@k8s-master01 20]# echo welcome mynginx,nfs active > /data/nfs/index.html 
[root@k8s-master01 20]# curl 10.244.85.254  
welcome mynginx,nfs active


#从容器内部也可以验证
[root@k8s-master01 20]# kubectl exec -it nfs-deploy-f4d77f4b7-djvpq -- /bin/bash
root@nfs-deploy-f4d77f4b7-djvpq:/# 
root@nfs-deploy-f4d77f4b7-djvpq:/# cat /usr/share/nginx/html/index.html  
welcome mynginx,nfs active
root@nfs-deploy-f4d77f4b7-djvpq:/# 

8.案例链路图(总结):

整个实验流程分为五个阶段:

首先在NFS服务器上创建共享目录/data/nfs并启动NFS服务,作为存储后端;

然后创建StorageClass定义存储策略(静态供给、保留数据、延迟绑定);

接着管理员手动创建PV,指定NFS服务器IP和共享路径,并声明storageClassName: nfs-storageclass

之后用户创建PVC,通过相同的storageClassName和匹配的accessModes(RWX)+容量(4Gi≤10Gi)与PV绑定(但由于WaitForFirstConsumer,PVC会保持Pending直到Pod使用);

最后创建Deployment,Pod通过volumes.persistentVolumeClaim引用PVC,触发PV-PVC绑定,Pod成功挂载NFS存储到容器内/usr/share/nginx/html,验证通过自定义index.html页面确认共享存储生效

9.一些扩展:

不知道大家有没有印象刚刚设置deployment资源清单时,我没有设置有几个副本,那么会默认一个,如果在生产环境中一定不是一个。

在生产环境,可能因为改业务的浮动,我们会动态调整副本数量(eg.写一个shell脚本来监控管理,某个值在什么范围内副本增加/减少到几个)。

我们现在来模拟一下,但是因为这个只是简单的实验环境,没有具体业务,所以直接使用命令行来增加副本。

目的是了解真实业务&理解pod/Deplyment和PV之间的联系

bash 复制代码
[root@k8s-master01 20]# kubectl scale deployment nfs-deploy --replicas=5
deployment.apps/nfs-deploy scaled
[root@k8s-master01 20]# 
[root@k8s-master01 20]# 
[root@k8s-master01 20]# kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
nfs-deploy-f4d77f4b7-b7j92   1/1     Running   0          7s
nfs-deploy-f4d77f4b7-c9582   1/1     Running   0          7s
nfs-deploy-f4d77f4b7-djvpq   1/1     Running   0          20m
nfs-deploy-f4d77f4b7-plstj   1/1     Running   0          7s
nfs-deploy-f4d77f4b7-rhwbt   1/1     Running   0          7s

Deployment管Pod数量,PVC管存储申请,PV管实际存储,Pod通过PVC使用PV,多个Pod可共享一个PV(需RWX支持)。

Tips:

重点是Pod通过PVC使用PV,可以返回定义Pod的清单(Deployment清单),该清单会定义卷并挂载卷,这个卷会定义PVC,即按照哪个持久卷声明去选择PV(持久卷),而PVC资源清单就会定义accessModes是什么,一些可以共享PV,一些只能一个PV给一个Pod使用(比如RWO模式)。

而PVC会自己去匹配符合的PV,或者直接在PVC去声明要使用哪个PV(但是也要符合accessMode+PV实际容量大于等于PVC)。如下

text 复制代码
方式一(自动匹配):
PVC(无volumeName)→ 系统自动筛选(容量+模式+class一致)→ 绑定最佳PV

方式二(手动指定):
PVC(有volumeName)→ 验证指定PV(容量+模式+class一致)→ 强制绑定该PV
相关推荐
BerrySen1781 小时前
一个 JDBC 参数引发的架构演进:Apache SeaTunnel 如何解决数据同步中的“定时 Flush”难题
运维·自动化·zabbix
三言老师1 小时前
CentOS7.9 + Kubernetes 1.18.20 -清理所有 docker / containerd 残留
docker·容器·kubernetes
草莓熊Lotso2 小时前
【Linux网络】从0手写Reactor反应堆(二):完善核心细节——ET非阻塞读写、分层架构与回调机制
linux·运维·服务器·网络·c++·tcp/ip·架构
AAA@峥2 小时前
K8s 配置管理实战:ConfigMap 与 Secret 完整使用指南
kubernetes·云计算
xlq223223 小时前
高并发服务器day21
运维·服务器
Brilliantwxx3 小时前
【Linux】 第一个程序终端进度条
linux·运维·服务器
AI创界者3 小时前
【网络安全运维】Kali Linux 下 Medusa(美杜莎)工具的高效部署、故障排查与安全测试实战
linux·运维·web安全
showyoui3 小时前
K8s 集群迁移踩坑:Istio 升级后 nginx 403
网络·docker·kubernetes·istio·service_mesh
FinelyYang3 小时前
CentOS 7.6 自建 LiveKit 部署指南
linux·运维·centos