Kubernetes持久化数据存储-StorageClass

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

1、创建StorageClass

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

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

当然在部署 nfs-client 之前,我们需要先成功安装上 nfs 服务器,前面我们已经安装过了,服务地址是172.16.200.1,共享数据目录是/data/k8s/,接下来我们直接部署 nfs-client 即可:

NFS_Client Github地址:https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client

第1步:配置 Deployment

将里面对应的参数替换成自己的 nfs 配置(nfs-client.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  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
      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
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 172.16.200.1
            - name: NFS_PATH
              value: /data/k8s
      volumes:
        - name: nfs-client-root
          nfs:
            server: 172.16.200.1
            path: /data/k8s

第2步:配置环境变量

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

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
---
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"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
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

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

第3步:创建StorageClass

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

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: course-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, 必须和 deployment's env PROVISIONER_NAME' 一致
parameters:
  archiveOnDelete: "false"

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

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

kubectl create -f nfs-client.yaml
kubectl create -f nfs-client-sa.yaml
kubectl create -f nfs-client-class.yaml

创建完成后查看下资源状态,我们可以看到storage-class的绑定模式:立即绑定;回收模式:删除;允允许扩容:否:

img

2、新建PVC

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

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "course-nfs-storage" # 对应class名称
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

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

img

第1种方法:添加 StorageClass 对象标识

在这个 PVC 对象中添加⼀个声明 StorageClass 对象的标识,我们可以利用⼀个 annotations 属性来标识,如下:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "course-nfs-storage"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

第2种方法:设置默认存储后端

我们可以设置 course-nfs-storage 的 StorageClass 为 Kubernetes 的默认存储后端,然后使用 kubectl patch 命令来更新:

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

上⾯两种方法都是可以的,当然为了不影响系统的默认行为,我们还是采用第⼀种方法,直接创建即可。我们可以看到⼀个名为 test-pvc 的 PVC 对象创建成功了,状态已经是 Bound 了,当然也产生了⼀个对应的 VOLUME 对象,最重要的⼀栏是 STORAGECLASS,现在也有值了,就是我们刚刚创建的 StorageClass 对象 course-nfs-storage。然后查看 PV 对象:

img

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

3、测试StorageClass

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

kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: busybox
    imagePullPolicy: IfNotPresent
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
      - name: nfs-pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: test-claim

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

img

我们可以看到下⾯有名字很长的文件夹,这个文件夹的命名方式是不是和上面的规则:{namespace}-namespace−{pvcName}-${pvName} 是⼀样的。我们看到下面有⼀个 SUCCESS 的文件,就可以证明上⾯的验证已经成功了。

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

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nfs-web
spec:
  selector:
    matchLabels:
      app: nfs-web
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels:
        app: nfs-web
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
      annotations:
        volume.beta.kubernetes.io/storage-class: course-nfs-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

实际上 volumeClaimTemplates 下面就是⼀个 PVC 对象的模板,就类似于这里 StatefulSet 下面的 template,实际上就是⼀个 Pod 的模板,我们不单独创建成 PVC 对象,而是用这种模板就可以动态的去创建对象了,这种方式在 StatefulSet 类型的服务下⾯使用得非常多。直接创建上面的对象,创建完成后可以看到上面的3个 Pod 已经运行成功,然后查看下 PVC 对象:

img

我们可以看到也生成了3个 PVC 对象,名称由模板名称 name 加上 Pod 的名称组合而成,这3个 PVC 对象也都是绑定状态了,很显然我们查看 PV 也可以看到对应的3个 PV 对象。查看 nfs 服务器上面的共享数据目录:

img

也有对应的3个数据目录,这就是 StorageClass 的使用方法,对于 StorageClass 多用于StatefulSet 类型的服务;后面我们也会不断的接触到这些知识。

推荐文章