Kubernetes PV & PVC
概念
PV: Persistent Volume,持久化存儲數據卷,Pod想要使用的持久化存儲的屬性,比如存儲的大小、讀寫權限等
PVC: Persistent Volume Claim,是一個具體的Volume的屬性,比如Volume類型、掛載目錄、遠程存儲服務器地址等
爲什麼需要Persistent Volume
所謂容器的Volume,就是將一個宿主機上的目錄,跟一個容器裏的目錄綁定掛載在一起。而所謂的“持久化Volume”,指的就是這個宿主機上的目錄,具備“持久性”。就是這個目錄裏面的內容,既不會因爲容器的刪除而被清理掉,也不會跟當前的宿主機綁定。這樣,當容器被重啓或者在其他節點上重建出來之後,它仍然能夠通過掛載這個Volume,訪問到這些內容。而僅僅通過hostPath和emptyDir類型的Volume無法達到持久化,因爲他們既有可能被kubelet清理掉,也不能被“遷移”到其他節點上。因此,大多數情況下,持久化Volume的實現,往往依賴於一個遠程存儲服務。
爲什麼還需要Persistent Volume Claim
場景:開發人員想使用Volume但是不知道有什麼類型的Volume可以用。
更具體說,作爲一個應用開發者,我可能對持久化存儲項目一竅不通,也不知道公司的kubernetes集羣裏到底是怎麼搭建的,自然也不會編寫它們對應的Volume定義文件。這些關於Volume的管理和遠程持久化存儲的知識,不僅超越了開發者的知識儲備,還會暴露公司基礎設施祕密的風險。
apiVersion: v1
kind: Pod
metadata:
name: rbd
spec:
containers:
- image: kubernetes/pause
name: rbd-rw
volumeMounts:
- name: rbdpd
mountPath: /mnt/rbd
volumes:
- name: rbdpd
rbd:
monitors:
- '10.16.154.78:6789'
- '10.16.154.82:6789'
- '10.16.154.83:6789'
pool: kube
image: foo
fsType: ext4
readOnly: true
user: admin
keyring: /etc/ceph/keyring
imageformat: "2"
imagefeatures: "layering"
例如上面這個Ceph RBD類型Volume的Pod。如果不懂得Ceph RBD的使用方法,volumes裏面絕大部分參數都是不清楚在幹嘛的。另外這個RBD對應的存儲服務器的地址、用戶名、授權文件的位置,也都被輕易地暴露給了全公司的所有開發人員,“過渡暴露”。
因此,爲了降低開發人員使用Volume的門檻,及避免“過渡暴露”,kubernetes提出了PVC,來解耦聲明volume和使用volume的動作。
使用PV和PVC
通過分別聲明PV和PVC,然後在Pod的yaml聲明要用的PVC,完成volume掛載。
PV yaml
聲明一個類型爲nfs的PV,一個PV yaml定義,主要是定義存儲大小,讀寫權限及存儲方式的參數。這裏nfs,就是要指定服務器地址,磁盤位置。
PV一般由運維人員聲明。
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: 10.244.1.4
path: "/"
PVC yaml
首先,定義一個PVC,PVC yaml文件主要聲明要使用的volume的需求是什麼,比如存儲空間大小,可讀寫權限等。
PVC一般由開發人員聲明。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
requests:
storage: 1Gi
然後,Pod聲明使用PVC,在volumes裏申明要使用PVC的名字,即persistentVolumeClaim。等這個Pod創建之後,kubelet就會把這個PVC對應的PV,掛載在這個Pod容器內的目錄上。
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
PV不滿足PVC
在上面的例子中,我們先聲明瞭一個PV,然後再聲明一個PVC,這種情況下PVC一定會會找到對應的PV來綁定。然後,有種情況是,開發人員先聲明瞭PVC,創建Pod的時候報錯表示沒有符合的PV。很明顯,運維人員還沒有聲明PV。
持久化存儲控制器
運維人員發現後馬上創建一個符合開發人員需求的PV,此時kubernetes會完成PVC和PV的綁定操作,然後啓動Pod。
kubernetes能夠馬上把PVC和PV綁定在一起,是因爲在kubernetes中,存在着⼀個專⻔處理持久化存儲的控制器,叫作Volume Controller。這個Volume Controller維護着多個控制循環,其中有⼀個循環,扮演的就是撮合PV和PVC的“紅娘”的⻆⾊。它的名字叫作PersistentVolumeController。
PersistentVolumeController會不斷地查看當前每⼀個PVC,是不是已經處於Bound(已綁定)狀態。如果不是,那它就會遍歷所有的、可⽤的PV,並嘗試將其與這個“單身”的PVC進⾏綁定。這樣,Kubernetes就可以保證⽤戶提交的每⼀個PVC,只要有合適的PV出現,它就能夠很快進⼊綁定狀態,從⽽結束“單身”之旅。
StorageClass
在上面的例子中,我們運維人員發現了有PVC不被滿足,所以馬上創建了一個滿足需求的PV。然後,在一個Kubernetes中可能有成千上萬的PVC,對運維人員來說是一個很大的工作量。kubernetes提供了一個自動創建PV的機制,爲Dynamic Provisioning。相比之前的,則被稱爲Static Provisioning。
Dynamic Provisioning機制的核心是StorageClass。StorageClass的目的主要是創建PV模版。
storage class yaml文件主要定義PV的屬性,比如存儲類型,Volume大小等。然後創建PV需要再用到存儲插件。比如Ceph等。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
PVC指定要使用的StorageClass,相比前面的PVC yaml的storageClassName爲manul,下面的PVC yaml指定了storageClassName。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim1
spec:
accessModes:
- ReadWriteOnce
storageClassName: block-service
resources:
requests:
storage: 30Gi
在PVC創建之後,kubernetes就會通過存儲插件創建一個PV,並且這個PV的StorageClass值和PVC的StorageClass的值相同,就是storageClassName相同。
PV持久化實現
kubernetes需要做的就是,使用這些存儲服務,來爲容器準備一個持久化的宿主機目錄,以供將來進行綁定掛載時使用。而所謂“持久化”,指的是容器在這個目錄裏面寫入的文件,都會保存在遠程存儲中,從而使得這個目錄具備了“持久性”。
這個準備“持久化”宿主機目錄的過程,分爲兩步,Attach和Mount。
Attach
這一步主要是爲虛擬機掛載遠程磁盤。
這一步主要針對Volume類型爲遠程塊存儲,比如Google Cloud的Persistent Disk,那麼kubelet就需要先調用Google Cloud的API,將它所提供的Persistent Disk掛載到Pod所在的宿主機上。如果Volume類型爲遠程文件存儲,例如NFS,那就會跳過這一步,因爲遠程文件存儲並沒有一個“存儲設備”需要掛載在宿主機上。
kubernetes中通過AttachDetachController執行Attach操作,不斷地檢查每一個Pod對應的PV,和這個Pod所在宿主機之間掛載情況。從而決定是否需要對這個PV進行Attach(或者Dettach)操作。這個Controller運行在master節點上。
Mount
Attach完成後,kubelet需要格式化磁盤設備,然後將它掛載到宿主機指定的掛載點上。
kubernetes中通過VolumeManagerReconciler執行Mount操作,是kubelet組件的一部分。它獨立運行於kubelet主循環的Goroutine。
相當於執行:
# 通過lsblk命令獲取磁盤設備ID
$ sudo lsblk
# 格式化成ext4格式
$ sudo mkfs.ext4 -m 0 -F -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/<磁盤設備ID>
# 掛載到掛載點
$ sudo mkdir -p /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume類型>/<Volume名字>
總結
⽤戶提交請求創建pod,Kubernetes發現這個pod聲明使⽤了PVC,那就靠PersistentVolumeController幫它找⼀個PV配對。
沒有現成的PV,就去找對應的StorageClass幫它新創建⼀個PV或讓運維人員創建一個,然後和PVC完成綁定。
新創建的PV,還只是⼀個API 對象,需要經過“兩階段處理”變成宿主機上的“持久化 Volume”才真正有用:
第⼀階段由運⾏在master上的AttachDetachController負責,爲這個PV完成 Attach 操作,爲宿主機掛載遠程磁盤;
第⼆階段是運⾏在每個節點上kubelet組件的內部,把第⼀步attach的遠程磁盤 mount 到宿主機⽬錄。這個控制循環叫VolumeManagerReconciler,運⾏在獨⽴的Goroutine,不會阻塞kubelet主循環。
完成這兩步,PV對應的“持久化 Volume”就準備好了,POD可以正常啓動,將“持久化 Volume”掛載在容器內指定的路徑。