• StorageClass
    • 创建
    • 新建
    • 测试

    StorageClass

    前面的课程中我们学习了 PV 和 PVC 的使用方法,但是前面的 PV 都是静态的,什么意思?就是我要使用的一个 PVC 的话就必须手动去创建一个 PV,我们也说过这种方式在很大程度上并不能满足我们的需求,比如我们有一个应用需要对存储的并发度要求比较高,而另外一个应用对读写速度又要求比较高,特别是对于 StatefulSet 类型的应用简单的来使用静态的 PV 就很不合适了,这种情况下我们就需要用到动态 PV,也就是我们今天要讲解的 StorageClass。

    创建

    要使用 StorageClass,我们就得安装对应的自动配置程序,比如我们这里存储后端使用的是 nfs,那么我们就需要使用到一个 nfs-client 的自动配置程序,我们也叫它 Provisioner,这个程序使用我们已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV。

    • 自动创建的 PV 以${namespace}-${pvcName}-${pvName}这样的命名格式创建在 NFS 服务器上的共享数据目录中
    • 而当这个 PV 被回收后会以archieved-${namespace}-${pvcName}-${pvName}这样的命名格式存在 NFS 服务器上。

    当然在部署nfs-client之前,我们需要先成功安装上 nfs 服务器,前面的课程中我们已经过了,服务地址是10.151.30.57,共享数据目录是/data/k8s/,然后接下来我们部署 nfs-client 即可,我们也可以直接参考 nfs-client 的文档:https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client,进行安装即可。

    第一步:配置 Deployment,将里面的对应的参数替换成我们自己的 nfs 配置(nfs-client.yaml)

    1. kind: Deployment
    2. apiVersion: extensions/v1beta1
    3. metadata:
    4. name: nfs-client-provisioner
    5. spec:
    6. replicas: 1
    7. strategy:
    8. type: Recreate
    9. template:
    10. metadata:
    11. labels:
    12. app: nfs-client-provisioner
    13. spec:
    14. serviceAccountName: nfs-client-provisioner
    15. containers:
    16. - name: nfs-client-provisioner
    17. image: quay.io/external_storage/nfs-client-provisioner:latest
    18. volumeMounts:
    19. - name: nfs-client-root
    20. mountPath: /persistentvolumes
    21. env:
    22. - name: PROVISIONER_NAME
    23. value: fuseim.pri/ifs
    24. - name: NFS_SERVER
    25. value: 10.151.30.57
    26. - name: NFS_PATH
    27. value: /data/k8s
    28. volumes:
    29. - name: nfs-client-root
    30. nfs:
    31. server: 10.151.30.57
    32. path: /data/k8s

    第二步:将环境变量 NFS_SERVER 和 NFS_PATH 替换,当然也包括下面的 nfs 配置,我们可以看到我们这里使用了一个名为 nfs-client-provisioner 的serviceAccount,所以我们也需要创建一个 sa,然后绑定上对应的权限:(nfs-client-sa.yaml)

    1. apiVersion: v1
    2. kind: ServiceAccount
    3. metadata:
    4. name: nfs-client-provisioner
    5. ---
    6. kind: ClusterRole
    7. apiVersion: rbac.authorization.k8s.io/v1
    8. metadata:
    9. name: nfs-client-provisioner-runner
    10. rules:
    11. - apiGroups: [""]
    12. resources: ["persistentvolumes"]
    13. verbs: ["get", "list", "watch", "create", "delete"]
    14. - apiGroups: [""]
    15. resources: ["persistentvolumeclaims"]
    16. verbs: ["get", "list", "watch", "update"]
    17. - apiGroups: ["storage.k8s.io"]
    18. resources: ["storageclasses"]
    19. verbs: ["get", "list", "watch"]
    20. - apiGroups: [""]
    21. resources: ["events"]
    22. verbs: ["list", "watch", "create", "update", "patch"]
    23. ---
    24. kind: ClusterRoleBinding
    25. apiVersion: rbac.authorization.k8s.io/v1
    26. metadata:
    27. name: run-nfs-client-provisioner
    28. subjects:
    29. - kind: ServiceAccount
    30. name: nfs-client-provisioner
    31. namespace: default
    32. roleRef:
    33. kind: ClusterRole
    34. name: nfs-client-provisioner-runner
    35. apiGroup: rbac.authorization.k8s.io

    我们这里新建的一个名为 nfs-client-provisioner 的ServiceAccount,然后绑定了一个名为 nfs-client-provisioner-runner 的ClusterRole,而该ClusterRole声明了一些权限,其中就包括对persistentvolumes的增、删、改、查等权限,所以我们可以利用该ServiceAccount来自动创建 PV。

    第三步:nfs-client 的 Deployment 声明完成后,我们就可以来创建一个StorageClass对象了:(nfs-client-class.yaml)

    1. apiVersion: storage.k8s.io/v1
    2. kind: StorageClass
    3. metadata:
    4. name: course-nfs-storage
    5. provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'

    我们声明了一个名为 course-nfs-storage 的StorageClass对象,注意下面的provisioner对应的值一定要和上面的Deployment下面的 PROVISIONER_NAME 这个环境变量的值一样。

    现在我们来创建这些资源对象吧:

    1. $ kubectl create -f nfs-client.yaml
    2. $ kubectl create -f nfs-client-sa.yaml
    3. $ kubectl create -f nfs-client-class.yaml

    创建完成后查看下资源状态:

    1. $ kubectl get pods
    2. NAME READY STATUS RESTARTS AGE
    3. ...
    4. nfs-client-provisioner-7648b664bc-7f9pk 1/1 Running 0 7h
    5. ...
    6. $ kubectl get storageclass
    7. NAME PROVISIONER AGE
    8. course-nfs-storage fuseim.pri/ifs 11s

    新建

    上面把StorageClass资源对象创建成功了,接下来我们来通过一个示例测试下动态 PV,首先创建一个 PVC 对象:(test-pvc.yaml)

    1. kind: PersistentVolumeClaim
    2. apiVersion: v1
    3. metadata:
    4. name: test-pvc
    5. spec:
    6. accessModes:
    7. - ReadWriteMany
    8. resources:
    9. requests:
    10. storage: 1Mi

    我们这里声明了一个 PVC 对象,采用 ReadWriteMany 的访问模式,请求 1Mi 的空间,但是我们可以看到上面的 PVC 文件我们没有标识出任何和 StorageClass 相关联的信息,那么如果我们现在直接创建这个 PVC 对象能够自动绑定上合适的 PV 对象吗?显然是不能的(前提是没有合适的 PV),我们这里有两种方法可以来利用上面我们创建的 StorageClass 对象来自动帮我们创建一个合适的 PV:

    • 第一种方法:在这个 PVC 对象中添加一个声明 StorageClass 对象的标识,这里我们可以利用一个 annotations 属性来标识,如下

      1. kind: PersistentVolumeClaim
      2. apiVersion: v1
      3. metadata:
      4. name: test-pvc
      5. annotations:
      6. volume.beta.kubernetes.io/storage-class: "course-nfs-storage"
      7. spec:
      8. accessModes:
      9. - ReadWriteMany
      10. resources:
      11. requests:
      12. storage: 1Mi
    • 第二种方法:我们可以设置这个 course-nfs-storage 的 StorageClass 为 Kubernetes 的默认存储后端,我们可以用 kubectl patch 命令来更新:

      1. $ kubectl patch storageclass course-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

    上面这两种方法都是可以的,当然为了不影响系统的默认行为,我们这里还是采用第一种方法,直接创建即可:

    1. $ kubectl create -f test-pvc.yaml
    2. persistentvolumeclaim "test-pvc" created
    3. $ kubectl get pvc
    4. NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
    5. ...
    6. test-pvc Bound pvc-73b5ffd2-8b4b-11e8-b585-525400db4df7 1Mi RWX course-nfs-storage 2m
    7. ...

    我们可以看到一个名为 test-pvc 的 PVC 对象创建成功了,状态已经是 Bound 了,是不是也产生了一个对应的 VOLUME 对象,最重要的一栏是 STORAGECLASS,现在是不是也有值了,就是我们刚刚创建的 StorageClass 对象 course-nfs-storage。

    然后查看下 PV 对象呢:

    1. $ kubectl get pv
    2. NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
    3. ...
    4. pvc-73b5ffd2-8b4b-11e8-b585-525400db4df7 1Mi RWX Delete Bound default/test-pvc course-nfs-storage 8m
    5. ...

    可以看到是不是自动生成了一个关联的 PV 对象,访问模式是 RWX,回收策略是 Delete,这个 PV 对象并不是我们手动创建的吧,这是通过我们上面的 StorageClass 对象自动创建的。这就是 StorageClass 的创建方法。

    测试

    接下来我们还是用一个简单的示例来测试下我们上面用 StorageClass 方式声明的 PVC 对象吧:(test-pod.yaml)

    1. kind: Pod
    2. apiVersion: v1
    3. metadata:
    4. name: test-pod
    5. spec:
    6. containers:
    7. - name: test-pod
    8. image: busybox
    9. imagePullPolicy: IfNotPresent
    10. command:
    11. - "/bin/sh"
    12. args:
    13. - "-c"
    14. - "touch /mnt/SUCCESS && exit 0 || exit 1"
    15. volumeMounts:
    16. - name: nfs-pvc
    17. mountPath: "/mnt"
    18. restartPolicy: "Never"
    19. volumes:
    20. - name: nfs-pvc
    21. persistentVolumeClaim:
    22. claimName: test-pvc

    上面这个 Pod 非常简单,就是用一个 busybox 容器,在 /mnt 目录下面新建一个 SUCCESS 的文件,然后把 /mnt 目录挂载到上面我们新建的 test-pvc 这个资源对象上面了,要验证很简单,只需要去查看下我们 nfs 服务器上面的共享数据目录下面是否有 SUCCESS 这个文件即可:

    1. $ kubectl create -f test-pod.yaml
    2. pod "test-pod" created

    然后我们可以在 nfs 服务器的共享数据目录下面查看下数据:

    1. $ ls /data/k8s/
    2. default-test-pvc-pvc-73b5ffd2-8b4b-11e8-b585-525400db4df7

    我们可以看到下面有名字很长的文件夹,这个文件夹的命名方式是不是和我们上面的规则:${namespace}-${pvcName}-${pvName}是一样的,再看下这个文件夹下面是否有其他文件:

    1. $ ls /data/k8s/default-test-pvc-pvc-73b5ffd2-8b4b-11e8-b585-525400db4df7
    2. SUCCESS

    我们看到下面有一个 SUCCESS 的文件,是不是就证明我们上面的验证是成功的啊。

    另外我们可以看到我们这里是手动创建的一个 PVC 对象,在实际工作中,使用 StorageClass 更多的是 StatefulSet 类型的服务,StatefulSet 类型的服务我们也可以通过一个 volumeClaimTemplates 属性来直接使用 StorageClass,如下:(test-statefulset-nfs.yaml)

    1. apiVersion: apps/v1beta1
    2. kind: StatefulSet
    3. metadata:
    4. name: nfs-web
    5. spec:
    6. serviceName: "nginx"
    7. replicas: 3
    8. template:
    9. metadata:
    10. labels:
    11. app: nfs-web
    12. spec:
    13. terminationGracePeriodSeconds: 10
    14. containers:
    15. - name: nginx
    16. image: nginx:1.7.9
    17. ports:
    18. - containerPort: 80
    19. name: web
    20. volumeMounts:
    21. - name: www
    22. mountPath: /usr/share/nginx/html
    23. volumeClaimTemplates:
    24. - metadata:
    25. name: www
    26. annotations:
    27. volume.beta.kubernetes.io/storage-class: course-nfs-storage
    28. spec:
    29. accessModes: [ "ReadWriteOnce" ]
    30. resources:
    31. requests:
    32. storage: 1Gi

    实际上 volumeClaimTemplates 下面就是一个 PVC 对象的模板,就类似于我们这里 StatefulSet 下面的 template,实际上就是一个 Pod 的模板,我们不单独创建成 PVC 对象,而用这种模板就可以动态的去创建了对象了,这种方式在 StatefulSet 类型的服务下面使用得非常多。

    直接创建上面的对象:

    1. $ kubectl create -f test-statefulset-nfs.yaml
    2. statefulset.apps "nfs-web" created
    3. $ kubectl get pods
    4. NAME READY STATUS RESTARTS AGE
    5. ...
    6. nfs-web-0 1/1 Running 0 1m
    7. nfs-web-1 1/1 Running 0 1m
    8. nfs-web-2 1/1 Running 0 33s
    9. ...

    创建完成后可以看到上面的3个 Pod 已经运行成功,然后查看下 PVC 对象:

    1. $ kubectl get pvc
    2. NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
    3. ...
    4. www-nfs-web-0 Bound pvc-cc36b3ce-8b50-11e8-b585-525400db4df7 1Gi RWO course-nfs-storage 2m
    5. www-nfs-web-1 Bound pvc-d38285f9-8b50-11e8-b585-525400db4df7 1Gi RWO course-nfs-storage 2m
    6. www-nfs-web-2 Bound pvc-e348250b-8b50-11e8-b585-525400db4df7 1Gi RWO course-nfs-storage 1m
    7. ...

    我们可以看到是不是也生成了3个 PVC 对象,名称由模板名称 name 加上 Pod 的名称组合而成,这3个 PVC 对象也都是 绑定状态了,很显然我们查看 PV 也可以看到对应的3个 PV 对象:

    1. $ kubectl get pv
    2. NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
    3. ... 1d
    4. pvc-cc36b3ce-8b50-11e8-b585-525400db4df7 1Gi RWO Delete Bound default/www-nfs-web-0 course-nfs-storage 4m
    5. pvc-d38285f9-8b50-11e8-b585-525400db4df7 1Gi RWO Delete Bound default/www-nfs-web-1 course-nfs-storage 4m
    6. pvc-e348250b-8b50-11e8-b585-525400db4df7 1Gi RWO Delete Bound default/www-nfs-web-2 course-nfs-storage 4m
    7. ...

    查看 nfs 服务器上面的共享数据目录:

    1. $ ls /data/k8s/
    2. ...
    3. default-www-nfs-web-0-pvc-cc36b3ce-8b50-11e8-b585-525400db4df7
    4. default-www-nfs-web-1-pvc-d38285f9-8b50-11e8-b585-525400db4df7
    5. default-www-nfs-web-2-pvc-e348250b-8b50-11e8-b585-525400db4df7
    6. ...

    是不是也有对应的3个数据目录,这就是我们的 StorageClass 的使用方法,对于 StorageClass 多用于 StatefulSet 类型的服务,在后面的课程中我们还学不断的接触到。