當遇上了local pv 與local StorageClass

Posted by Kubeguts on 2023-08-02

這篇主要說明什麼時間點會需要使用local persistent volume並搭配local storageclass的文章紀錄

假如對kubernetes針對資料保存的機制 (Persistent Volume, Persistent Volume Mount, 以及StorageClass) 尚未接觸的讀者們,可以先參考我之前的筆記
Kubernetes - Storage 學習紀錄

先談談 StorageClass volumeBindingMode的兩種模式 WaitForFirstConsumer & Immediate

我想大部分的人都是在學習k8的階段,了解到怎麼建立一個persistent volume,然後掛上本地的目錄並提供給PersistentVolumeClaim (PVC) 使用。

除了掛載給PVC之外,K8s admin也可以預先開好數個persistent volume,然後提供給配置provisionerWaitForFirstConsumer的StorageClass使用

1
2
3
4
5
6
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

然後就是這裡,直接給他套用上去

volumeBindingMode: WaitForFirstConsumer 是什麼用法???

WaitForFirstConsumer的consumer,指的是Pod!

直到有Pod被安排到特定的K8s node上,確定好位置後,StorageClass才會將PV分配給Pod所綁定的PVC囉

那另外一種Mode為 Immediate 也是Default的行為,就是不用等到Pod被Scheduler調度完成,即把PVC與PV給綁定。

為何要使用StorageClass WaitForFirstConsumer啊???

直接先開門見山回答問題,如果你有Pod有存取特定節點上的特殊目錄的需求,那就得使用WaitForFirstConsumer啊???

因為當你的Pod必須要先跑在指定的Worker Node的位置,也就是等Pod被調度完成後,StorageClass才能夠分配Persistent Volume給Pod。

見了山,再見林,配個使用情境快速了解:

當你有一座Elasticsearch集群時,你看了他們的官方說明,被建議使用Local PersistentVolumes來達到讀寫都是在本地端達成,而不須經過頻寬傳輸 (例如使用Network Storage (NFS))而多產生傳輸延遲

於是乎,額外產了幾台worker node專門給Elasticsearch使用,這時StorageClass的volumeBindingMode就得使用 WaitForFirstConsumer,先讓Pod跑在Elasticsearch專屬的位置,然後才分配PV給他,Elastic官方也是建議使用這種行為。

因為當使用了Local PV,其使用的空間為Worker node上面的filesystem.所以必須得等到Pod順順利利的放到指定的Worker node上面,才能夠把PV給Pod這樣。

這邊補充一下,PV也可以限制被哪些Node上的Pod存取,可透過nodeAffinity達成

使用local persistent volume潛在問題與解方

當只有使用Local Persistent Volumes與Local StorageClass
必須要事先將Local Persistent Volumes建立起來,並且註冊給StorageClass

但當Pod被刪除,PVC也跟著被移除後,那對應的Persistent Volume會如何??

答案是:Persistent Volume只會認得之前被移除的PVC!!!除非要對他做一些更改否則

透過以下範例來說明:

創建 volumeBindingMode: WaitForFirstConsumer的StorageClass local-storage

1
2
3
4
5
6
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
1
2
$ kubectl create -f sc.yaml 
storageclass.storage.k8s.io/local-storage created

創建PV,並指定給StorageClass local-storage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-1
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/disks/local-pv-1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node01
1
2
3
4
5
6
$ kubectl create -f pv.yaml
persistentvolume/local-pv-1 created

$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-1 10Gi RWO Retain Available local-storage 3s

創建PVC,向StorageClass要求PV分配

1
2
3
4
5
6
7
8
9
10
11
12
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: local-pvc-1
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 10Gi
storageClassName: local-storage

這時可以看到,PVC pending中,尚未被分配,因為的StorageClass設置WaitForFirstConsumer,故要等待Pod被kube-scheduler分配到worker nodes上,SC才會將PV與PVC做綁定

1
2
3
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-pvc-1 Pending local-storage 2s

開一個pod掛載

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kind: Pod
apiVersion: v1
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
# 指定掛載的目錄
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
# 掛載 PVC "myclaim"
persistentVolumeClaim:
claimName: local-pvc-1

記得使用local persistent volume時,Pod所在的worker node,mount的掛載目錄要確保存在,否則Pod會卡在ContainerCreating,並冒出 MountVolume.NewMounter initialization failed for volume "local-pv-1" : path "/mnt/disks/local-pv-1" does not exist Event log

1
2
3
4
5
6
7
8
9
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mypod 0/1 ContainerCreating 0 58s

Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 68s default-scheduler Successfully assigned default/mypod to node01
Warning FailedMount 4s (x8 over 68s) kubelet MountVolume.NewMounter initialization failed for volume "local-pv-1" : path "/mnt/disks/local-pv-1" does not exist
1
2
3
$ kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mypod 1/1 Running 0 9s 192.168.1.4 node01 <none> <none>

這時查看PVC狀態,已經Bound成功

1
2
3
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-pvc-1 Bound local-pv-1 10Gi RWO local-storage 10m

PV也已經被StorageClass local-storage分配給PVC

1
2
3
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-1 10Gi RWO Retain Bound default/local-pvc-1 local-storage 12m

砍掉Pod與PVC,那PV會變成怎樣????

先觀察一下PVC內容,會有個uid 資訊

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
pv.kubernetes.io/bind-completed: "yes"
pv.kubernetes.io/bound-by-controller: "yes"
creationTimestamp: "2023-08-02T15:25:50Z"
finalizers:
- kubernetes.io/pvc-protection
name: local-pvc-1
namespace: default
resourceVersion: "2723"
uid: b5f70249-4abc-4b11-85ce-f5ad0b6ba575

然後我們再把PVC給砍了,並查看一下PV的狀態為Release

1
2
3
4
5
$ kubectl delete -f pvc.yaml
persistentvolumeclaim "local-pvc-1" deleted
controlplane $ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-1 10Gi RWO Retain Released default/local-pvc-1 local-storage 15m

那我們想要再重新綁定PV給重新創建的PVC,會發生什麼事呢??
會發現怎沒辦法重新綁定,Pod呈現Pending狀態

1
2
3
4
5
6
7
8
9
10
$ kubectl create -f pvc.yaml
persistentvolumeclaim/local-pvc-1 created
controlplane $ kubectl create -f pod.yaml
pod/mypod created
controlplane $ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-pvc-1 Pending local-storage 14s
controlplane $ kubectl get pods
NAME READY STATUS RESTARTS AGE
mypod 0/1 Pending 0 10s

查看Pod的Event log會看到 1 node(s) didn't find available persistent volumes to bind ,原因是因為原本的Persistent Volume因為是使用local volume mount的手動創建的機制,他所綁定的PVC還停留在上一個刪除pvc name,可以透過uid辨識出是上一個刪除的pvc…

請用 kubectl edit pv 查看,才能看到pv綁定的pvc uid資訊在 claimRef之下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ kubectl edit pv 

apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/bound-by-controller: "yes"
creationTimestamp: "2023-08-02T15:24:40Z"
finalizers:
- kubernetes.io/pv-protection
name: local-pv-1
....
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 10Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: local-pvc-1
namespace: default
resourceVersion: "2628"
uid: b5f70249-4abc-4b11-85ce-f5ad0b6ba575

這時需要清除PV的claimRef內容,才能夠讓Pod的PVC重新綁定到PV

1
kubectl patch pv local-pv-1 -p '{"spec":{"claimRef": null}}'

以上的動作都可以至 KillerKoda提供免費的playground操作囉

其實可以看到若使用local persistent volume會產生不少operation的活,這時K8 admin就會很想哭,所以就可以透過 Local Persistence Volume Static Provisioner 來自動化產生PV所需的mount目錄,以及管理PV與PVC綁定的關係和PV的生命週期

小結

  1. 什麼情況使用 WaitForFirstConsumer

A: Pod跑在指定的Node上,且存取指定Node上特定的目錄空間 (由local PV掛載而存取到)

  1. Local PV為何沒辦法重新綁定給重新產生的PVC

A: 請將PV的ClaimRef所聲明的PVC對象資訊給清除掉

  1. 要如何改善使用Local Persistent Volume??

A: 可以考慮使用 Local Persistence Volume Static Provisioner

Reference

StorageClass volumeBindingMode
https://kubernetes.io/docs/concepts/storage/storage-classes/#local

Local Persistence Volume Static Provisioner
https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner

文章版權聲明:請勿抄襲,本部落格採用創用CC, 姓名標示-非商業性-相同方式分享授權
欲轉載請標註出處與作者-Kubeguts 庫柏格,thanks!