1. 程式人生 > >4.深入k8s:容器持久化儲存

4.深入k8s:容器持久化儲存

![img](https://img.luozhiyun.com/20200802155232.jpg) ### 從一個例子入手PV、PVC Kubernetes 專案引入了一組叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 物件用於管理儲存卷。 簡單的說PersistentVolume (PV) 是叢集中已由管理員配置的一段網路儲存,是持久化儲存資料卷;Persistent Volume Claim(PVC)描述的,則是 Pod 所希望使用的持久化儲存的屬性,比如,Volume 儲存的大小、可讀寫許可權等等。 上面的這段文字說明可能過於模糊,下面舉個例子看看: 我們定義一個PVC,宣告需要的Volume屬性: ```yaml 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: ```yaml 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繫結的時候需要滿足: 1. PV 和 PVC 的 spec 欄位要匹配,比如PV 的儲存(storage)大小,就必須滿足 PVC 的要求。 2. PV 和 PVC 的 storageClassName 欄位必須一樣才能進行繫結。storageClassName表示的是StorageClass的name屬性。 做好PVC的宣告之後,並建立好PV,然後就可以使用這個PVC了: ```yaml 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 物件會定義如下兩個部分內容: 1. PV 的屬性。比如,儲存型別、Volume 的大小等等。 2. 建立這種 PV 需要用到的儲存外掛。比如,Ceph 等等。 這樣k8s就能夠根據使用者提交的 PVC,找到一個對應的 StorageClass ,然後呼叫該 StorageClass 宣告的儲存外掛,創建出需要的 PV。 例如宣告如下StorageClass: ```yaml 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物件。 如下: ```yaml 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可以重複利用。 #### 刪除流程 一般的情況下,我們遵循這個刪除流程: 1. 刪除使用這個 PV 的 Pod; 2. 從宿主機移除本地磁碟(比如,umount 它); 3. 刪除 PVC; 4. 刪除 PV。 ### Local Persistent Volume實戰 Local Persistent Volume適用於類似分散式資料儲存比如 MongoDB、Cassandra等需要在多個不同節點上儲存資料,並且對I/O 較為敏感的應用。但是相比於正常的 PV,一旦這些節點宕機且不能恢復時,Local Persistent Volume 的資料就可能丟失。 在我們的實驗環境中,在宿主機上掛載幾個 RAM Disk(記憶體盤)來模擬本地磁碟。例如: 我們在node1節點上掛載幾個磁碟 ```shell $ mkdir /mnt/disks $ for vol in vol1 vol2 vol3; do mkdir /mnt/disks/$vol mount -t tmpfs $vol /mnt/disks/$vol done ``` 然後建立相應的PV: ```yaml 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: ```yaml $ 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: ```yaml 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: ```shell $ kubectl create -f local-sc.yaml storageclass.storage.k8s.io/local-storage created ``` 再建立一個PVC: ```yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: example-local-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 512Mi storageClassName: local-storage ``` 這裡注意宣告storageClassName需要是我們上面建立的StorageClass。 然後建立PVC: ```shell $ 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: ```yaml 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繫結狀態: ```shell $ 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中: ```shell $ kubectl exec -it example-pv-pod -- /bin/sh # cd /usr/share/nginx/html # touch test.txt # 在node1上 $ ls /mnt/disks/vol1 test