【Kubernetes】K8s筆記(十三):PersistentVolume 解決數據持久化問題
0. ConfigMap 和 Secret 中的 Volume
【Kubernetes】K8s筆記(五):應用的配置管理 ConfigMap / Secret 提到了 Volume 存儲卷的概念。它使用字段 volumes
和 volumeMounts
將配置信息掛載到 Pod 中供進程使用。
本篇筆記會講述 Volume 的高級用法,看看 Kubernetes 管理存儲資源的 API 對象 PersistentVolume、PersistentVolumeClaim、StorageClass,然后使用本地磁盤來創建實際可用的存儲卷。
1. PersistentVolume
Pod 里的容器是由鏡像產生的,而鏡像文件本身是只讀的,進程要讀寫磁盤只能用一個臨時的存儲空間,一旦 Pod 銷毀,臨時存儲也就會立即回收釋放,數據也就丟失了。為了保證即使 Pod 銷毀后重建數據依然存在,Kubernetes 就順著 Volume 的概念,延伸出了 PersistentVolume 對象,它專門用來表示持久存儲設備,但隱藏了存儲的底層實現,我們只需要知道它能安全可靠地保管數據就可以了(由于 PersistentVolume 這個詞很長,一般都把它簡稱為 PV)。
作為存儲的抽象,PV 實際上就是一些存儲設備、文件系統,比如 Ceph、GlusterFS、NFS,甚至是本地磁盤,管理它們已經超出了 Kubernetes 的能力范圍,所以,一般會由系統管理員單獨維護,然后再在 Kubernetes 里創建對應的 PV。
要注意的是,PV 屬于集群的系統資源,是和 Node 平級的一種對象,Pod 對它沒有管理權,只有使用權。
PersistentVolumeClaim 和 StorageClass
PV 無法直接掛載到 Pod 里面使用,因為不同的存儲設備的差異巨大:速度快慢、共享/獨占讀寫、容量大小...這么多種存儲設備,只用一個 PV 對象來管理還是有點太勉強了,不符合“單一職責”的原則,讓 Pod 直接去選擇 PV 也很不靈活。
于是 Kubernetes 就又增加了兩個新對象,PersistentVolumeClaim 和 StorageClass,用的還是“中間層”的思想,把存儲卷的分配管理過程再次細化。
PersistentVolumeClaim 簡稱 PVC。它是用來向 Kubernetes 申請存儲資源的 API 對象,用來給 Pod 使用,相當于 Pod 的存儲代理,代表 Pod 向系統申請 PV。一旦資源申請成功,Kubernetes 就會把 PV 和 PVC 關聯在一起,這個動作叫做“綁定”(bind)。
但是,系統里的存儲資源非常多,如果要 PVC 去直接遍歷查找合適的 PV 也很麻煩,所以就要用到 StorageClass。
StorageClass 抽象了特定類型的存儲系統(比如 Ceph、NFS),在 PVC 和 PV 之間充當“協調人”的角色,幫助 PVC 找到合適的 PV。也就是說它可以簡化 Pod 掛載“虛擬盤”的過程,讓 Pod 看不到 PV 的實現細節。
2. 使用 YAML 描述 PersistentVolume
Kubernetes 里有很多種類型的 PV,最基礎的是本機存儲 HostPath
。它和 Docker 中使用 docker run -v
使用的參數 -v
非常相似。
因為 Pod 會在集群的任意節點上運行,所以首先,我們要作為系統管理員在每個節點上創建一個目錄,它將會作為本地存儲卷掛載到 Pod 里:在 /tmp
目錄下建立名字為 host-10mb-pv
的文件夾,表示一個容量為 10 MB 的存儲設備。
目前只能用 kubectl api-resources
kubectl explain
查看 PV 的字段說明,手動編寫 PV 的 YAML 描述文件。
apiVersion: v1
kind: PersistentVolume
metadata:
name: host-10m-pv
spec:
storageClassName: host-manual
accessModes:
- ReadWriteOnce
capacity:
storage: 10Mi
hostPath:
path: /tmp/host-10mib-pv/
storageClassName
是對存儲類型的抽象 StorageClass 的名字。這個 PV 是手動管理的,名字可以任意起。
accessModes
定義了存儲設備的訪問模式。簡單來說就是虛擬盤的讀寫權限,和 Linux 的文件訪問模式差不多,目前 Kubernetes 里有 4 種:
-
ReadWriteOnce
:存儲卷可讀可寫,但只能被一個節點上的 Pod 掛載 -
ReadOnlyMany
:存儲卷只讀不可寫,可以被任意節點上的 Pod 多次掛載 -
ReadWriteMany
:存儲卷可讀可寫,也可以被任意節點上的 Pod 多次掛載 -
ReadWriteOncePod
:卷可以被單個 Pod 以讀寫方式掛載。 如果你想確保整個集群中只有一個 Pod 可以讀取或寫入該 PVC, 請使用 ReadWriteOncePod 訪問模式。這只支持 CSI 卷以及需要 Kubernetes 1.22 以上版本
前三種 3 種訪問模式限制的對象是節點而不是 Pod,因為存儲是系統級別的概念,不屬于 Pod 里的進程。顯然,本地目錄只能是在本機使用,所以這個 PV 使用了 ReadWriteOnce。
在命令行接口(CLI)中,訪問模式也使用以下縮寫形式:
RWO - ReadWriteOnce
ROX - ReadOnlyMany
RWX - ReadWriteMany
RWOP - ReadWriteOncePod
capacity
表示存儲設備的容量,這里我設置為 10MB。Kubernetes 里定義存儲容量使用的是國際標準,要寫成 Ki/Mi/Gi。
hostPath
指定了存儲卷的本地路徑,也就是我們在節點上創建的目錄。
用這些字段把 PV 的類型、訪問模式、容量、存儲位置都描述清楚,一個存儲設備就創建好了。
3. 使用 YAML 描述 PersistentVolumeClaim
有了 PV,就表示集群里有了這么一個持久化存儲可以供 Pod 使用,我們需要再定義 PVC 對象,向 Kubernetes 申請存儲。
下面這份 YAML 就是一個 PVC,要求使用一個 5MB 的存儲設備,訪問模式是 ReadWriteOnce
:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: host-5mib-pvc
spec:
storageClassName: host-manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Mi
PVC 的內容與 PV 很像,但它不表示實際的存儲,而是一個“申請”或者“聲明”,spec
里的字段描述的是對存儲的“期望狀態”。所以 PVC 里的 storageClassName
accessModes
和 PV 是一樣的,但不會有字段 capacity
,而是要用 resources.requests.storage
表示希望要有多大的容量。
這樣,Kubernetes 就會根據 PVC 里的描述,去找能夠匹配 StorageClass 和容量的 PV,然后把 PV 和 PVC“綁定”在一起,實現存儲的分配。
4. 在 Kubernetes 中使用 PersistentVolume
創建 PV 對象,然后查看它的狀態
$ kubectl apply -f host-path-pv.yaml
persistentvolume/host-10m-pv created
$ kubectl get pv -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
host-10m-pv 10Mi RWO Retain Available host-manual 8s Filesystem
可以看到,這個 PV 的容量是 10 MiB,訪問模式是 RWO(ReadWriteOnce),StorageClass 是自定義的 host-manual
,狀態是可用(Available),可以隨時分配給 Pod 使用。
創建PVC申請存儲資源
$ kubectl apply -f host-path-pvc.yaml
persistentvolumeclaim/host-5mib-pvc created
$ kubectl get pvc -o wide
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
host-5mib-pvc Bound host-10m-pv 10Mi RWO host-manual 5s Filesystem
$ kubectl get pv -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
host-10m-pv 10Mi RWO Retain Bound default/host-5mib-pvc host-manual 14m Filesystem
一旦 PVC 對象創建成功,Kubernetes 就會立即通過 StorageClass
resources
等條件在集群里查找符合要求的 PV,如果找到合適的存儲對象就會把它倆“綁定”(bind)在一起,綁定后的狀態就是 Bound。
PVC 對象申請的是 5MB,但現在系統里只有一個 10MB 的 PV,沒有更合適的對象,所以 Kubernetes 也只能把這個 PV 分配出去。
如果把 PVC 的申請容量改大一些,超過現有可用 PV 的容量,PVC 就會一直處于 Pending 狀態,這意味著 Kubernetes 沒有在系統中找到合適的存儲,無法分配資源,只有等滿足要求的 PV 出現才能完成綁定。
5. 為 Pod 掛載 PersistentVolume
有了持久化存儲就可以為 Pod 掛載存儲卷。先要在 spec.volumes
定義存儲卷,然后在 containers.volumeMounts
掛載進容器。因為我們用的是 PVC,所以要在 volumes 里用字段 persistentVolumeClaim
指定 PVC 的名字。
下面就是 Pod 的 YAML 描述文件,把存儲卷掛載到了 Nginx 容器的 /tmp
目錄:
apiVersion: v1
kind: Pod
metadata:
name: host-pvc-pod
spec:
volumes:
- name: host-pvc-vol
persistentVolumeClaim:
claimName: host-5mib-pvc
containers:
- name: ngx-pvc-pod
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: host-pvc-vol
mountPath: /tmp
現在啟動這個 Pod:
$ kubectl apply -f ngx-pvc-pod.yaml
pod/host-pvc-pod created
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
host-pvc-pod 1/1 Running 0 21s 10.10.1.70 worker1 <none> <none>
我們在 Pod 中創建一個文件,然后取宿主機上看看文件是否被創建:
$ kubectl exec -it host-pvc-pod -- sh
/ # cd /tmp/
/tmp # echo yeah! > file.text
登錄宿主機:
root@worker1:~# ls /tmp/host-10mib-pv/
file.text
root@worker1:~# cat /tmp/host-10mib-pv/file.text
yeah!
確實在 worker 節點的本地目錄有一個 file.text
文件。因為 Pod 產生的數據已經通過 PV 存在了磁盤上,所以如果 Pod 刪除后再重新創建,掛載存儲卷時會依然使用這個目錄,數據保持不變,也就實現了持久化存儲。不過還有一點小問題,因為這個 PV 是 HostPath 類型,只在本節點存儲,如果 Pod 重建時被調度到了其他節點上,那么即使加載了本地目錄,也不會是之前的存儲位置,持久化功能也就失效了。
HostPath 類型的 PV 一般用來做測試,或者是用于 DaemonSet 這樣與節點關系比較密切的應用。下篇筆記就要學習網絡共享存儲了。