一、Local PV的设计
Local PV: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(内存盘)来模拟本地磁盘。
1$ mkdir /mnt/disks
2$ 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
1apiVersion: v1
2kind: PersistentVolume
3metadata:
4 name: example-pv
5spec:
6 capacity:
7 storage: 5Gi
8 volumeMode: Filesystem
9 accessModes:
10 - ReadWriteOnce
11 persistentVolumeReclaimPolicy: Delete
12 storageClassName: local-storage
13 local:
14 path: /mnt/disks/vol1
15 nodeAffinity:
16 required:
17 nodeSelectorTerms:
18 - matchExpressions:
19 - key: kubernetes.io/hostname
20 operator: In
21 values:
22 - k8s-node01
上面定义的local 字段,指定了它是一个 Local Persistent Volume;而 path 字段,指定的正是这个 PV 对应的本地磁盘的路径,即:/mnt/disks/vol1。而这个磁盘存在与k8s-node01节点上,也就意味着 Pod使用这个 PV就必须运行在 node-1 上。所以nodeAffinity 字段就指定 node-1 这个节点的名字,声明PV与节点的对应关系。这正是 Kubernetes 实现“在调度的时候就考虑 Volume 分布”的主要方法。
创建这个PV
1$ kubectl create -f localpv.yaml
2persistentvolume/example-pv created
3$ kubectl get pv
4NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
5example-pv 5Gi RWO Delete Available local-storage 12s
可以看到当前PV的状态为Available可用。
三、StorageClass的延迟绑定机制
PV 与PVC 的最佳实践,需要创建一个 StorageClass 来描述这个 PV,如下所示:
1kind: StorageClass
2apiVersion: storage.k8s.io/v1
3metadata:
4 name: local-storage
5provisioner: kubernetes.io/no-provisioner
6volumeBindingMode: 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
1$ kubectl create -f localpv-storageclass.yaml
2storageclass.storage.k8s.io/local-storage created
创建PVC
1kind: PersistentVolumeClaim
2apiVersion: v1
3metadata:
4 name: example-local-claim
5spec:
6 accessModes:
7 - ReadWriteOnce
8 resources:
9 requests:
10 storage: 5Gi
11 storageClassName: local-storage
上面定义的StorageClass为“local-storage”,也就是StorageClass看到这个PVC并不会立即为它绑定 PV。
创建资源
1$ kubectl create -f local-pvc.yaml
2persistentvolumeclaim/example-local-claim created
3$ kubectl get pvc
4NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
5example-local-claim Pending local-storage 103s
6$ kubectl get pv
7NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
8example-pv 5Gi RWO Delete Available local-storage 51m
可以看到当前PVC的状态为Pending,PV与 PVC也没有建立绑定关系。
现在我们创建使用这个PVC的Pod
1apiVersion: v1
2kind: Pod
3metadata:
4 name: localpv-pod
5spec:
6 containers:
7 - name: localpv-po-container
8 image: nginx
9 ports:
10 - containerPort: 80
11 name: "http-server"
12 volumeMounts:
13 - mountPath: "/usr/share/nginx/html"
14 name: example-pv-storage
15 volumes:
16 - name: example-pv-storage
17 persistentVolumeClaim:
18 claimName: example-local-claim
创建这个Pod
1$ kubectl create -f localpv-pod.yaml
2pod/localpv-pod created
3$ kubectl get pods -o wide
4NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
5localpv-pod 1/1 Running 0 16h 10.244.2.51 k8s-node01 <none> <none>
6$ kubectl get pvc
7NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
8example-local-claim Bound example-pv 5Gi RWO local-storage 16h
9$ kubectl get pv
10NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
11example-pv 5Gi RWO Delete Bound default/example-local-claim local-storage 17h
可以看到Pod调度在“k8s-node01”节点并成功运行后,PVC和PV的状态已经Bound绑定。
现在验证文件是否可以持久化存储,进入当前这个Pod的挂载目录新建一个测试文件
1$ kubectl exec -it localpv-pod -- /bin/bash
2$ cd /usr/share/nginx/html/
3$ touch test.html
4$ ls
5test.html
在宿主机的挂载目录上查看是否创建
1$ ls /mnt/disks/vol1/
2test.html
现在我们删除或者重建这个Pod,查看宿主机上是否还存在这个测试文件
1$ kubectl delete pod localpv-pod
2pod "localpv-pod" deleted
3$ ls /mnt/disks/vol1/
4test.html
可以看到文件是依旧存在的,这也说明,像 Kubernetes 这样构建出来的、基于本地存储的 Volume,完全可以提供容器持久化存储的功能。所以,像 StatefulSet 这样的有状态编排工具,也完全可以通过声明 Local 类型的 PV 和 PVC,来管理应用的存储状态。
需要注意的是,我们上面手动创建 PV 的方式,在删除 PV 时需要按如下流程执行操作:
删除使用这个 PV 的 Pod;
从宿主机移除本地磁盘(比如,umount 它);
删除 PVC;
删除 PV。
如果不按照这个流程的话,这个 PV 的删除就会失败。
参考资料:
深入剖析Kubernetes-张磊
关注公众号回复【k8s】获取视频教程及更多资料:





