k8s十二 | 本地持久化存储Local PV

一、Local PV的设计

LocalPV:Kubernetes直接使用宿主机的本地磁盘目录 ,来持久化存储容器的数据。它的读写性能相比于大多数远程存储来说,要好得多,尤其是SSD盘。

1. Local PV 使用场景

Local Persistent Volume 并不适用于所有应用。它的适用范围非常固定,比如:高优先级的系统应用,需要在多个不同节点上存储数据,而且对 I/O 要求较高。

典型的应用包括:分布式数据存储比如 MongoDB,分布式文件系统比如 GlusterFS、Ceph 等,以及需要在本地磁盘上进行大量数据缓存的分布式应用,其次使用 Local Persistent Volume 的应用必须具备数据备份和恢复的能力,允许你把这些数据定时备份在其他位置。

2. Local PV的实现

LocalPV的实现可以理解为我们前面使用的hostpath加上nodeAffinity,比如:在宿主机NodeA上提前创建好目录 ,然后在定义Pod时添加nodeAffinity=NodeA,指定Pod在我们提前创建好目录的主机上运行。但是我们绝不应该把一个宿主机上的目录当作 PV 使用,因为本地目录的磁盘随时都可能被应用写满,甚至造成整个宿主机宕机。而且,不同的本地目录之间也缺乏哪怕最基础的 I/O 隔离机制。所以,一个 Local Persistent Volume 对应的存储介质,一定是一块额外挂载在宿主机的磁盘或者块设备(“额外”的意思是,它不应该是宿主机根目录所使用的主硬盘)。这个原则,我们可以称为“一个 PV 一块盘”。

3. Local PV 和常规PV的区别

对于常规的 PV,Kubernetes 都是先调度 Pod 到某个节点上,然后再持久化”这台机器上的 Volume 目录。而 Local PV,则需要运维人员提前准备好节点的磁盘。它们在不同节点上的挂载情况可以完全不同,甚至有的节点可以没这种磁盘。所以调度器就必须能够知道所有节点与 Local Persistent Volume 对应的磁盘的关联关系,然后根据这个信息来调度 Pod。也就是在调度的时候考虑Volume 分布。

二、创建Local PV

创建Local PV 其实应该给宿主机挂载并格式化一个可用的磁盘,这里我们就在宿主机上挂载几个 RAM Disk(内存盘)来模拟本地磁盘。

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

如果希望其他节点也能支持 Local Persistent Volume 的话,那就需要为它们也执行上述操作,并且确保这些磁盘的名字(vol1、vol2 等)不重复。

创建Local PV

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 5Gi
  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:
          - k8s-node01

上面定义的local 字段,指定了它是一个 Local Persistent Volume;而 path 字段,指定的正是这个 PV 对应的本地磁盘的路径,即:/mnt/disks/vol1。而这个磁盘存在与k8s-node01节点上,也就意味着 Pod使用这个 PV就必须运行在 node-1 上。所以nodeAffinity 字段就指定 node-1 这个节点的名字,声明PV与节点的对应关系。这正是 Kubernetes 实现“在调度的时候就考虑 Volume 分布”的主要方法。

创建这个PV

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

三、StorageClass的延迟绑定机制

PV 与PVC 的最佳实践,需要创建一个 StorageClass 来描述这个 PV,如下所示:

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

provisioner 字段定义为no-provisioner,这是因为 Local Persistent Volume 目前尚不支持 Dynamic Provisioning动态生成PV,所以我们需要提前手动创建PV。

volumeBindingMode字段定义为WaitForFirstConsumer,它是 Local Persistent Volume 里一个非常重要的特性,即:延迟绑定。延迟绑定就是在我们提交PVC文件时,StorageClass为我们延迟绑定PV与PVC的对应关系。

这样做的原因是:比如我们在当前集群上有两个相同属性的PV,它们分布在不同的节点Node1和Node2上,而我们定义的Pod需要运行在Node1节点上 ,但是StorageClass已经为Pod声明的PVC绑定了在Node2上的PV,这样的话,Pod调度就会失败,所以我们要延迟StorageClass的绑定操作。

也就是延迟到到第一个声明使用该 PVC 的 Pod 出现在调度器之后,调度器再综合考虑所有的调度规则,当然也包括每个 PV 所在的节点位置,来统一决定,这个 Pod 声明的 PVC,到底应该跟哪个 PV 进行绑定。

比如上面的Pod需要运行在node1节点上,StorageClass发现可以绑定的PV后,先不为Pod中的PVC绑定PV,而是等到Pod调度到node1节点后,再为PVC绑定当前节点运行的PV。

所以,通过这个延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度。

现在我们创建StoragClass

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

创建PVC

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

上面定义的StorageClass为“local-storage”,也就是StorageClass看到这个PVC并不会立即为它绑定 PV。

创建资源

$ 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   103s
$ kubectl  get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM               STORAGECLASS    REASON   AGE
example-pv                                 5Gi        RWO            Delete           Available                       local-storage            51m

可以看到当前PVC的状态为Pending,PV与 PVC也没有建立绑定关系。

现在我们创建使用这个PVC的Pod

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

创建这个Pod

$ kubectl  create   -f localpv-pod.yaml 
pod/localpv-pod created
$ kubectl  get pods -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
localpv-pod                               1/1     Running   0          16h   10.244.2.51   k8s-node01              
$ kubectl  get pvc 
NAME                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
example-local-claim   Bound    example-pv                                 5Gi        RWO            local-storage   16h
$ kubectl  get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                         STORAGECLASS    REASON   AGE
example-pv                                 5Gi        RWO            Delete           Bound    default/example-local-claim   local-storage            17h

可以看到Pod调度在“k8s-node01”节点并成功运行后,PVC和PV的状态已经Bound绑定。

现在验证文件是否可以持久化存储,进入当前这个Pod的挂载目录新建一个测试文件

$ kubectl  exec -it  localpv-pod  -- /bin/bash
$ cd /usr/share/nginx/html/
$ touch test.html
$ ls
test.html

在宿主机的挂载目录上查看是否创建

$ ls /mnt/disks/vol1/
test.html

现在我们删除或者重建这个Pod,查看宿主机上是否还存在这个测试文件

$ kubectl  delete pod localpv-pod
pod "localpv-pod" deleted
$ ls /mnt/disks/vol1/
test.html

可以看到文件是依旧存在的,这也说明,像 Kubernetes 这样构建出来的、基于本地存储的 Volume,完全可以提供容器持久化存储的功能。所以,像 StatefulSet 这样的有状态编排工具,也完全可以通过声明 Local 类型的 PV 和 PVC,来管理应用的存储状态。

需要注意的是,我们上面手动创建 PV 的方式,在删除 PV 时需要按如下流程执行操作:

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

如果不按照这个流程的话,这个 PV 的删除就会失败。


参考资料:
深入剖析Kubernetes-张磊


关注公众号回复【k8s】获取视频教程及更多资料:


image.png

你可能感兴趣的:(k8s十二 | 本地持久化存储Local PV)