Kubernetes(k8s)存储管理之数据卷volumes(五):动态制备-存储类StorageClass

发布时间 2023-03-22 19:06:56作者: 人生的哲理

一.系统环境

服务器版本 docker软件版本 Kubernetes(k8s)集群版本 CPU架构
CentOS Linux release 7.4.1708 (Core) Docker version 20.10.12 v1.21.9 x86_64

Kubernetes集群架构:k8scloude1作为master节点,k8scloude2,k8scloude3作为worker节点

服务器 操作系统版本 CPU架构 进程 功能描述
k8scloude1/192.168.110.130 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kube-apiserver,etcd,kube-scheduler,kube-controller-manager,kubelet,kube-proxy,coredns,calico k8s master节点
k8scloude2/192.168.110.129 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kubelet,kube-proxy,calico k8s worker节点
k8scloude3/192.168.110.128 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kubelet,kube-proxy,calico k8s worker节点

二.前言

Kubernetes(k8s)数据卷volumes类型众多,本文介绍使用存储类StorageClass动态制备持久卷Persistent Volume,关于静态制备持久卷Persistent Volume,请查看博客《Kubernetes(k8s)存储管理之数据卷volumes(四):持久卷Persistent Volume》

使用数据卷volumes的前提是已经有一套可以正常运行的Kubernetes集群,关于Kubernetes(k8s)集群的安装部署,可以查看博客《Centos7 安装部署Kubernetes(k8s)集群》

三.静态制备和动态制备

创建持久卷Persistent Volume有两种方法:静态制备和动态制备 。

静态制备(集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息, 并且对集群用户可用。PV 卷对象存在于 Kubernetes API 中,可供用户消费使用),简言之就是需要先创建pv,然后才能创建PVC

动态制备 (如果管理员所创建的所有静态 PV 卷都无法与用户的 PersistentVolumeClaim 匹配, 集群可以尝试为该 PVC 申领动态制备一个存储卷。 这一制备操作是基于 StorageClass 来实现的:PVC 申领必须请求某个 存储类, 同时集群管理员必须已经创建并配置了该类,这样动态制备卷的动作才会发生。 如果 PVC 申领指定存储类为 "",则相当于为自身禁止使用动态制备的卷)。

为了基于存储类完成动态的存储制备,集群管理员需要在 API 服务器上启用 DefaultStorageClass 准入控制器。 举例而言,可以通过保证 DefaultStorageClass 出现在 API 服务器组件的 --enable-admission-plugins 标志值中实现这点;该标志的值可以是逗号分隔的有序列表。

简言之就是我们可以使用存储类StorageClass实现动态制备,不需要提前创建PV,只要创建好存储类StorageClass,用户创建PVC之后,存储类StorageClass会自动创建一个PV和PVC进行绑定。

四.存储类StorageClass

4.1 存储类StorageClass概览

StorageClass 为管理员提供了描述存储 "类" 的方法。 不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。 Kubernetes 本身并不清楚各种类代表的什么。这个类的概念在其他存储系统中有时被称为 "配置文件"。

4.2 StorageClass 资源

每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态制备 PersistentVolume 时会使用到。

存储制备器provisioner:每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV。 该字段必须指定。

回收策略reclaimPolicy:由 StorageClass 动态创建的 PersistentVolume 会在类的 reclaimPolicy 字段中指定回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy,它将默认为 Delete。通过 StorageClass 手动创建并管理的 PersistentVolume 会使用它们被创建时指定的回收策略。

允许卷扩展allowVolumeExpansion:PersistentVolume 可以配置为可扩展。将此功能设置为 true 时,允许用户通过编辑相应的 PVC 对象来调整卷大小。此功能仅可用于扩容卷,不能用于缩小卷。

挂载选项mountOptions:由 StorageClass 动态创建的 PersistentVolume 将使用类中 mountOptions 字段指定的挂载选项。如果卷插件不支持挂载选项,却指定了挂载选项,则制备操作会失败。 挂载选项在 StorageClass 和 PV 上都不会做验证,如果其中一个挂载选项无效,那么这个 PV 挂载操作就会失败。

卷绑定模式volumeBindingMode:volumeBindingMode 字段控制了卷绑定和动态制备应该发生在什么时候。

  • Immediate 模式:默认情况下,Immediate 模式表示一旦创建了 PersistentVolumeClaim 也就完成了卷绑定和动态制备。 对于由于拓扑限制而非集群所有节点可达的存储后端,PersistentVolume 会在不知道 Pod 调度要求的情况下绑定或者制备。
  • WaitForFirstConsumer 模式:集群管理员可以通过指定 WaitForFirstConsumer 模式来解决此问题。 该模式将延迟 PersistentVolume 的绑定和制备,直到使用该 PersistentVolumeClaim 的 Pod 被创建。 PersistentVolume 会根据 Pod 调度约束指定的拓扑来选择或制备。 这些包括但不限于资源需求、 节点筛选器、 Pod 亲和性和互斥性、 以及污点和容忍度。

注意:如果你选择使用 WaitForFirstConsumer,请不要在 Pod 规约中使用 nodeName 来指定节点亲和性。 如果在这种情况下使用 nodeName,Pod 将会绕过调度程序,PVC 将停留在 pending 状态。相反,在这种情况下,你可以使用节点选择器nodeSelector指定主机名。

参数parameters:Storage Classes 的参数描述了存储类的卷。取决于制备器,可以接受不同的参数。 例如,参数 type 的值 io1 和参数 iopsPerGB 特定于 EBS PV。 当参数被省略时,会使用默认值。一个 StorageClass 最多可以定义 512 个参数。这些参数对象的总长度不能超过 256 KiB, 包括参数的键和值。

五.创建存储类StorageClass

5.1 配置NFS服务端以及共享目录

在一台机器上安装NFS服务端,k8s的两个worker安装NFS客户端。

etcd1机器作为NFS的服务端,安装NFS。

[root@etcd1 ~]# yum -y install nfs-utils

[root@etcd1 ~]# rpm -qa | grep nfs
libnfsidmap-0.25-19.el7.x86_64
nfs-utils-1.3.0-0.68.el7.2.x86_64

启动NFS

#使nfs开机自启动并现在就启动
[root@etcd1 ~]# systemctl enable nfs-server --now
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.

#查看nfs状态
[root@etcd1 ~]# systemctl status nfs-server 
● nfs-server.service - NFS server and services
   Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; vendor preset: disabled)
   Active: active (exited) since 二 2022-01-18 17:24:24 CST; 8s ago
  Process: 1469 ExecStartPost=/bin/sh -c if systemctl -q is-active gssproxy; then systemctl reload gssproxy ; fi (code=exited, status=0/SUCCESS)
  Process: 1453 ExecStart=/usr/sbin/rpc.nfsd $RPCNFSDARGS (code=exited, status=0/SUCCESS)
  Process: 1451 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
 Main PID: 1453 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/nfs-server.service

1月 18 17:24:24 etcd1 systemd[1]: Starting NFS server and services...
1月 18 17:24:24 etcd1 systemd[1]: Started NFS server and services.

先在NFS服务端创建/dongtaijuandongying,并把目录/dongtaijuandongying共享出去

#在NFS服务端创建共享目录/dongtaijuandongying
[root@etcd1 ~]# mkdir /dongtaijuandongying

[root@etcd1 ~]# vim /etc/exports

#把/dongtaijuandongying目录共享出去
[root@etcd1 ~]# cat /etc/exports
/dongtaijuandongying *(rw,async,no_root_squash)

[root@etcd1 ~]# exportfs -arv
exporting *:/dongtaijuandongying

5.2 配置NFS客户端

在k8s集群的worker节点安装nfs的客户端

[root@k8scloude3 ~]# yum -y install nfs-utils

 #安装nfs的客户端
[root@k8scloude2 ~]# yum -y install nfs-utils

查看etcd1(192.168.110.133)机器共享出来的目录是哪个?

[root@k8scloude2 ~]# showmount -e 192.168.110.133
Export list for 192.168.110.133:
/dongtaijuandongying *

5.3 配置StorageClass支持NFS

NFS类型的StorageClass没有内部制备器provisioner,但可以使用外部制备器。 也有第三方存储供应商提供自己的外部制备器。Kubernetes 不包含内部 NFS 驱动。你需要使用外部驱动为 NFS 创建 StorageClass。

由于k8s默认不支持nfs类型的StorageClass,需要修改参数然后自定义nfs StorageClass

[root@k8scloude1 volume]# vim /etc/kubernetes/manifests/kube-apiserver.yaml 

#添加参数如下
[root@k8scloude1 volume]# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep RemoveSelfLink
    - --feature-gates=RemoveSelfLink=false

重启kubelet使配置生效

[root@k8scloude1 volume]# systemctl restart kubelet

[root@k8scloude1 volume]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: active (running) since 三 2022-01-19 18:11:09 CST; 6s ago
     Docs: https://kubernetes.io/docs/
 Main PID: 89887 (kubelet)
   Memory: 37.4M
   CGroup: /system.slice/kubelet.service
           ├─89887 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --network-plugin=cni --pod-in...
           └─90152 [find]

下载git

[root@k8scloude1 volume]# yum -y install git

下载扩展存储卷yaml文件

[root@k8scloude1 volume]# git clone https://github.com/kubernetes-incubator/external-storage.git

[root@k8scloude1 volume]# ls
external-storage  

[root@k8scloude1 volume]# cd external-storage/nfs-client/

[root@k8scloude1 nfs-client]# cd deploy/

[root@k8scloude1 deploy]# ls
class.yaml  deployment-arm.yaml  deployment.yaml  objects  rbac.yaml  test-claim.yaml  test-pod.yaml

因为deployment.yaml文件里需要NFS制备器镜像:nfs-client-provisioner:latest镜像,可以提前下载下来镜像

[root@k8scloude1 deploy]# cat deployment.yaml | grep image
          image: quay.io/external_storage/nfs-client-provisioner:latest
          
#在master和worker上都下载好镜像
[root@k8scloude1 deploy]# docker pull quay.io/external_storage/nfs-client-provisioner:latest

[root@k8scloude2 ~]# docker pull quay.io/external_storage/nfs-client-provisioner:latest

[root@k8scloude3 ~]# docker pull quay.io/external_storage/nfs-client-provisioner:latest  

[root@k8scloude1 deploy]# docker images | grep nfs-client-provisioner
quay.io/external_storage/nfs-client-provisioner                   latest     16d2f904b0d8   3 years ago     45.5MB

安装NFS制备器

[root@k8scloude1 deploy]# pwd
/root/volume/external-storage/nfs-client/deploy

#当前的namespace为:volume
[root@k8scloude1 deploy]# kubens 
default
kube-node-lease
kube-public
kube-system
ns1
ns2
pod
volume

[root@k8scloude1 deploy]# ls
class.yaml  deployment-arm.yaml  deployment.yaml  objects  rbac.yaml  test-claim.yaml  test-pod.yaml

#修改namespace
[root@k8scloude1 deploy]# sed -i 's/namespace: default/namespace: volume/g' rbac.yaml

[root@k8scloude1 deploy]# kubectl apply -f rbac.yaml 
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created

[root@k8scloude1 deploy]# grep image deployment.yaml 
          image: quay.io/external_storage/nfs-client-provisioner:latest

[root@k8scloude1 deploy]# vim deployment.yaml 

#修改NFS的服务器地址和共享目录,设置镜像下载策略为: imagePullPolicy: IfNotPresent:本地有镜像就不下载
[root@k8scloude1 deploy]# cat deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed,命名空间要修改
  namespace: volume
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          #镜像下载策略要修改
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            #NFS服务器地址
            - name: NFS_SERVER
              value: 192.168.110.133
            #NFS共享目录
            - name: NFS_PATH
              value: /dongtaijuandongying
      volumes:
        - name: nfs-client-root
          nfs:
            #NFS服务器地址
            server: 192.168.110.133
            #NFS共享目录
            path: /dongtaijuandongying


[root@k8scloude1 deploy]# kubectl apply -f deployment.yaml 
deployment.apps/nfs-client-provisioner created

#可以看到nfs制备器nfs-client-provisioner-76c576954d-5x7t2
[root@k8scloude1 deploy]# kubectl get pod -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
nfs-client-provisioner-76c576954d-5x7t2   1/1     Running   0          17s   10.244.112.129   k8scloude2   <none>           <none>

5.4 创建StorageClass

查看StorageClass

[root@k8scloude1 deploy]# kubectl get sc
No resources found

配置StorageClass

[root@k8scloude1 deploy]# vim class.yaml 

#archiveOnDelete参数表示:If it exists and has a false value, delete the directory. if onDelete exists, archiveOnDelete will be ignored.
[root@k8scloude1 deploy]# cat class.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "false"

创建storageclass

[root@k8scloude1 deploy]# kubectl apply -f class.yaml 
storageclass.storage.k8s.io/managed-nfs-storage created

[root@k8scloude1 deploy]# kubectl get sc
NAME                  PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage   fuseim.pri/ifs   Delete          Immediate           false                  10s

5.5 创建持久卷申领PersistentVolumeClaim(PVC)

现在没有pv和PVC

[root@k8scloude1 deploy]# kubectl get pv
No resources found

[root@k8scloude1 deploy]# kubectl get pvc
No resources found in volume namespace.

配置PVC

[root@k8scloude1 deploy]# vim pvc1.yaml 

#storageClassName要和刚才创建的storageClass一样
[root@k8scloude1 deploy]# cat pvc1.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 1Gi
  storageClassName: managed-nfs-storage

创建PVC

[root@k8scloude1 deploy]# kubectl apply -f pvc1.yaml 
persistentvolumeclaim/mypvc created

[root@k8scloude1 deploy]# kubectl get pvc
NAME    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
mypvc   Bound    pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a   1Gi        RWO            managed-nfs-storage   6s

创建PVC之后,pv也自动创建了

[root@k8scloude1 deploy]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM          STORAGECLASS          REASON   AGE
pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a   1Gi        RWO            Delete           Bound    volume/mypvc   managed-nfs-storage            9s

查看pv的详细信息

[root@k8scloude1 deploy]# kubectl describe pv pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a 
Name:            pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a
Labels:          <none>
Annotations:     pv.kubernetes.io/provisioned-by: fuseim.pri/ifs
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    managed-nfs-storage
Status:          Bound
Claim:           volume/mypvc
Reclaim Policy:  Delete
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        1Gi
Node Affinity:   <none>
Message:         
Source:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    192.168.110.133
    Path:      /dongtaijuandongying/volume-mypvc-pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a
    ReadOnly:  false
Events:        <none>

删除pvc,PV也自动删除

[root@k8scloude1 deploy]# kubectl delete pvc mypvc 
persistentvolumeclaim "mypvc" deleted

[root@k8scloude1 deploy]# kubectl get pv
No resources found

[root@k8scloude1 deploy]# kubectl get pvc
No resources found in volume namespace.

重新创建PVC

[root@k8scloude1 deploy]# kubectl apply -f pvc1.yaml 
persistentvolumeclaim/mypvc created

[root@k8scloude1 deploy]# kubectl get pvc
NAME    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
mypvc   Bound    pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e   1Gi        RWO            managed-nfs-storage   6s

[root@k8scloude1 deploy]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM          STORAGECLASS          REASON   AGE
pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e   1Gi        RWO            Delete           Bound    volume/mypvc   managed-nfs-storage            9s

六.把卷挂载到pod

配置pod把PVC挂载到容器的/xx目录

[root@k8scloude1 deploy]# vim pvcpod.yaml 

[root@k8scloude1 deploy]# cat pvcpod.yaml 
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pvcshare
  name: pvcshare
spec:
  #nodeName指定pod运行在k8scloude3节点
  nodeName: k8scloude3
  terminationGracePeriodSeconds: 0
  volumes:
  - name: v1
    #卷类型为PVC
    persistentVolumeClaim:
      claimName: mypvc
  containers:
  - image: nginx
    imagePullPolicy: IfNotPresent
    name: h1
    resources: {}
    volumeMounts:
    - name: v1
      mountPath: /xx
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

创建pod

[root@k8scloude1 deploy]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-76c576954d-5x7t2   1/1     Running   0          15m

[root@k8scloude1 deploy]# kubectl apply -f pvcpod.yaml 
pod/pvcshare created

[root@k8scloude1 deploy]# kubectl get pod -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
nfs-client-provisioner-76c576954d-5x7t2   1/1     Running   0          15m   10.244.112.129   k8scloude2   <none>           <none>
pvcshare                                  1/1     Running   0          5s    10.244.251.195   k8scloude3   <none>           <none>

进入pod,创建文件

[root@k8scloude1 deploy]# kubectl exec -it pvcshare -- bash
root@pvcshare:/# ls /xx/

root@pvcshare:/# touch /xx/{1..10}.txt

root@pvcshare:/# ls /xx/
1.txt  10.txt  2.txt  3.txt  4.txt  5.txt  6.txt  7.txt  8.txt	9.txt

root@pvcshare:/# exit
exit

NFS服务器对应目录下也有文件

[root@etcd1 ~]# ls /dongtaijuandongying/volume-mypvc-pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e/
10.txt  1.txt  2.txt  3.txt  4.txt  5.txt  6.txt  7.txt  8.txt  9.txt

删除pod

[root@k8scloude1 deploy]# kubectl get pods 
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-76c576954d-5x7t2   1/1     Running   0          17m
pvcshare                                  1/1     Running   0          2m9s

[root@k8scloude1 deploy]# kubectl delete -f pvcpod.yaml 
pod "pvcshare" deleted

[root@k8scloude1 deploy]# kubectl get pods 
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-76c576954d-5x7t2   1/1     Running   0          18m

删除PVC

[root@k8scloude1 deploy]# kubectl delete -f pvc1.yaml 
persistentvolumeclaim "mypvc" deleted

[root@k8scloude1 deploy]# kubectl get pv
No resources found

[root@k8scloude1 deploy]# kubectl get pvc
No resources found in volume namespace.

pvc被删除之后,NFS服务端文件也没了,是因为回收策略RECLAIM POLICY为Delete

[root@etcd1 ~]# ls /dongtaijuandongying/volume-mypvc-pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e/
ls: 无法访问/dongtaijuandongying/volume-mypvc-pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e/: 没有那个文件或目录

当配置了存储类StorageClass,PVC可以进行动态扩容,关于PVC动态扩容请查看博客《troubleshoot:PVC动态扩容报错》