4.深刻k8s:容器持久化存储

img

从一个例子入手PV、PVC

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

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

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

咱们定义一个PVC,声明须要的Volume属性:web

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。shell

而后再定义一个PV:api

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绑定的时候须要知足:frontend

  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 容器内的目录上。ui

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
相关文章
相关标签/搜索