從一個例子入手PV、PVC
Kubernetes 項目引入了一組叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 對象用於管理存儲卷。
簡單的說PersistentVolume (PV) 是集羣中已由管理員配置的一段網絡存儲,是持久化存儲數據卷;Persistent Volume Claim(PVC)描述的,則是 Pod 所希望使用的持久化存儲的屬性,比如,Volume 存儲的大小、可讀寫權限等等。
上面的這段文字說明可能過於模糊,下面舉個例子看看:
我們定義一個PVC,聲明需要的Volume屬性:
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。
然後再定義一個PV:
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綁定的時候需要滿足:
- PV 和 PVC 的 spec 字段要匹配,比如PV 的存儲(storage)大小,就必須滿足 PVC 的要求。
- 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 容器內的目錄上。
PersistentVolumeController會不斷地查看當前每一個 PVC,是不是已經處於 Bound(已綁定)狀態。如果不是,那它就會遍歷所有的、可用的 PV,並嘗試將其與這個“單身”的 PVC 進行綁定。所以如果出現沒有PV可以和PVC綁定,那麼Pod 的啓動就會報錯。
這個時候就需要用到StorageClass了,在上面我們說的PV和PVC綁定的過程稱爲Static Provisioning,需要手動的創建PV;StorageClass還提供了Dynamic Provisioning機制,可以根據模板創建PV。
StorageClass的Dynamic Provisioning
StorageClass 對象會定義如下兩個部分內容:
- PV 的屬性。比如,存儲類型、Volume 的大小等等。
- 創建這種 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可以重複利用。
刪除流程
一般的情況下,我們遵循這個刪除流程:
- 刪除使用這個 PV 的 Pod;
- 從宿主機移除本地磁盤(比如,umount 它);
- 刪除 PVC;
- 刪除 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