一、Local PV的設(shè)計(jì)
LocalPV:Kubernetes直接使用宿主機(jī)的本地磁盤目錄 ,來持久化存儲(chǔ)容器的數(shù)據(jù)。它的讀寫性能相比于大多數(shù)遠(yuǎn)程存儲(chǔ)來說,要好得多,尤其是SSD盤。
1. Local PV 使用場景
Local Persistent Volume 并不適用于所有應(yīng)用。它的適用范圍非常固定,比如:高優(yōu)先級(jí)的系統(tǒng)應(yīng)用,需要在多個(gè)不同節(jié)點(diǎn)上存儲(chǔ)數(shù)據(jù),而且對(duì) I/O 要求較高。
典型的應(yīng)用包括:分布式數(shù)據(jù)存儲(chǔ)比如 MongoDB,分布式文件系統(tǒng)比如 GlusterFS、Ceph 等,以及需要在本地磁盤上進(jìn)行大量數(shù)據(jù)緩存的分布式應(yīng)用,其次使用 Local Persistent Volume 的應(yīng)用必須具備數(shù)據(jù)備份和恢復(fù)的能力,允許你把這些數(shù)據(jù)定時(shí)備份在其他位置。
2. Local PV的實(shí)現(xiàn)
LocalPV的實(shí)現(xiàn)可以理解為我們前面使用的hostpath加上nodeAffinity,比如:在宿主機(jī)NodeA上提前創(chuàng)建好目錄 ,然后在定義Pod時(shí)添加nodeAffinity=NodeA,指定Pod在我們提前創(chuàng)建好目錄的主機(jī)上運(yùn)行。但是我們絕不應(yīng)該把一個(gè)宿主機(jī)上的目錄當(dāng)作 PV 使用,因?yàn)楸镜啬夸浀拇疟P隨時(shí)都可能被應(yīng)用寫滿,甚至造成整個(gè)宿主機(jī)宕機(jī)。而且,不同的本地目錄之間也缺乏哪怕最基礎(chǔ)的 I/O 隔離機(jī)制。所以,一個(gè) Local Persistent Volume 對(duì)應(yīng)的存儲(chǔ)介質(zhì),一定是一塊額外掛載在宿主機(jī)的磁盤或者塊設(shè)備(“額外”的意思是,它不應(yīng)該是宿主機(jī)根目錄所使用的主硬盤)。這個(gè)原則,我們可以稱為“一個(gè) PV 一塊盤”。
3. Local PV 和常規(guī)PV的區(qū)別
對(duì)于常規(guī)的 PV,Kubernetes 都是先調(diào)度 Pod 到某個(gè)節(jié)點(diǎn)上,然后再持久化”這臺(tái)機(jī)器上的 Volume 目錄。而 Local PV,則需要運(yùn)維人員提前準(zhǔn)備好節(jié)點(diǎn)的磁盤。它們?cè)诓煌?jié)點(diǎn)上的掛載情況可以完全不同,甚至有的節(jié)點(diǎn)可以沒這種磁盤。所以調(diào)度器就必須能夠知道所有節(jié)點(diǎn)與 Local Persistent Volume 對(duì)應(yīng)的磁盤的關(guān)聯(lián)關(guān)系,然后根據(jù)這個(gè)信息來調(diào)度 Pod。也就是在調(diào)度的時(shí)候考慮Volume 分布。
二、創(chuàng)建Local PV
創(chuàng)建Local PV 其實(shí)應(yīng)該給宿主機(jī)掛載并格式化一個(gè)可用的磁盤,這里我們就在宿主機(jī)上掛載幾個(gè) RAM Disk(內(nèi)存盤)來模擬本地磁盤。
$ mkdir /mnt/disks
$ for vol in vol1 vol2 vol3; do mkdir /mnt/disks/$vol; mount -t tmpfs $vol /mnt/disks/$vol; done
如果希望其他節(jié)點(diǎn)也能支持 Local Persistent Volume 的話,那就需要為它們也執(zhí)行上述操作,并且確保這些磁盤的名字(vol1、vol2 等)不重復(fù)。
創(chuàng)建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 字段,指定了它是一個(gè) Local Persistent Volume;而 path 字段,指定的正是這個(gè) PV 對(duì)應(yīng)的本地磁盤的路徑,即:/mnt/disks/vol1。而這個(gè)磁盤存在與k8s-node01節(jié)點(diǎn)上,也就意味著 Pod使用這個(gè) PV就必須運(yùn)行在 node-1 上。所以nodeAffinity 字段就指定 node-1 這個(gè)節(jié)點(diǎn)的名字,聲明PV與節(jié)點(diǎn)的對(duì)應(yīng)關(guān)系。這正是 Kubernetes 實(shí)現(xiàn)“在調(diào)度的時(shí)候就考慮 Volume 分布”的主要方法。
創(chuàng)建這個(gè)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的延遲綁定機(jī)制
PV 與PVC 的最佳實(shí)踐,需要?jiǎng)?chuàng)建一個(gè) StorageClass 來描述這個(gè) PV,如下所示:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
provisioner 字段定義為no-provisioner,這是因?yàn)?Local Persistent Volume 目前尚不支持 Dynamic Provisioning動(dòng)態(tài)生成PV,所以我們需要提前手動(dòng)創(chuàng)建PV。
volumeBindingMode字段定義為WaitForFirstConsumer,它是 Local Persistent Volume 里一個(gè)非常重要的特性,即:延遲綁定。延遲綁定就是在我們提交PVC文件時(shí),StorageClass為我們延遲綁定PV與PVC的對(duì)應(yīng)關(guān)系。
這樣做的原因是:比如我們?cè)诋?dāng)前集群上有兩個(gè)相同屬性的PV,它們分布在不同的節(jié)點(diǎn)Node1和Node2上,而我們定義的Pod需要運(yùn)行在Node1節(jié)點(diǎn)上 ,但是StorageClass已經(jīng)為Pod聲明的PVC綁定了在Node2上的PV,這樣的話,Pod調(diào)度就會(huì)失敗,所以我們要延遲StorageClass的綁定操作。
也就是延遲到到第一個(gè)聲明使用該 PVC 的 Pod 出現(xiàn)在調(diào)度器之后,調(diào)度器再綜合考慮所有的調(diào)度規(guī)則,當(dāng)然也包括每個(gè) PV 所在的節(jié)點(diǎn)位置,來統(tǒng)一決定,這個(gè) Pod 聲明的 PVC,到底應(yīng)該跟哪個(gè) PV 進(jìn)行綁定。
比如上面的Pod需要運(yùn)行在node1節(jié)點(diǎn)上,StorageClass發(fā)現(xiàn)可以綁定的PV后,先不為Pod中的PVC綁定PV,而是等到Pod調(diào)度到node1節(jié)點(diǎn)后,再為PVC綁定當(dāng)前節(jié)點(diǎn)運(yùn)行的PV。
所以,通過這個(gè)延遲綁定機(jī)制,原本實(shí)時(shí)發(fā)生的 PVC 和 PV 的綁定過程,就被延遲到了 Pod 第一次調(diào)度的時(shí)候在調(diào)度器中進(jìn)行,從而保證了這個(gè)綁定結(jié)果不會(huì)影響 Pod 的正常調(diào)度。
現(xiàn)在我們創(chuàng)建StoragClass
$ kubectl create -f localpv-storageclass.yaml
storageclass.storage.k8s.io/local-storage created
創(chuàng)建PVC
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: example-local-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage
上面定義的StorageClass為“l(fā)ocal-storage”,也就是StorageClass看到這個(gè)PVC并不會(huì)立即為它綁定 PV。
創(chuàng)建資源
$ 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
可以看到當(dāng)前PVC的狀態(tài)為Pending,PV與 PVC也沒有建立綁定關(guān)系。
現(xiàn)在我們創(chuàng)建使用這個(gè)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
創(chuàng)建這個(gè)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 <none> <none>
$ 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調(diào)度在“k8s-node01”節(jié)點(diǎn)并成功運(yùn)行后,PVC和PV的狀態(tài)已經(jīng)Bound綁定。
現(xiàn)在驗(yàn)證文件是否可以持久化存儲(chǔ),進(jìn)入當(dāng)前這個(gè)Pod的掛載目錄新建一個(gè)測試文件
$ kubectl exec -it localpv-pod -- /bin/bash
$ cd /usr/share/nginx/html/
$ touch test.html
$ ls
test.html
在宿主機(jī)的掛載目錄上查看是否創(chuàng)建
$ ls /mnt/disks/vol1/
test.html
現(xiàn)在我們刪除或者重建這個(gè)Pod,查看宿主機(jī)上是否還存在這個(gè)測試文件
$ kubectl delete pod localpv-pod
pod "localpv-pod" deleted
$ ls /mnt/disks/vol1/
test.html
可以看到文件是依舊存在的,這也說明,像 Kubernetes 這樣構(gòu)建出來的、基于本地存儲(chǔ)的 Volume,完全可以提供容器持久化存儲(chǔ)的功能。所以,像 StatefulSet 這樣的有狀態(tài)編排工具,也完全可以通過聲明 Local 類型的 PV 和 PVC,來管理應(yīng)用的存儲(chǔ)狀態(tài)。
需要注意的是,我們上面手動(dòng)創(chuàng)建 PV 的方式,在刪除 PV 時(shí)需要按如下流程執(zhí)行操作:
- 刪除使用這個(gè) PV 的 Pod;
- 從宿主機(jī)移除本地磁盤(比如,umount 它);
- 刪除 PVC;
- 刪除 PV。
如果不按照這個(gè)流程的話,這個(gè) PV 的刪除就會(huì)失敗。
參考資料:
深入剖析Kubernetes-張磊
關(guān)注公眾號(hào)回復(fù)【k8s】獲取視頻教程及更多資料:
