美文网首页Java架构技术进阶互联网科技老男孩的成长之路
深入Kubernetes:K8s的容器持久化存储操作

深入Kubernetes:K8s的容器持久化存储操作

作者: Java入门到入坟 | 来源:发表于2020-08-21 14:42 被阅读0次

    推荐阅读:

    从一个例子入手PV、PVC

    Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 对象用于管理存储卷。

    简单的说PersistentVolume (PV) 是集群中已由管理员配置的一段网络存储,是持久化存储数据卷;Persistent Volume Claim(PVC)描述的,则是 Pod 所希望使用的持久化存储的属性,比如,Volume 存储的大小、可读写权限等等。

    上面的这段文字说明可能过于模糊,下面举个例子看看:

    我们定义一个PVC,声明需要的Volume属性:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: nfs
    spec:
      accessModes:
        - ReadWriteMany
      storageClassName: manual
      resources:
        requests:
          storage: 1Gi
    

    yaml文件中定义了一个1 GiB的PVC,Access Modes表示需要的volume存储类型,ReadWriteOnce表示只能在一个node节点上进行读写操作,其他的Access Modes详见:https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes

    然后再定义一个PV:

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: nfs
    spec:
      storageClassName: manual
      capacity:
        storage: 1Gi
      accessModes:
        - ReadWriteMany
      nfs:
        server: 10.244.1.4
        path: "/"
    

    这个 PV 对象中会详细定义存储的类型是NFS,以及大小是1 GiB。

    PVC和PV相当于“接口”和“实现”,所以我们需要将PVC和PV绑定起来才可以使用,而PVC和PV绑定的时候需要满足:

    1. PV 和 PVC 的 spec 字段要匹配,比如PV 的存储(storage)大小,就必须满足 PVC 的要求。
    2. PV 和 PVC 的 storageClassName 字段必须一样才能进行绑定。storageClassName表示的是StorageClass的name属性。

    做好PVC的声明之后,并建立好PV,然后就可以使用这个PVC了:

    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        role: web-frontend
    spec:
      containers:
      - name: web
        image: nginx
        ports:
          - name: web
            containerPort: 80
        volumeMounts:
            - name: nfs
              mountPath: "/usr/share/nginx/html"
      volumes:
      - name: nfs
        persistentVolumeClaim:
          claimName: nfs
    

    在Pod中只需要声明PVC的名字,等Pod创建后kubelet 就会把这个 PVC 所对应的 PV,也就是一个 NFS 类型的 Volume,挂载在这个 Pod 容器内的目录上。

    PersistentVolumeController会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。所以如果出现没有PV可以和PVC绑定,那么Pod 的启动就会报错。

    这个时候就需要用到StorageClass了,在上面我们说的PV和PVC绑定的过程称为Static Provisioning,需要手动的创建PV;StorageClass还提供了Dynamic Provisioning机制,可以根据模板创建PV。

    StorageClass的Dynamic Provisioning

    StorageClass 对象会定义如下两个部分内容:

    1. PV 的属性。比如,存储类型、Volume 的大小等等。
    2. 创建这种 PV 需要用到的存储插件。比如,Ceph 等等。

    这样k8s就能够根据用户提交的 PVC,找到一个对应的 StorageClass ,然后调用该 StorageClass 声明的存储插件,创建出需要的 PV。

    例如声明如下StorageClass:

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: block-service
    provisioner: kubernetes.io/gce-pd
    parameters:
      type: pd-ssd
    

    这里定义了名叫 block-service 的 StorageClass,provisioner 字段的值是:kubernetes.io/gce-pd,这是k8s内置的存储插件,type字段也是跟着provisioner定义的,官方默认支持 Dynamic Provisioning 的内置存储插件:https://kubernetes.io/docs/concepts/storage/storage-classes/

    然后就可以在PVC中声明storageClassName为block-service,当创建好PVC 对象之后,k8s就会调用相应的存储插件API创建一个PV对象。

    如下:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: claim1
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: block-service
      resources:
        requests:
          storage: 30Gi
    

    这种自动创建PV的机制就是Dynamic Provisioning,Kubernetes 就能够根据用户提交的 PVC,找到一个对应的 StorageClass ,然后会调用StorageClass 声明的存储插件,创建出需要的 PV。

    需要注意的是,如果没有声明StorageClassName在PVC中,PVC 的 storageClassName 的值就是"",这也意味着它只能够跟 storageClassName 也是""的 PV 进行绑定。

    PV和PVC的生命周期

    PV和PVC之间的相互作用遵循这个生命周期:

    Provisioning —>Binding —>Using —>Reclaiming

    Provisioning

    k8s提供了两种PV生成方式: statically or dynamically

    statically:由管理员创建PV,它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费。

    dynamically:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试为PVC动态配置卷。 此配置基于StorageClasses,PVC必须请求一个StorageClasses,并且管理员必须已创建并配置该类才能进行动态配置。

    Binding

    由用户创建好PersistentVolumeClaim 后,PersistentVolumeController会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。

    Using

    Pods声明并使用PVC作为volume后,集群会找到该PVC,如果该PVC已经绑定了PV,那么会将该volume挂载到Pod中。

    Reclaiming

    当用户已经不再使用该volume,可以将该PVC删除,以便让资源得以回收。相应的在PVC删除后,PV的回收策略可以是Retained, Recycled, or Deleted,这个策略可以在字段spec.persistentVolumeReclaimPolicy中设置。

    • Retain:这个策略允许手动回收资源,当PVC被删除后,PV仍然可以存在,管理员可以手动的执行删除PV,并且和PV绑定的存储资源也不会被删除,如果想要删除相应的存储资源的数据,需要手动删除对应存储资源的数据。
    • Delete:这个策略会在PVC被删除之后,连带将PV以及PV管理的存储资源也删除。
    • Recycle:相当于在volume中执行rm -rf /thevolume/*命令,以便让volume可以重复利用。

    删除流程

    一般的情况下,我们遵循这个删除流程:

    1. 删除使用这个 PV 的 Pod;
    2. 从宿主机移除本地磁盘(比如,umount 它);
    3. 删除 PVC;
    4. 删除 PV。

    Local Persistent Volume实战

    Local Persistent Volume适用于类似分布式数据存储比如 MongoDB、Cassandra等需要在多个不同节点上存储数据,并且对I/O 较为敏感的应用。但是相比于正常的 PV,一旦这些节点宕机且不能恢复时,Local Persistent Volume 的数据就可能丢失。

    在我们的实验环境中,在宿主机上挂载几个 RAM Disk(内存盘)来模拟本地磁盘。例如:

    我们在node1节点上挂载几个磁盘

    $ mkdir /mnt/disks
    $ for vol in vol1 vol2 vol3; do
        mkdir /mnt/disks/$vol
        mount -t tmpfs $vol /mnt/disks/$vol
    done
    

    然后创建相应的PV:

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: example-pv
    spec:
      capacity:
        storage: 512Mi
      volumeMode: Filesystem
      accessModes:
      - ReadWriteOnce
      persistentVolumeReclaimPolicy: Delete
      storageClassName: local-storage
      local:
        path: /mnt/disks/vol1
      nodeAffinity:
        required:
          nodeSelectorTerms:
          - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
              - node1
    

    这个 PV 的定义里:local 字段指定了它是一个 Local Persistent Volume;而 path 字段,指定的正是这个 PV 对应的本地磁盘的路径,即:/mnt/disks/vol1。并且用nodeAffinity指定这个PV必须运行在node1节点上。

    运行上面的PV:

    $ kubectl create -f local-pv.yaml 
    persistentvolume/example-pv created
    
    $ kubectl get pv
    NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY  STATUS      CLAIM             STORAGECLASS    REASON    AGE
    example-pv   512Mi        RWO            Delete           Available                     local-storage             16s
    

    然后创建一个StorageClass 来描述这个 PV:

    kind: StorageClass
    apiVersion: storage.k8s.io/v1
    metadata:
      name: local-storage
    provisioner: kubernetes.io/no-provisioner
    volumeBindingMode: WaitForFirstConsumer
    

    这个 StorageClass叫local-storage,provisioner为no-provisioner表示不需要自动创建PV。

    volumeBindingMode=WaitForFirstConsumer表示需要等到Pod运行之后才让PVC和PV绑定。因为在使用Local Persistent Volume的时候PV和对应的PVC必须要跟随Pod在同一node下面,否则会调度失败。

    然后我们运行StorageClass:

    $ kubectl create -f local-sc.yaml 
    storageclass.storage.k8s.io/local-storage created
    

    再创建一个PVC:

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: example-local-claim
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 512Mi
      storageClassName: local-storage
    

    这里注意声明storageClassName需要是我们上面创建的StorageClass。

    然后创建PVC:

    $ kubectl create -f local-pvc.yaml 
    persistentvolumeclaim/example-local-claim created
    
    $ kubectl get pvc
    NAME                  STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS    AGE
    example-local-claim   Pending                                       local-storage   7s
    

    这个时候因为还没创建Pod,所以状态还是Pending。

    创建一个pod:

    kind: Pod
    apiVersion: v1
    metadata:
      name: example-pv-pod
    spec:
      volumes:
        - name: example-pv-storage
          persistentVolumeClaim:
           claimName: example-local-claim
      containers:
        - name: example-pv-container
          image: nginx
          ports:
            - containerPort: 80
              name: "http-server"
          volumeMounts:
            - mountPath: "/usr/share/nginx/html"
              name: example-pv-storage
    

    然后我们创建pod后再看看PVC绑定状态:

    $ kubectl create -f local-pod.yaml 
    pod/example-pv-pod created
    
    $ kubectl get pvc
    NAME                  STATUS    VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS    AGE
    example-local-claim   Bound     example-pv   512Mi        RWO            local-storage   6h
    

    然后我们试着写入一个文件到/usr/share/nginx/html中:

    $ kubectl exec -it example-pv-pod -- /bin/sh
    # cd /usr/share/nginx/html
    # touch test.txt
    
    # 在node1上
    $ ls /mnt/disks/vol1
    test.txt
    

    相关文章

      网友评论

        本文标题:深入Kubernetes:K8s的容器持久化存储操作

        本文链接:https://www.haomeiwen.com/subject/sefzdktx.html