StorageClass


一.StorageClass持久化存储介绍

1.是什么

StorageClass又称为PV动态供给,是k8s1.4之后引入的一个新资源,主要实现了存储卷PV按需创建,动态创建。一旦有pvc资源,storageclass会自动创建pv

2.为什么用

以前静态的PV和PVC都需要手动来创建,因为pv和pvc一一对应,一个pv一旦被绑定就不会再被其他pvc绑定,因此当pvc逐渐增多,pv也会同比增加,这时候就需要自动创建pv,就要用到stroageclass

3.和pv、pvc关系

  • StorageClass创建的pvc,只要pvc不删除,即使控制器删除、pod删除重建还是会找到原来的pvc,并且持久化数据,一旦pvc删除,下次重建statefulset控制器,重新使用StorageClass创建pvc,pvc的路径就会发生改变,当然pvc一般是不会删除的
  • StorageClass可以配合Statefulset控制器为每一个有状态的Pod自动创建PVC并进行挂载,使每一个有状态的pod数据都实现持久化
  • StorageClass只能配合statefulset为每个Pod自动创建PVC,因为只有statefulset有volumeClaimTemplates配置选项
StorageClass依赖一个自动配置程序,比如我们的后端存储是nfs,那么就需要一个nfs-client的自动配置程序,如果是gfs,就需要gfs-client自动配置程序,也称为Provisione,StorageClass也是靠这个Provisione自动配置程序来实现自动创建PV持久卷

自动创建的PV以{namespace}-${pvcname}-${pvname}进行命名到服务器上创建相应的目录

当pv被回收后会以archieved-${namespace}-${pvcname}-${pvname}格式存在服务器上

storageclass自动创建的pv命名规则:pvc-${PVC_UID}

当程序删除后,PVC、PV不会消失

4.部署步骤

1.编写nfs-client-provisioner程序的rbac授权角色账号

2.编写nfs-client-provisioner程序的deployment资源文件,与rbac账号进行绑定,使nfs-client-provisioner对pv、pvc有增删改查权限(这是server account存在的原因)

3.创建一个StorageClass资源关联nfs-client,自动创建PV时,就将PV存储到了nfs-client对应的nfs存储上

4.编写一个PVC yaml文件,验证是否能自动创建PV

5.编写一个无状态的yaml文件,使用PVC挂载数据

6.在statefulset里定义StorageCLass,实现每个pod都使用单独的pvc存储

5.原理

一旦有pvc资源文件,storageclass资源会去找自动配置程序也就是nfs-client-provisioner,由nfs-client-provisioner去创建一个PV,并将PV的数据存储在对应的nfs设备上,然后再从PV中分出一个PVC-1,最后挂载到Pod上实现持久化存储

部署storageclass

一、准备NFS服务器(本地虚拟机)

首先需要在一台虚拟机上搭建NFS服务(可以是集群外的虚拟机,也可以是集群内的节点,测试环境推荐用集群内节点简化网络)。

1. 安装NFS服务(以Ubuntu/Debian为例)

# 安装NFS服务器
sudo apt update && sudo apt install -y nfs-kernel-server

# 若为CentOS/RHEL
# sudo yum install -y nfs-utils

2. 创建NFS共享目录

# 创建共享目录(例如 /data/nfs-share)
sudo mkdir -p /data/nfs-share
sudo chmod 777 /data/nfs-share  # 测试环境简化权限,生产环境需按需配置
sudo chown nobody:nogroup /data/nfs-share  # NFS默认用户

3. 配置NFS共享(exports文件)

编辑/etc/exports,允许K8s集群所有节点访问:

sudo vim /etc/exports

添加如下内容(替换<k8s节点网段>为实际集群节点的IP网段,例如192.168.8.0/24,测试环境可直接用*允许所有IP):

/data/nfs-share  <k8s节点网段>(rw,sync,no_subtree_check,no_root_squash)
# 示例(允许所有IP访问,仅测试用):
# /data/nfs-share  *(rw,sync,no_subtree_check,no_root_squash)
  • rw:读写权限;sync:同步写入;no_root_squash:允许root用户操作(测试环境简化)

4. 生效配置并启动服务

# 生效exports配置
sudo exportfs -r

# 重启NFS服务
sudo systemctl restart nfs-kernel-server  # Ubuntu/Debian
# sudo systemctl restart nfs-server  # CentOS/RHEL

# 确认共享生效
showmount -e localhost
# 输出应包含:/data/nfs-share  <你的网段>

二、K8s节点安装NFS客户端

所有K8s节点(包括控制平面和工作节点)必须安装NFS客户端,否则无法挂载NFS共享:

# Ubuntu/Debian
sudo apt install -y nfs-common

# CentOS/RHEL
sudo yum install -y nfs-utils

验证节点能否访问NFS服务器:

# 在K8s节点执行,替换<NFS服务器IP>为NFS服务器的IP
showmount -e <NFS服务器IP>
# 若输出共享目录,则说明网络和权限正常

注意事项

# Kubernetes 在 1.21 版本中移除了 selfLink 字段(该字段用于标识对象的 URL 路径,因安全和冗余性被废弃)。如果你使用的 nfs-client-provisioner 版本较旧,其代码逻辑依赖 selfLink 字段来生成 PVC(PersistentVolumeClaim)的引用,当集群版本 ≥1.21 时,由于 selfLink 不存在,就会触发错误。

#编辑 kube-apiserver 的静态 Pod 配置(通常位于 /etc/kubernetes/manifests/kube-apiserver.yaml):

vim /etc/kubernetes/manifests/kube-apiserver.yaml

#在 spec.containers.command 中添加参数:
- --feature-gates=RemoveSelfLink=false

#重启 kubelet 使配置生效:
systemctl restart kubelet

三、部署NFS Provisioner(动态供应器)

# 创建安装目录
mkdir nfs-subdir
cd nfs-subdir

使用nfs-subdir-external-provisioner作为Provisioner,负责动态创建PV。

1.用户角色授权

创建角色授权是为了让pod(内含nfs-client-provisioner工具)对pv和pvc有操作权限

vim rbac.yaml
---
#创建名称为nfs-client-provisioner的serviceaccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

2.nfs-client-provisioner资源

创建nfs-client-provisioner工具

修改env部分:

env:
  - name: PROVISIONER_NAME
    value: k8s-sigs.io/nfs-subdir-external-provisioner  # 固定值,后续StorageClass需匹配
  - name: NFS_SERVER
    value: <NFS服务器IP>  # 替换为你的NFS服务器IP
  - name: NFS_PATH
    value: /data/nfs-share  # 替换为NFS共享目录
vim nfs-client-provisioner.yaml

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-client-provisioner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      #绑定service account,使得自动化配置工具能访问pv、pvc等资源
      serviceAccountName: nfs-client-provisioner  
      containers:
        - name: nfs-client-provisioner
          # 下载nfs-client-provisioner工具
          image: quay.io/external_storage/nfs-client-provisioner:latest
          env:
            - name: PROVISIONER_NAME #给nfs-client-provisioner起个名字
              value: nfs-storage-01
            - name: NFS_SERVER
              value: 192.168.8.12 #nfs server地址
            - name: NFS_PATH
              value: /data/nfs-share #nfs共享路径
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes #具体挂载路径
      volumes:       #定义一个nfs类型的volume,将nfs挂载到容器中
        - name: nfs-client-root
          nfs:
            server: 192.168.8.12
            path: /data/nfs-share

3.storageclass资源

vim storage-test.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storageclass
provisioner: nfs-storage-01  #匹配找到env中PROVISIONER_NAME的值,通过其创建pv
reclaimPolicy: Retain        #回收策略

storageclass资源通过provisioner绑定2中的nfs-client-provisioner资源,nfs-client-provisioner资源通过serviceAccountName绑定1中的用户进而对pvc、pv进行操作

4.创建所有资源

1.创建资源
[root@k8s-master1 storageclass]# kubectl apply -f ./
2.查看nfs-client-provisioner资源的状态
[root@k8s-master1 storageclass]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-554cc7d84d-j4966   1/1     Running   0          35m
3.查看storageclass资源的状态
[root@k8s-master1 storageclass]# kubectl get sc 
NAME               PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-storageclass   nfs-storage-01   Retain          Immediate           false                  7s

以下5、6、7都是对上面资源的测试


5.手动创建pvc测试

5.1创建pvc资源
1.编写yaml文件
[root@k8s-master1 storageclass]# vim test-pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: test-pvc
spec:
  storageClassName: nfs-storageclass            #指定storageclass的名称
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

2.创建资源
[root@k8s-master1 storageclass]# kubectl create -f test-pvc.yaml 
persistentvolumeclaim/test-pvc created
5.2查看nfs-client日志输出

image-20251112210311279

可以看到日志最后一行的输出,产生一个事件信息,一个类型为pvc、命名空间为default、名称为test-pvc、的pvc通过storageclass建立了,storageclass为test-pvc自动创建了一个pv

storageclass自动创建的pv命名规则:pvc-${PVC_UID}

5.3查看pv、pvc状态

image-20251112205807905

pv创建成功后,会在2中nfs存储路径创建一个{namespace}-${pvcname}-${pvname}这样命名规则的目录存储pvc的数据

image-20251112205844135

6.使用deployment资源引用创建的PVC资源

# 1.编写yaml文件
[root@k8s-master1 storageclass]# vim nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-pv
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx-pv
        image: nginx:1.17.1
        volumeMounts:
        - name: nginx-data
          mountPath: /usr/share/nginx
      volumes:
      - name: nginx-data
        persistentVolumeClaim:
          claimName: test-pvc           #指定pvc
          readOnly: false

# 2.创建资源
[root@k8s-master1 storageclass]# kubectl create -f nginx-deploy.yaml
deployment.apps/nginx-pv created

# 3.进入容器产生数据
[root@k8s-master1 storageclass]# kubectl exec -it nginx-pv-775f4c5fb-vs45q -- bash
root@nginx-pv-775f4c5fb-vs45q:/# cd /usr/share/nginx/
root@nginx-pv-775f4c5fb-vs45q:/usr/share/nginx# echo "this is web" >> index.html

# 4.查看nfs存储上是否有数据产生
root@master:/data/nfs-share# ll default-test-pvc-pvc-e05afb6c-0e8d-4cde-8f77-a621a9816d9c
total 12
drwxrwxrwx 2 root   root    4096 Nov 12 21:07 ./
drwxrwxrwx 3 nobody nogroup 4096 Nov 12 20:55 ../
-rw-r--r-- 1 root   root       6 Nov 12 21:07 index.html

7.使用StatefulSet控制器引入StorageClass

1.使用statfulset部署nginx每个pod使用不同的pvc存储数据

2.如果我们使用手动指定pvc的方式当我们pod为多实例的时候多个pod使用的是一个pvc但是我们使用statefulset时storageclass会为我们每一个Pod创建一个pv和pvc造成数据隔离因为在不同的主机

3.statefulset使用storageclass需要用到volumeClaimTemplates配置项volumeClaimTemplates配置项是statfulset控制器独有的因此需要配置在statefulset.spec下volumeClaimTemplates下的配置和pvc调用storageclass一致定义好volumeClaimTemplates后直接在pod中定义volumeMounts填写volumeClaimTemplates中的pvc名称即可对每个Pod分配一个pvc进行存储
7.1创建statefulset资源
[root@k8s-master1 storageclass]# vim nginx-statfuleset.yaml 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-storage-stat
spec:
  replicas: 3
  serviceName: "nginx"
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        volumeMounts:
        - name: nginx-storage-test-pvc
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates: #在statfulset.spec下定义pvc模板,里面的配置参数和pvc的一致
    - metadata:     #定义元数据
        name: nginx-storage-test-pvc    #pvc的名称,要与volumeMounts中的名称一致    
      spec: #定义属性
        storageClassName: nfs-storageclass  #指定使用哪个storageclass
        accessModes: #访问模式为多主机可读写
        - ReadWriteMany
        resources: #分配的资源大小
          requests:
            storage: 1Gi 

[root@k8s-master1 storageclass]# kubectl create -f nginx-statfuleset.yaml 
statefulset.apps/nginx-storage-stat created

[root@k8s-master1 storageclass]# kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-c479ffb76-z9j8d   1/1     Running   0          15m
nginx-pv-775f4c5fb-vs45q                 1/1     Running   0          6m18s
nginx-storage-stat-0                     1/1     Running   0          64s
nginx-storage-stat-1                     1/1     Running   0          41s
nginx-storage-stat-2                     1/1     Running   0          26s
7.2查看是否为每个pod分配了pvc

使用satefuleset部署的pod会在nfs目录下生成对应不同的pod目录,pod0目录挂载在pod0上,pod1目录挂载在pod1上,数据隔离

kubectl get pv

ll /data/nfs-share/

image-20251112211158812