3-9-3-PV、PVC、SC资源

熊 0 阅读 容器

1. 基础概念

volume的基础使用 , 需要我们用户手动来向不同类型存储接口传递不同的参数 , 从而实现把外部存储映射到k8s上的一个volume对象 , 使得pod才能正常的挂载对应的存储卷 , 对应pod里的容器才能正常使用 ; 这种使用方式的前提是用户必须了解对应的存储系统 , 了解对应类型的存储接口 , 以及相关参数 ; 这使得用户在k8s上使用存储卷变得有些复杂 ; 为了简化这一过程 , 在k8s上使用pv和pvc资源来把对应底层存储接口给隐藏了 , 用户使用存储卷不再关心底层存储系统接口 ; 不管底层是那种类型的存储 , 用户只需面对一个pvc接口即可 ;

kubernetnes-volume-pv-pvc-sc-1
kubernetnes-volume-pv-pvc-sc-2

用户在创建pod时使用存储卷只需要关心对应名称空间的pvc对象 ; 而对应pv是需要集群管理管理员定义 ; 后端存储是专门的存储管理员负责管理 ; pv是k8s上的一种标准资源 , 全称叫做PersistentVolume翻译成中文就是持久存储卷 ; 它主要作用是把后端存储中的某个逻辑单元 , 映射为k8s上的pv资源 ; pv是集群级别的资源 ; 任意名称空间都可以直接关联某一个pv ; 关联pv的过程我们叫做绑定pv ; 而对应名称空间关联某一pv需要使用pvc资源来定义 ; pvc全称PersistentVolumeClaim的缩写 , 意思就是持久存储卷申请 ; 在一个名称空间下创建一个pvc就是把对应名称空间同集群上的某一pv做绑定 ; 一旦一个名称空间绑定了一个pv后 , 对应的pv就会从available状态转变成bond状态 , 其他名称空间将不能再使用 , 只有对应pv是available状态才能正常的被其他名称空间关联绑定 ; 简单讲pvc和pv的关系是一一对应的 , 一个pv只能对应一个pvc ; 至于同一名称空间下的多个pod是否能够同时使用一个PVC取决pv是否允许多路读写 , 对应pv是否支持多路读写取决后端存储系统 ; 不同类型的存储系统 , 对应访问模式也有所不同。访问模式有三种 , 单路读写(ReadWriteOnce简称RWO) , 多路读写(ReadWriteMany简称RWX) , 多路只读(ReadOnlyMany简称ROX) ;

2. PV、PVC使用流程

2.1 PV资源创建

示例 : pv资源创建

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-v1
  labels:
    storsystem: nfs-v1
    rel: stable
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes: ["ReadWriteOnce","ReadWriteMany","ReadOnlyMany"]
  persistentVolumeReclaimPolicy: Retain
  mountOptions:
  - hard
  - nfsvers=4.1
  nfs:
    path: /data/v1
    server: 192.168.71.34

pv是k8s的标准资源 , 其群组版本为v1,类型为PersistentVolume ;

  • spec.capacity.storage字段用来描述pv的存储容量 ;

  • volumeMode用来描述对应存储系统提供的存储卷类型接口 , 一般存储卷类型接口有两种 , 分别是Filesystem文件系统接口和Block块设备接口 ;

  • accessModes用来描述pv的访问模式 ;

  • presistentVolumeReclaimPolicy字段用来描述存储卷回收策略 , 持久卷回收策略有三种 :

    1. 一种是Delete , 表示当pvc删除以后 , 对应pv也随之删除 ;

    2. 第二种是Recycle,表示当pvc删除以后 , 对应pv的数据也随之被删除 ;

    3. 第三种是Retain , 表示当pvc被删除以后 , pv原封动 , 即pv也在 , 对应数据也在 ;

  • mountOptions字段用来指定挂载选项 ;

  • nfs表示后端存储为nfs , 对于不同类型的存储 , 对应的要传递的参数各不相同 , 对于nfs这种类型的存储 , 我们只需要指定其nfs服务器地址以及对应共享出来的文件路径 ;

以上配置就表示把nfs上的/data/v1目录映射到k8s上的pv , 对应pv的名称为nfs-pv-v1 ; 这里需要注意一点 , 在创建pv时 , 对应后端存储应该提前准备好 ;

应用配置清单

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl apply -f pv-v1-demo.yaml 
persistentvolume/nfs-pv-v1 created
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pv 
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nfs-pv-v1   1Gi        RWO,ROX,RWX    Retain           Available                          <unset>                          8s
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl describe pv nfs-pv-v1
Name:            nfs-pv-v1
Labels:          rel=stable
                 storsystem=nfs-v1
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    
Status:          Available
Claim:           
Reclaim Policy:  Retain
Access Modes:    RWO,ROX,RWX
VolumeMode:      Filesystem
Capacity:        1Gi
Node Affinity:   <none>
Message:         
Source:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    192.168.71.34
    Path:      /data/v1
    ReadOnly:  false
Events:        <none>
​

在pv的详细信息中能够看到 , 当前pv的状态为available , pv对应后端的存储是nfs , 对应存储的ip地址为192.168.71.34 , 当前pv对应后端存储的逻辑单元就是/data/v1 ;

2.2 PVC资源创建

示例 : 创建pvc

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs-pv-v1
  namespace: default
  labels:
    storsystem: nfs-v1
spec:
  accessModes:
    - ReadWriteMany
  volumeMode: Filesystem
  resources:
    requests:
      storage: 500Mi
  selector:
    matchLabels:
      storsystem: nfs-v1
      rel: stable

pvc也是k8s上的标准资源 , 对应的群组版本为v1,类型为PersistentVolumeClaim ;

  • spec.accessModes字段是用来指定其pvc的访问模式 , 一般这个模式是被pv的accessModes包含 , 也就说pvc的访问模式必须是pv的子集 , 即等于小于pv的访问模式 ;

  • resources用来描述对应pvc的存储空间限制 ,

    • requests用来描述对应pvc最小容量限制 ,

    • limits用来描述最大容量限制 ;

  • selector用来定义标签选择器 , 主要作用过滤符合对应标签的pv ;

    如果不定义标签选择器 , 它会在所有available状态的pv中 , 通过其容量大小限制以及访问模式去匹配一个最佳的pv进行关联 ;

应用配置清单

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# cat pvc-v1-demo.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs-pv-v1
  namespace: default
  labels:
    storsystem: nfs-v1
spec:
  accessModes:
    - ReadWriteMany
  volumeMode: Filesystem
  resources:
    requests:
      storage: 500Mi
  selector:
    matchLabels:
      storsystem: nfs-v1
      rel: stable
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl apply -f pvc-v1-demo.yaml 
persistentvolumeclaim/pvc-nfs-pv-v1 created
​
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pvc
NAME            STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
pvc-nfs-pv-v1   Bound    nfs-pv-v1   1Gi        RWO,ROX,RWX                   <unset>                 24s
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl describe pvc pvc-nfs-pv-v1
Name:          pvc-nfs-pv-v1
Namespace:     default
StorageClass:  
Status:        Bound
Volume:        nfs-pv-v1
Labels:        storsystem=nfs-v1
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      1Gi
Access Modes:  RWO,ROX,RWX
VolumeMode:    Filesystem
Used By:       <none>
Events:        <none>
​

这里显示pvc的大小是pvc最大容量显示 , 默认不限制最大容量就是其pv的最大容量 ; 从上面的显示可以看到对应pv被pvc绑定以后 , 其状态就变成了bound ;

2.3 pod关联pvc

示例: 创建pod关联pvc , 并在其pod容器里挂载pvc

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# cat redis-demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: redis-demo
  labels:
    app: redis
spec:
  containers:
  - name: redis
    image: redis:alpine
    volumeMounts:
    - mountPath: /data
      name: redis-data
  volumes:
  - name: redis-data
    persistentVolumeClaim:
      claimName: pvc-nfs-pv-v1
​

在pod里关联pvc , 只需要指定后端存储类型为persistentVolumeClaim , 然后指定对应的pvc名称 ;

应用资源清单

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl apply -f redis-demo.yaml 
pod/redis-demo created
​
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pod
NAME         READY   STATUS    RESTARTS   AGE
redis-demo   1/1     Running   0          98s
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl describe pod redis-demo
Name:             redis-demo
Namespace:        default
Priority:         0
Service Account:  default
Node:             k8s-node01.cbear.top/192.168.71.35
Start Time:       Sun, 02 Jun 2024 11:36:52 +0000
Labels:           app=redis
Annotations:      <none>
Status:           Running
IP:               172.20.3.24
IPs:
  IP:  172.20.3.24
Containers:
  redis:
    Container ID:   containerd://09296d1b325d3e44069f7e969e86a9cbc2a72854ba34a3a9b3e1dc64811d3ea0
    Image:          redis:alpine
    Image ID:       docker.io/library/redis@sha256:4bed291aa5efb9f0d77b76ff7d4ab71eee410962965d052552db1fb80576431d
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sun, 02 Jun 2024 11:37:23 +0000
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /data from redis-data (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-qjvdg (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True 
  Initialized                 True 
  Ready                       True 
  ContainersReady             True 
  PodScheduled                True 
Volumes:
  redis-data:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  pvc-nfs-pv-v1
    ReadOnly:   false
  kube-api-access-qjvdg:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  2m8s  default-scheduler  Successfully assigned default/redis-demo to k8s-node01.cbear.top
  Normal  Pulling    2m7s  kubelet            Pulling image "redis:alpine"
  Normal  Pulled     97s   kubelet            Successfully pulled image "redis:alpine" in 29.902s (29.902s including waiting)
  Normal  Created    97s   kubelet            Created container redis
  Normal  Started    97s   kubelet            Started container redis
​

可以看到对应pod已经正常运行起来 ; 从详细信息中可以看到对应pod使用的volumes类型为PersistentVolumeClaim , 对应名称为pvc-nfs-pv-v1 ; 对应容器以读写方式挂载了对应存储卷 ;

测试 : 在redis-demo上产生数据 , 看看是否能够正常保存到nfs服务器上

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pod -o wide
NAME         READY   STATUS    RESTARTS   AGE     IP            NODE                   NOMINATED NODE   READINESS GATES
redis-demo   1/1     Running   0          4m31s   172.20.3.24   k8s-node01.cbear.top   <none>           <none>
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pod
NAME         READY   STATUS    RESTARTS   AGE
redis-demo   1/1     Running   0          8m44s
​
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl exec -it redis-demo -- /bin/sh
/data # redis-cli
127.0.0.1:6379> set mykey "this is test key"
OK
127.0.0.1:6379> get mykey
"this is test key"
127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> exit
/data # ls
dump.rdb
/data # exit
​

在nfs服务器上查看对应目录下是否有dump.rdb文件产生

root@harbor:~# ls /data/v1/
dump.rdb

可以看到 , redis上产生的快照文件在nfs服务器上有对应的文件存在 ;

2.4 测试效果

测试 : 删除pod , 看看对应文件是否还在

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl delete -f redis-demo.yaml 
pod "redis-demo" deleted
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pods
No resources found in default namespace.
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# ssh root@192.168.71.34

root@harbor:~# ll /data/v1/
total 12
drwxr-xr-x  2 lxd  root  4096 Jun  2 12:00 ./
drwxr-xr-x 11 root root  4096 Jun  2 09:59 ../
-rw-r--r--  1 lxd  xiong  121 Jun  2 12:00 dump.rdb
root@harbor:~# exit
logout
Connection to 192.168.71.34 closed.

可以看到删除了pod对应快照文件在nfs服务器还是存在 ;

绑定节点 , 重新新建pod , 看看对应是否能够自动应用快照中的数据

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# cat redis-demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: redis-demo
  labels:
    app: redis
spec:
  nodeName: k8s-node02.cbear.top
  containers:
  - name: redis
    image: redis:alpine
    volumeMounts:
    - mountPath: /data
      name: redis-data
  volumes:
  - name: redis-data
    persistentVolumeClaim:
      claimName: pvc-nfs-pv-v1
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl apply -f redis-demo.yaml 
pod/redis-demo created

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pods -o wide
NAME         READY   STATUS    RESTARTS   AGE    IP            NODE                   NOMINATED NODE   READINESS GATES
redis-demo   1/1     Running   0          112s   172.20.4.15   k8s-node02.cbear.top   <none>           <none>
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl exec -it redis-demo -- /bin/sh
/data # redis-cli 
127.0.0.1:6379> get mykey
"this is test key"
127.0.0.1:6379> exit
/data # ls
dump.rdb
/data # exit

可以看到新建的pod能够正常读取到nfs上的快照文件并应用到内存中 ;

删除pvc , 看看对应pv是否被删除

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pvc 
NAME            STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
pvc-nfs-pv-v1   Bound    nfs-pv-v1   1Gi        RWO,ROX,RWX                   <unset>                 45m
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl delete pvc pvc-nfs-pv-v1
persistentvolumeclaim "pvc-nfs-pv-v1" deleted
^Croot@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pvc
NAME            STATUS        VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
pvc-nfs-pv-v1   Terminating   nfs-pv-v1   1Gi        RWO,ROX,RWX                   <unset>                 46m
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pv
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nfs-pv-v1   1Gi        RWO,ROX,RWX    Retain           Bound    default/pvc-nfs-pv-v1    

可以看到现在pvc的状态变成了terminating , 但对应pvc还在并没有被删除 ; 对应pv还处于绑定状态 ;

删除pod , 看看对应pvc是否会被删除

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pod
NAME         READY   STATUS    RESTARTS   AGE
redis-demo   1/1     Running   0          10m
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl delete pod redis-demo
pod "redis-demo" deleted
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pvc
No resources found in default namespace.
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pv
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nfs-pv-v1   1Gi        RWO,ROX,RWX    Retain           Released   default/pvc-nfs-pv-v1                  <unset>                          123m

可以看到删除对应pod以后 , pvc就立即删除了 ; 对应pvc被删除以后 , 对应pv的状态就从bound状态转变为Released状态 , 表示等待回收 ; 我们在资源清单中使用的是Retain回收策略 , pv和pvc都是我们人工手动回收 ;

删除pv,看看对应数据是否会被删除

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pv
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nfs-pv-v1   1Gi        RWO,ROX,RWX    Retain           Released   default/pvc-nfs-pv-v1                  <unset>                          123m
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pv
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nfs-pv-v1   1Gi        RWO,ROX,RWX    Retain           Released   default/pvc-nfs-pv-v1                  <unset>                          127m

root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl delete pv nfs-pv-v1
persistentvolume "nfs-pv-v1" deleted
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# kubectl get pv
No resources found
root@k8s-master01:/opt/k8s-data/yaml/pv-pvc# ssh root@192.168.71.34

Last login: Sun Jun  2 12:01:02 2024 from 192.168.71.29
root@harbor:~# ll /data/v1/
total 12
drwxr-xr-x  2 lxd  root  4096 Jun  2 12:14 ./
drwxr-xr-x 11 root root  4096 Jun  2 09:59 ../
-rw-r--r--  1 lxd  xiong  121 Jun  2 12:14 dump.rdb

可以看到删除了pv,对应快照文件并没有清除 ;

3. SC使用

SC是StorageClass的缩写,表示存储类 ; 这种资源主要用来对pv资源的自动供给提供接口 ; 所谓自动供给是指用户无需手动创建pv,而是在创建pvc时对应pv会由persistentVolume-controller自动创建并完成pv和pvc的绑定 ; 使用sc资源的前提是对应后端存储必须支持restfull类型接口的管理接口,并且pvc必须指定对应存储类名称来引用SC ; 简单讲SC资源就是用来为后端存储提供自动创建pv并关联对应pvc的接口 ; 如下图

kubernetnes-volume-pv-pvc-sc-3

使用sc动态创建pv,对应pvc必须也是属于对应的sc ; 上图主要描述了用户在创建pvc时,引用对应的sc以后,对应sc会调用底层存储系统的管理接口,创建对应的pv并关联至对应pvc ;

示例 : 创建sc资源

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/glusterfs
parameters:
  resturl: "http://127.0.0.1:8081"
  clusterid: "630372ccdc720a92c681fb928f27b53f"
  restauthenabled: "true"
  restuser: "admin"
  secretNamespace: "default"
  secretName: "heketi-secret"
  gidMin: "40000"
  gidMax: "50000"
  volumetype: "replicate:3"

上述是官方文档中的一个示例,在创建sc资源时,对应群组是storage.k8s.io/v1,类型为StorageClass;provisioner字段用于描述对应供给接口名称;parameters用来定义向对应存储管理接口要传递的参数;

在pvc资源中引用SC资源对象

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: foo-pvc
  namespace: foo
spec:
  storageClassName: "slow"
  volumeName: foo-pv
  ...

在创建pvc时用storageClassName字段来指定对应的SC名称即可


参考链接

容器编排系统K8s之PV、PVC、SC资源 - Linux-1874 - 博客园 (cnblogs.com)