• k8s中持久化存储卷nfs、pv、pvc


    前言

    环境:centos7.9 docker-ce-20.10.9 kubernetes-version v1.22.6
    前面我们讲解了emptyDir和hostPath卷,前者只是临时数据,pod消失emptyDir卷的数据就没了,后者在工作节点的文件系统中保留卷的数据,但是无法保证pod异常挂掉之后重新创建之后还是调度到原来的节点上。
    所以本篇来讲解持久化存储,其原理很简单,就是创建某种网络存储,这样即使pod异常挂掉之后,重新创建pod,pod还是会读取之前的存储卷数据。
    一个pod可以定义多个不同类型的卷,一个容器也可以使用不同类型的多个卷。

    创建NFS存储卷

    我们最熟悉的远程网络存储之一就是NFS了,下面将演示pod中如何使用nfs卷;
    1、首先需要创建一个nfs 服务器,作为存储服务器,这里不在简单介绍,可参考《企业级NFS网络文件共享服务》;
    2、nfs 服务器搭建好之后,我们开始创建pod;

    [root@master ~]# cat deplyment_nginx_nfs.yaml 		#创建deployment和server资源
    apiVersion: apps/v1
    kind: Deployment
    metadata: 
      name: deployment-nginx-nfs
      namespace: default
    spec:
      replicas: 1
      selector:
         matchLabels:
             app: nginx-nfs
      template:
         metadata:
           labels:
             app: nginx-nfs
         spec:
             containers:
             - image: nginx:1.7.9
               name: nginx-container
               ports:
               - name: http 
                 containerPort: 80
               volumeMounts:							#挂载点,有s表示可以设置多个
               - name: nfs-volume						#指定要挂载的卷
                 mountPath: /var/log/nginx				#指定挂载到容器内哪个路径下
             volumes: 
             - name: nfs-volume
               nfs:										#创建nfs存储卷
                 server: 192.168.118.128				#nfs服务器ip
                 path: /home/k8s_data					#nfs服务器的共享数据目录
    ---
    apiVersion: v1									
    kind: Service
    metadata:
      labels:
        app: nginx-nfs
      name: svc-nginx-nfs-nodeport
    spec:
      sessionAffinity: ClientIP
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
        nodePort: 30068
      selector:
        app: nginx-nfs
      type: NodePort
    [root@master ~]# 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    pod启动失败排查
    启动资源后发现,pod创建失败,使用kubectl describe pod 命令查看pod信息发现如下报错:

    mount: wrong fs type, bad option, bad superblock on /dev/mapper/oraclevg-oraclelv,
    missing codepage or helper program, or other error
    In some cases useful info is found in syslog - try
    dmesg | tail or so
    
    • 1
    • 2
    • 3
    • 4

    这说明挂载nfs服务器出现了问题。

    排查发现pod被分配到了node1工作节点上,但是node1节点没有mount.nfs命令,所以pod才会报错,而mount.nfs命令是需要安装nfs-utils服务的,所以yum install -y nfs-utils,可以不用启动nfs服务,只需要有这个mount.nfs命令即可。

    重新启动deployment,发现pod还是启动失败,kubectl describe pod 命令查看pod信息发现nfs正常挂载了,但是pod就是启动失败。
    在这里插入图片描述
    查看容器的日志,发现nginx启动报错了,如下所示:

    root@master ~]# kubectl  logs  deployment-nginx-nfs-7776555dbd-gzlpq
    nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (13: Permission denied)
    2022/03/20 08:12:20 [emerg] 1#0: open() "/var/log/nginx/error.log" failed (13: Permission denied)
    [root@master ~]# 
    
    • 1
    • 2
    • 3
    • 4

    这说明,nginx在往nfs服务器的共享目录写数据,没有权限,所以需要给nfs服务器的目录设置权限,如下:

     [root@mysql ~]# chmod 777 -R /home/k8s_data/				#给nfs服务器共享数据的目录设置权限,让nginx有权限写入
    
    • 1

    以上,重新重启deployment,测试访问nginx,一切正常,日志数据也保存到了nfs服务器上;然后停掉node1来实现pod调度到node2上,注意node2也要安装nfs-server,即yum install -y nfs-utils,让其有mount.nfs命令,然后可以关闭nfs服务(systemctl stop nfs-server),重新创建pod之后,pod就会被调度到node2上,pod还是可以读取到nfs的日志文件,这说明,使用nfs来做持久化储存是成功的。

    从底层持久化技术解耦pod

    以上,我们在pod创建nfs存储卷时,是直接在pod定义存储卷的,这显然需要开发pod的工程师知道nfs服务器的ip地址及其他的参数,这违背了kubernetes的基本理念,kubernetes的基本理念旨在向应用程序及其开发人员隐藏真实的底层基础设施,使他们不必担心基础设施的具体状态,并使应用程序可在大量云服务商和数据企业之间进行功能迁移。

    理想的情况是,在Kubernetes上部署应用程序的开发人员不需要知道底层使用的是哪种存储技术,同理他们也不需要了解应该使用哪些类型的物理服务器来运行pod,与基础设施相关的交互是集群管理员独有的控制领域。

    当开发人员需要一定数量的持久化存储来进行应用时,可以向Kubernetes请求,就像在创建pod时可以请求CPU、内存和其他资源一样。系统管理员可以对集群进行配置让其可以为应用程序提供所需的服务。

    在Kubernetes集群中,为了使应用能够正常请求存储资源,同时避免处理基础设施细节,引入了两个新的资源,分别是持久卷和持久卷声明。

    pv持久卷

    PersistentVolume(持久卷,简称PV),PV不属于任何命名空间,PV是集群层面的资源。
    简单的来说,持久卷就是定义存储的一个k8s资源对象;

    [root@master ~]# kubectl  get pv					#查看k8s集群中的pv命令
    
    • 1

    下面来创建一个持久卷:

    [root@master ~]# vim pv-nfs.yaml
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv-nfs
      labels:
        pv: nfs
    spec:
      capacity:											#容量为500M
        storage: 500M
      accessModes:										#访问模式为允许多节点以读写方式挂载,可以有多个访问模式
      - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain				#回收策略
      nfs:												#定义nfs服务器的信息
        server: 192.168.118.128
        path: /home/k8s_data
        readOnly: false
    [root@master ~]# 
    [root@master ~]# kubectl  apply -f pv-nfs.yaml 		#应用资源清单
    [root@master ~]# kubectl  get pv					#查看PV,状态是可用,容量大小是500M,访问模式时ROX
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    pv-nfs   500M       ROX            Retain           Available                                   2m42s
    [root@master ~]# 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    pv 的 persistentVolumeReclaimPolicy 字段用于定义当 pvc 被删除后,使用哪一种pv 的保留策略,k8s提供了以下3种pv的回收策略:

    回收策略说明
    RetainRetain策略,当 PersistentVolumeClaim 被删除时,PersistentVolume 仍然存在,该卷被认为是“已释放”, 但它还不能用于另一个pvc,因为前一个pvc的数据仍在卷上。 管理员可以手动通过删除持久卷pv, 删除 pv 后,数据仍然存在于存储服务器磁盘里,所以还需要相应地手动清理相关存储服务器磁盘上的数据;如果要重用相同的存储,请创建具有相同存储资源的新 pv。
    DeleteDelete 策略,删除被pvc释放的pv和后端存储数据
    Recycle保留pv,但清空pv上的数据(已废弃)

    pv 的访问模式有4种,使用accessModes字段进行设置,一个pv可以设置多个访问模式,pv的4种访问模式为:

    模式说明
    ReadWriteOnce,简写:RWO只仅允许单个节点以读写方式挂载
    ReadOnlyMany,简写:ROX可以被许多节点以只读方式挂载
    ReadWriteMany,简写:RWX可以被多个节点以读写方式挂载
    ReadWriteOncePod,简写:RWOP该卷可以由单个 Pod 以读写方式挂载。如果要确保整个集群中只有一个 pod 可以读取或写入 PVC,请使用 ReadWriteOncePod 访问模式。这仅支持 CSI 卷和 Kubernetes 1.22+ 版本

    创建pv后,pv的的状态有以下4种:
    Available(可用)、Bound(已绑定)、Released(已释放)、Failed(失败)

    Available,表示pv已经创建正常,是可用状态;
    Bound,表示pv已经被某个pvc绑定,注意,一个pv一旦被某个pvc绑定,那么该pvc就独占该pv,其他pvc不能再与该pv绑定;
    Released,表示pvc被删除了,pv状态就会变成已释放;
    Failed,表示pv的自动回收失败;

    再次声明,pv并不属于任何命名空间,它使集群层面上的资源,如下图所示:
    在这里插入图片描述

    pvc持久卷声明

    当集群用户需要在其pod中使用持久化存储时,他们并不是直接使用pv的,而是,首先创建一个持久卷声明(PersistentVolumeClaim,简称PVC),指定所需要的最低容量要求和访问模式,然后用户将pvc清单提交给Kubernetes API服务器,Kubernetes将找到可匹配的pv并将其绑定到pvc。
    pvc可以当作pod中的一个卷来使用,其他用户不能使用相同的持久卷,除非先通过删除持久卷声明绑定来释放。
    创建pvc:

    [root@master ~]# vim pvc-nfs.yaml    		#创建一个pvc,用于绑定我们上面创建的pv
    apiVersion: v1
    kind: PersistentVolumeClaim					#资源类型
    metadata:
      name: pvc-nfs
      namespace: default						#所属命名空间,注意,pvc是有命名空间的,pv是集群层面的没有命名空间
      labels:
        pvc: nfs
    spec:
      resources:								#定义pvc所需的资源
        requests:
          storage: 500M							#需要500M的存储空间
      accessModes:
      - ReadWriteMany							#访问模式为ReadWriteMany
      storageClassName: ""						#这个写上空字符串,后面动态持久卷将解释为什么
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    pvc创建好之后,我们来查看会发现什么:

    [root@master ~]# kubectl get  pv,pvc
    NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
    persistentvolume/pv-nfs   500M       RWX            Retain           Bound    default/pvc-nfs                           108m
    
    NAME                            STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    persistentvolumeclaim/pvc-nfs   Bound    pv-nfs   500M       RWX                           4m46s
    [root@master ~]# 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们发现,pvc与pv进行了绑定,现在pvc和pv的状态都是Bound表示绑定了,既能查看到pv被哪个pvc绑定,也能查看得到pvc绑定了哪个pv
    那么,pvc是如何指定该与哪个pv进行绑定呢?答案是:通过pvc申请的容量大小和访问模式来匹配最佳的pv。

    在pod中使用pvc

    创建一个deployment,如下所示:

    [root@master ~]# vim deplyment_nginx_pvc.yaml 			#创建一个资源清单
    apiVersion: apps/v1
    kind: Deployment
    metadata: 
      name: deployment-nginx-pvc
      namespace: default
    spec:
      replicas: 1
      selector:
         matchLabels:
             app: nginx-pvc
      template:
         metadata:
           labels:
             app: nginx-pvc
         spec:
             containers:
             - image: nginx:1.7.9
               name: nginx-container
               ports:
               - name: http 
                 containerPort: 80
               volumeMounts:
               - name: pvc-volume
                 mountPath: /var/log/nginx
             volumes: 									#这里直接引用pvc
             - name: pvc-volume
               persistentVolumeClaim:
                 claimName: pvc-nfs
                 readOnly: false
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: nginx-pvc
      name: svc-nginx-pvc-nodeport
    spec:
      sessionAffinity: ClientIP
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
        nodePort: 30058
      selector:
        app: nginx-pvc
      type: NodePort
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    访问nginx:

    [root@master ~]# kubectl  get pod,svc				#查看pod,service是否正常
    NAME                                        READY   STATUS    RESTARTS   AGE
    pod/deployment-nginx-pvc-844bf79b44-k654f   1/1     Running   0          5m56s
    
    NAME                             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
    service/kubernetes               ClusterIP   10.96.0.1        <none>        443/TCP        45d
    service/svc-nginx-pvc-nodeport   NodePort    10.105.138.113   <none>        80:30058/TCP   5m55s
    [root@master ~]# 
    [root@master ~]# curl 10.105.138.113 				#访问pod的nginx应用,正常访问
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
        body {
            width: 35em;
            margin: 0 auto;
            font-family: Tahoma, Verdana, Arial, sans-serif;
        }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    
    
    [root@mysql k8s_data]# tail -22f access.log 				#查看nfs服务器上的共享数据目录,发现数据已经过来了
    
    
    10.244.1.1 - - [20/Mar/2022:15:45:10 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36" "-"
    10.244.1.1 - - [20/Mar/2022:15:45:10 +0000] "GET /favicon.ico HTTP/1.1" 404 570 "http://192.168.118.132:30058/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36" "-"
    10.244.0.0 - - [20/Mar/2022:15:45:23 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
    10.244.0.0 - - [20/Mar/2022:15:45:24 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
    10.244.0.0 - - [20/Mar/2022:15:45:25 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
    10.244.0.0 - - [20/Mar/2022:15:45:26 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
    10.244.0.0 - - [20/Mar/2022:15:49:27 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
    
    以上就说明了pod是正常的。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    pv的回收策略之 Retain策略

    Retain策略,当 PersistentVolumeClaim 被删除后,保留PersistentVolume,该pv就会被认为是“已释放”, 但它仍不能用于另一个pvc,因为前一个pvc的数据仍在卷上。 管理员可以通过手动删除持久卷pv, 删除 pv 后,数据仍然存在于存储服务器磁盘里,如果彻底不需要数据了,此时可以手动清理相关存储服务器磁盘上的数据;如果需要重用之前的数据,此时重新创建具有相同存储资源的新 pv即可复用之前的数据。

    #现在pvc已经和pv绑定了,pv回收策略是Retain,下面进行删除pvc
    [root@master ~]# kubectl  get pv,pvc					
    NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM         STORAGECLASS   REASON   AGE
    persistentvolume/pv-nfs   500M       RWX            Retain           Bound    default/pvc-nfs                       42h
    
    NAME                            STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    persistentvolumeclaim/pvc-nfs   Bound    pv-nfs   500M       RWX                           42h
    [root@master ~]# kubectl  delete pvc pvc-nfs			#删除pvc
    persistentvolumeclaim "pvc-nfs" deleted
    [root@master ~]# kubectl  get pv					  	#查看pv,现在pv的状态是Released
    NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM       STORAGECLASS   REASON   AGE
    persistentvolume/pv-nfs   500M       RWX            Retain           Released   default/pvc-nfs                     42h
    [root@mysql k8s_data]# ll								#查看nfs服务器上的数据,仍存在
    total 12
    -rw-r--r--. 1 nfsnobody nfsnobody 1064 Mar 22 15:17 access.log
    -rw-r--r--. 1 nfsnobody nfsnobody  268 Mar 20 23:45 error.log
    [root@mysql k8s_data]# 
    
    #我们重新创建pvc,发现pvc状态显示Pending,pvc并没有绑定到之前的pv上
    [root@master ~]# kubectl apply  -f pvc-nfs.yaml			
    persistentvolumeclaim/pvc-nfs created
    [root@master ~]# kubectl  get pvc
    NAME      STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    pvc-nfs   Pending                                                     7s
    
    [root@master ~]# kubectl delete pv pv-nfs 				#下面我们把pv删掉
    persistentvolume "pv-nfs" deleted
    
    #重新创建pv,可以看到刚开始创建的pv状态是Available,然后过了一会被我们之前创建pvc绑定了
    [root@master ~]# kubectl  apply -f pv-nfs.yaml ;kubectl get pv	
    persistentvolume/pv-nfs created
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    pv-nfs   500M       RWX            Retain           Available                                   0s
    [root@master ~]# kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
    pv-nfs   500M       RWX            Retain           Bound    default/pvc-nfs                           15s
    [root@master ~]# 
    #此时,我们重新创建一个pod,进入pod查看发现还是可以读取之前上一个pod的数据
    
    #以上,我们就演示了pv的回收策略是persistentVolumeReclaimPolicy: Retain时,当删除pvc后,pv的状态就会变成Released,后端存储服务器的数据仍保留存在;Released状态的pv将不会被任何pvc绑定,如果需要重新使用该pv的定义,只能删除该pv,重新创建一个和它一模一样的pv,后端数据的删除与否取决于管理员的决定,如果不删,那么新创建的pv将继承数据,pod也将继承数据,即新的pod将可以访问之前pod的旧数据。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    pv的回收策略之 Delete 策略

    Delete 策略,当pvc被删除之后,级联删除pv和后端存储数据。例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等,NFS不支持delete策略。下面不在做代码演示。

    pv的回收策略之 Recycle策略(已弃用)

    Recycle策略,已弃用,只需了解即可,当删除pvc之后,pv的状态由Released 变为Available,可用再次被新的pvc绑定,此时存储服务器的数据将会被彻底删除(很危险,所以不建议使用该回收策略,所以 Recycle策略被弃用了)。演示如下:

    [root@master pv]# kubectl  delete  pvc pvc-nfs 				#删除pvc
    persistentvolumeclaim "pvc-nfs" deleted
    [root@master pv]# kubectl get pv 							#查看pv,这时pv的状态是Released已释放
    NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM             STORAGECLASS   REASON   AGE
    persistentvolume/pv-nfs   500M       RWX            Recycle          Released   default/pvc-nfs                   17m
    [root@master pv]# kubectl get pv 							#等了一会儿,查看pv,这时pv的状态是Available 可用
    NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    persistentvolume/pv-nfs   500M       RWX            Recycle          Available                                   18m
    
    #查看后端的存储服务器上的数据
    [root@mysql k8s_data]# ll									#数据已经被删除掉了
    total 0
    [root@mysql k8s_data]# 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    以前3中pv的回收策略,目前,AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持Delete删除策略,而只有 NFS 和 HostPath 支持Recycle回收策略。,也不建议使用Recycle策略,因为会直接删除存储数据。

    持久卷的动态卷配置、为什么需要StorageClass

    以上,我们创建pv和使用pv的流程是:
    1、创建一个持久卷;
    2、再创建一个持久卷声明,持久卷声明根据访问模式和容量大小来自动适配到PV,并进行绑定;
    3、pod中应用持久卷声明即可实现使用存储。

    假如在一个大规模的Kubernetes集群里,可能有成千上万个PVC,这就意味着运维人员必须实现创建出这个多个PV,此外,随着项目的需要,会有新的PVC不断被提交,那么运维人员就需要不断的添加新的,满足要求的PV,否则新的Pod就会因为PVC绑定不到PV而导致创建失败。而且通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求。
    而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。

    什么是StorageClass(简写sc)

    Kubernetes提供了一套可以自动创建PV的机制,即:Dynamic Provisioning。而这个机制的核心在于StorageClass这个API对象。
    StorageClass对象会定义下面两部分内容:
    1、PV的属性。比如,存储类型,Volume的大小等。
    2、创建这种PV需要用到的存储插件,即存储制备器。
    有了这两个信息之后,Kubernetes就能够根据用户提交的PVC,找到一个对应的StorageClass,之后Kubernetes就会调用该StorageClass声明的存储插件,进而创建出需要的PV。

    优化后的流程

    引入了持久卷的动态配置之后,整个流程就变成了如下:
    1、管理员首选需要提供或创建Provisioner(供应者),Provisioner负责为外部请求提供pv(持久化存储卷)实例(相当于定义哪个是nfs存储服务器),说白了,Provisioner作用就是,决定使用哪个卷插件分配 PV;
    2、StorageClass向绑定的Provistioner发出创建pv(持久化存储卷)请求;
    3、PVC绑定到StorageClass,StorageClass就会根据PVC指定的容量、访问模式来自动创建与PVC匹配的PV,无需人工干预;
    4、Pod应用PVC。

    正向流程:pod引用pvc,pvc向sc发出请求,sc向provisioner发出创建pv请求并成功创建pv。

    以上4步就是整个动态卷的流程,现在只需要我们运维人员前期创建多个StorageClass存储类,然后就不用管了,开发人员他们自己创建pvc,pvc就会自动向sc发出创建pv请求,这样pv自动创建成功,就不需要运维人员手动一个个创建pv了。

    声明:我们以最常用的nfs来做演示,同时以下创建账号和provisioner、StorageClass存储类都是参考:https://github.com/kubernetes-retired/external-storage/blob/master/nfs-client/deploy/来做的。

    创建ServiceAccount账号

    [root@master pv]# cat rbac.yaml					#先创建账号和角色
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: nfs-client-provisioner
      namespace: default
    ---
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: nfs-client-provisioner-runner
    rules:
      - 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
        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
      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
      namespace: default
    subjects:
      - kind: ServiceAccount
        name: nfs-client-provisioner
        namespace: default
    roleRef:
      kind: Role
      name: leader-locking-nfs-client-provisioner
      apiGroup: rbac.authorization.k8s.io
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    创建provisioner(也可称为供应者、置备程序、存储分配器)

    需要先准备好nfs存储服务器,然后我们使nfs来作为Provisioner(供应者、置备程序),创建一个deploy,使用pod来运行置备程序provisioner,其大概配置的就是定义nfs服务器的ip,共享路径等信息;

    [root@master ~]# vim nfs-client-provisioner.yaml		#创建nfs类型的置备程序
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nfs-client-provisioner
      labels:
        app: nfs-client-provisioner
      namespace: default
    spec:
      replicas: 1
      strategy:
        type: Recreate
      selector:
        matchLabels:
          app: nfs-client-provisioner
      template:
        metadata:
          labels:
            app: nfs-client-provisioner
        spec:
          serviceAccountName: nfs-client-provisioner				#这个serviceAccountName就是上面创建ServiceAccount账号
          containers:
            - name: nfs-client-provisioner
              image: quay.io/external_storage/nfs-client-provisioner:latest
              volumeMounts:
                - name: nfs-client-root
                  mountPath: /persistentvolumes
              env:
                - name: PROVISIONER_NAME			#PROVISIONER_NAME的值就是本清单的顶部定义的name
                  value: nfs-client-provisioner
                - name: NFS_SERVER					#这个NFS_SERVER参数的值就是nfs服务器的IP地址
                  value: 192.168.118.128
                - name: NFS_PATH					#这个NFS_PATH参数的值就是nfs服务器的共享目录
                  value: /home/k8s_data
          volumes:
            - name: nfs-client-root
              nfs:									#这里就是配置nfs服务器的ip地址和共享目录
                server: 192.168.118.128
                path: /home/k8s_data
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    创建StorageClass

    置备程序provisioner创建好了,下面现在我们就可以创建一个StorageClass存储类了。

    [root@master ~]# vim storageclass.yaml			#创建StorageClass存储类
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: nfs-storageclass
    provisioner: nfs-client-provisioner				#provisioner参数定义置备程序
    reclaimPolicy: Retain							#回收策略,默认是Delete
    parameters:
      archiveOnDelete: "false"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    创建pvc来引用storageclass

    pvc中应用存储类很简单,通过一个参数即可实现:

    [root@master ~]# vim pvc.yaml			#创建pvc,pvc引用sc
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pvc-nfs
      namespace: default
      labels:
        pvc: nfs
    spec:
      resources:
        requests:
          storage: 200M
      accessModes:
      - ReadWriteMany
      storageClassName: nfs-storageclass	#指定要使用哪个存储类
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    创建业务pod、service

    pvc以及创建好了,下面就是创建业务pod和service了,

    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata: 
      name: deploy-nginx-pv-pvc
      namespace: default
    spec:
      replicas: 2
      selector:
         matchLabels:
             app: nginx-pv-pvc
      template:
         metadata:
           labels:
             app: nginx-pv-pvc
         spec:
             containers:
             - image: nginx:1.7.9
               name: nginx-container
               ports:
               - name: http 
                 containerPort: 80
               volumeMounts:
               - name: pv-pvc-volume
                 mountPath: /var/log/nginx
             volumes: 
             - name: pv-pvc-volume
               persistentVolumeClaim:
                 claimName: pvc-nfs
                 readOnly: false
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: svc-nginx-pv-pvc
      name: svc-nginx-nodeport-pv-pvc
    spec:
      sessionAffinity: ClientIP
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
        nodePort: 30058
      selector:
        app: nginx-pv-pvc
      type: NodePort
    
    ---
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    查看各个资源

    [root@master pv]# kubectl  get deployments.apps 		#查看deploy
    NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
    deploy-nginx-pv-pvc      2/2     2            2           57m	#这个是我们的业务deploy,有两个pod
    nfs-client-provisioner   1/1     1            1           57m	#这个是我们的provisioner置备程序的deploy,有一个pod
    [root@master pv]# kubectl  get sc						#查看存储类,可以看到创建的储存类回收策略是 Retain策略,m默认是Delete
    NAME               PROVISIONER              RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    nfs-storageclass   nfs-client-provisioner   Retain          Immediate           false                  59m
    [root@master pv]#
    [root@master pv]# kubectl  get pv,pvc	#查看pvc,pv,我们看到,自动创建一个和pvc定义访问模式容量大小匹配的pv并且与pvc绑定了
    NAME CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM      STORAGECLASS       REASON   AGE
    persistentvolume/pvc-ab2e38cf-2468-4acd-8818-13854be3d2f1   200M  RWX  Retain  Bound  default/pvc-nfs   nfs-storageclass            86s
    
    NAME                           STATUS VOLUME                        CAPACITY   ACCESS  MODES STORAGECLASS      AGE
    persistentvolumeclaim/pvc-nfs  Bound  pvc-ab2e38cf-2468-4acd-8818-13854be3d2f1 200M    RWX   nfs-storageclass  110s
    [root@master pv]# 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    默认存储类、不指定存储类的pvc、pvc中存储类名设置为空字符串

    管理员可以创建多个存储类,如果存在多个存储类,可以将某个存储类设置为默认存储类:

    [root@master pv]# kubectl get sc 
    NAME               PROVISIONER              RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    nfs-storageclass   nfs-client-provisioner   Retain          Immediate           false                  45m
    [root@master pv]# kubectl  describe sc nfs-storageclass 
    Name:            nfs-storageclass
    IsDefaultClass:  No								#显示不是默认存储类
    Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"nfs-storageclass"},"parameters":{"archiveOnDelete":"false"},"provisioner":"nfs-client-provisioner","reclaimPolicy":"Retain"}
    
    Provisioner:           nfs-client-provisioner
    ....
    
    #将已创建好的存储类设置为默认存储类
    [root@master pv]# kubectl patch storageclass nfs-storageclass -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
    storageclass.storage.k8s.io/nfs-storageclass patched
    [root@master pv]# kubectl  describe sc nfs-storageclass 
    Name:            nfs-storageclass
    IsDefaultClass:  Yes								#已经是默认存储类
    Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"nfs-storageclass"},"parameters":{"archiveOnDelete":"false"},"provisioner":"nfs-client-provisioner","reclaimPolicy":"Retain"}
    ,storageclass.kubernetes.io/is-default-class=true
    Provisioner:           nfs-client-provisioner
    Parameters:            archiveOnDelete=false
    AllowVolumeExpansion:  <unset>
    MountOptions:          <none>
    ReclaimPolicy:         Retain
    VolumeBindingMode:     Immediate
    Events:                <none>
    [root@master pv]# kubectl  get sc						#名字后面也显示是默认存储类
    NAME                         PROVISIONER              RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    nfs-storageclass (default)   nfs-client-provisioner   Retain          Immediate           false                  52m
    [root@master pv]#
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    有了默认存储类,在定义pvc如果不指定 pvc.spec.storageClassName,则将使用默认存储类进行创建pv。

    以上我们讨论了写上storageClassName和不写storageClassName的两种情况,provisioner(存储分配器)都会创建pv,那么如果不想让其创建pv,而是像让pvc与我们手动创建的pv进行绑定怎么办呢?细心观察的同学可能已经发现,文章最开始创建pvc的时候将storageClassName:“” ,设置为空字符串,是的,当storageClassName值设置为空字符串时,provisioner将不会创建pv,pvc自动寻找匹配你手动创建的pv。

    管理员可以实现配置多个provisioner,然后再创建多个storageClass,然后由开发人员根据他们存储需求来创建pvc引用对应的storageClass。

    通过一幅图来完整了解动态持久卷供应全貌

    在这里插入图片描述

    配置动态卷出现的一些问题排查

    1、必须要创建ServiceAccount,因为Provisioner(存储分配器)中用到了ServiceAccount;
    2、如果deployment创建了,但是pod没创建成功(deployment底层原理是使用rc去创建pod),此时可以查看controller-manager的日志,kubeadm安装的controller-manager是使用pod启动的,kubectl logs -f kube-controller-manager-master -n kube-system;
    3、如果出现问题,顺着这样的思路一层一层的查看pod的来排查问题:业务pod调用pvc,pvc调用storageClass,sc调用provisioner去创建pv;
    4、业务pod一直是“Pending”状态,排查发现pvc一直是“等待创建pv”,继续排查provisioner,查看provisioner的日志, kubectl logs nfs-client-provisioner-686c9994b5-p6h6n 发现报错信息“unexpected error getting claim reference: selfLink was empty, can’t make reference”,百度,使用以下解决办法:
    在这里插入图片描述

    总结

    1、pod中直接使用nfs卷。
    必须先创建一个nfs服务器,然后启动nfs-server,设置共享数据目录等前期工作,同时在node节点还需要安装nfs-utils
    服务,即`yum install -y nfs-utils`,主要是为了让node节点上有mount.nfs命令,该命令是挂载时需要的命令,可以不启动node节点上nfs-server.
    
    [root@master pv]# vim deployment-nfs.yaml
    .............
    spec:
             containers:
             - image: nginx:1.7.9
               name: nginx-container
               ports:
               - name: http 
                 containerPort: 80
               volumeMounts:							#挂载点
               - name: nfs-volume						#要挂载的卷的名称
                 mountPath: /var/log/nginx				#挂载的路径
             volumes: 
             - name: nfs-volume
               nfs:										#创建nfs存储卷
                 server: 192.168.118.128				#nfs服务器ip
                 path: /home/k8s_data					#nfs服务器的共享数据目录
    .............
    [root@master pv]#
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    2、创建一个pv。
    为了屏蔽存储服务器的相关配置,即向开发人员屏蔽后端的nfs存储服务器的ip,共享目录等情况,kubernetes能将卷的定义从pod中解耦出来,从而有
    了pv和pvc资源对象,pv是persistentVolume的简写,即持久化存储,它是集群层面的资源,所以pv不属于任何一个命名空间。
    
    [root@master ~]# vim pv-nfs.yaml
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv-nfs
      labels:
        pv: nfs
    spec:
      capacity:											#容量为500M
        storage: 500M
      accessModes:										#访问模式为允许多节点以读写方式挂载,可以有多个访问模式
      - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain				#回收策略
      nfs:												#定义nfs服务器的信息
        server: 192.168.118.128
        path: /home/k8s_data
        readOnly: false
    [root@master pv]# 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    3、创建一个pvc。
    创建好后的pvc会自动与最匹配的pv进行绑定,这种绑定机制其实是通过pvc申请的容量大小和访问模式来寻找最佳的pv进行绑定的。
    
    [root@master ~]# vim pvc-nfs.yaml    		#创建一个pvc,用于绑定我们上面创建的pv
    apiVersion: v1
    kind: PersistentVolumeClaim					#资源类型
    metadata:
      name: pvc-nfs
      namespace: default						#所属命名空间,注意,pvc是有命名空间的,pv是集群层面的没有命名空间
      labels:
        pvc: nfs
    spec:
      resources:								#定义pvc所需的资源
        requests:
          storage: 500M							#需要500M的存储空间
      accessModes:
      - ReadWriteMany							#访问模式为ReadWriteMany
      storageClassName: ""						#这个写上空字符串,后面动态持久卷将解释为什么
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    4、pod中引用pvc。
    定义后pvc之后,pod中可以直接引用pvc,如下所示,pv定义了存储空间大小,pvc绑定pv,pod中引用pvc,这样就能实现pod持久化存储数据的目的。
    ..............
         spec:
             containers:
             - image: nginx:1.7.9
               name: nginx-container
               ports:
               - name: http 
                 containerPort: 80
               volumeMounts:
               - name: pvc-volume
                 mountPath: /var/log/nginx
             volumes: 									
             - name: pvc-volume					#卷名
               persistentVolumeClaim:			#这里直接引用已经定义好的pvc-nfs这个pvc
                 claimName: pvc-nfs
                 readOnly: false
    .......................
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    5、问题思考,如果k8s中存在大量的应用pod都需要持久化存储数据,那要管理员一个个的创建成千上万的pv吗?k8s为我们引入了一种自动创建pv的机
    制,存储类StorageClass。
    StorageClass对象会定义下面两部分内容:
    1、PV的属性。比如,存储类型,Volume的大小等。
    2、创建这种PV需要用到的存储插件,即存储制备器。
    有了这两个信息之后,Kubernetes就能够根据用户提交的PVC,找到一个对应的StorageClass,之后Kubernetes就会调用该StorageClass声明的存储
    插件,进而创建出需要的PV。
    引入了持久卷的动态配置之后,整个流程就变成了如下:
    1、管理员首选需要提供或创建Provisioner(供应者),Provisioner负责为外部请求提供pv(持久化存储卷)实例(相当于定义哪个是nfs存储服务
    器),说白了,Provisioner作用就是,决定使用哪个卷插件分配 PV;
    2、StorageClass向绑定的Provistioner发出创建pv(持久化存储卷)请求;
    3、PVC绑定到StorageClass,StorageClass就会根据PVC指定的容量、访问模式来自动创建与PVC匹配的PV,无需人工干预;
    4、Pod应用PVC。
    
    正向流程:pod引用pvc,pvc向sc发出请求,sc向provisioner发出创建pv请求并成功创建pv。
    
    以上4步就是整个动态卷的流程,现在只需要我们运维人员前期创建多个StorageClass存储类,然后就不用管了,开发人员他们自己创建pvc,pvc就会自
    动向sc发出创建pv请求,这样pv自动创建成功,就不需要运维人员手动一个个创建pv了。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    6、创建一个nfs类型的存储类。
    整个流程为:创建账号和角色-->创建Provisioner供应者/置备程序(Provisioner供应者指定了后端存储的类型,ip地址等信息)-->创建
    StorageClass(sc中会指明使用哪个Provisioner以及回收策略)-->开发人员创建pvc-->pod中引用pvc。
    
    使用最常用的nfs来做演示,同时以下创建账号和provisioner、StorageClass存储类都是参考:https://github.com/kubernetes-
    retired/external-storage/blob/master/nfs-client/deploy/来做的。
    
    (1)、创建账号与角色
    [root@master pv]# vim rbac.yaml					#先创建账号和角色
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: nfs-client-provisioner
      namespace: default
    ---
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: nfs-client-provisioner-runner
    rules:
      - 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
        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
      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
      namespace: default
    subjects:
      - kind: ServiceAccount
        name: nfs-client-provisioner
        namespace: default
    roleRef:
      kind: Role
      name: leader-locking-nfs-client-provisioner
      apiGroup: rbac.authorization.k8s.io
    
    (2)、创建provisioner供应者、置备程序、存储分配器.
    需要先准备好nfs存储服务器,然后我们使nfs来作为Provisioner(供应者、置备程序),创建一个deploy,使用pod来运行置备程序provisioner,其
    大概配置的就是定义nfs服务器的ip,共享路径等信息;
    [root@master ~]# vim nfs-client-provisioner.yaml		#创建nfs类型的置备程序
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nfs-client-provisioner
      labels:
        app: nfs-client-provisioner
      namespace: default
    spec:
      replicas: 1
      strategy:
        type: Recreate
      selector:
        matchLabels:
          app: nfs-client-provisioner
      template:
        metadata:
          labels:
            app: nfs-client-provisioner
        spec:
          serviceAccountName: nfs-client-provisioner				#这个serviceAccountName就是上面创建ServiceAccount账号
          containers:
            - name: nfs-client-provisioner
              image: quay.io/external_storage/nfs-client-provisioner:latest
              volumeMounts:
                - name: nfs-client-root
                  mountPath: /persistentvolumes
              env:
                - name: PROVISIONER_NAME			#PROVISIONER_NAME的值就是本清单的顶部定义的name
                  value: nfs-client-provisioner
                - name: NFS_SERVER					#这个NFS_SERVER参数的值就是nfs服务器的IP地址
                  value: 192.168.118.128
                - name: NFS_PATH					#这个NFS_PATH参数的值就是nfs服务器的共享目录
                  value: /home/k8s_data
          volumes:
            - name: nfs-client-root
              nfs:									#这里就是配置nfs服务器的ip地址和共享目录
                server: 192.168.118.128
                path: /home/k8s_data
    
    (3)、 创建StorageClass存储类。置备程序provisioner创建好了,下面开始创建一个StorageClass存储类。
    [root@master ~]# vim storageclass.yaml			#创建StorageClass存储类
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: nfs-storageclass
    provisioner: nfs-client-provisioner				#provisioner参数定义置备程序
    reclaimPolicy: Retain							#回收策略,默认是Delete
    parameters:
      archiveOnDelete: "false"
    
    (4)、创建pvc来引用storageclass
    [root@master ~]# vim pvc.yaml			#创建pvc,pvc引用sc
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pvc-nfs
      namespace: default
      labels:
        pvc: nfs
    spec:
      resources:
        requests:
          storage: 200M
      accessModes:
      - ReadWriteMany
      storageClassName: nfs-storageclass	#指定要使用哪个存储类
    
    5、创建业务pod调用pvc就是能实现动态分配存储了。
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata: 
      name: deploy-nginx-pv-pvc
      namespace: default
    spec:
      replicas: 2
      selector:
         matchLabels:
             app: nginx-pv-pvc
      template:
         metadata:
           labels:
             app: nginx-pv-pvc
         spec:
             containers:
             - image: nginx:1.7.9
               name: nginx-container
               ports:
               - name: http 
                 containerPort: 80
               volumeMounts:
               - name: pv-pvc-volume
                 mountPath: /var/log/nginx
             volumes: 
             - name: pv-pvc-volume
               persistentVolumeClaim:
                 claimName: pvc-nfs
                 readOnly: false
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: svc-nginx-pv-pvc
      name: svc-nginx-nodeport-pv-pvc
    spec:
      sessionAffinity: ClientIP
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
        nodePort: 30058
      selector:
        app: nginx-pv-pvc
      type: NodePort
    
    ---
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
  • 相关阅读:
    【Linux初阶】操作系统概念与定位 | 操作系统管理硬件方法、系统调用和库函数概念
    SOCKS5代理在全球电商、游戏及网络爬虫领域的技术创新
    同样的SQL,怎么突然就慢了?
    vue中的provide/inject你知道吗(vue2、vue3)?
    Arm架构下麒麟操作系统安装配置Mariadb数据库
    自然语言处理——基础篇01
    Android编写一个视频监控App
    【前端系列】pnpm 与 npm:现代 JavaScript 包管理工具的比较
    在soc与mcu发送视频数据的过程中如何保证PTS的一致性和准确性
    DHCP动态主机配置协议
  • 原文地址:https://blog.csdn.net/MssGuo/article/details/123611986