目錄
- local volume
- 創建一個storage class
- 靜態創建PV
- 使用local volume PV
- 動態創建PV
local volume
kubernetes從1.10版本開始支持local volume(本地卷),workload(不僅是statefulsets類型)可以充分利用本地快速SSD,從而獲取比remote volume(如cephfs、RBD)更好的性能。
在local volume出現之前,statefulsets也可以利用本地SSD,方法是配置hostPath,並通過nodeSelector或者nodeAffinity綁定到具體node上。但hostPath的問題是,管理員需要手動管理集群各個node的目錄,不太方便。
下面兩種類型應用適合使用local volume。
- 數據緩存,應用可以就近訪問數據,快速處理。
- 分布式存儲系統,如分布式數據庫Cassandra ,分布式文件系統ceph/gluster
下面會先以手動方式創建PV、PVC、Pod的方式,介紹如何使用local volume,然后再介紹external storage提供的半自動方式,最后介紹社區的一些發展。
創建一個storage class
首先需要有一個名為local-volume
的sc。
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: local-volume provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer
sc的provisioner是 kubernetes.io/no-provisioner
。
WaitForFirstConsumer
表示PV不要立即綁定PVC,而是直到有Pod需要用PVC的時候才綁定。調度器會在調度時綜合考慮選擇合適的local PV,這樣就不會導致跟Pod資源設置,selectors,affinity and anti-affinity策略等產生沖突。很明顯:如果PVC先跟local PV綁定了,由於local PV是跟node綁定的,這樣selectors,affinity等等就基本沒用了,所以更好的做法是先根據調度策略選擇node,然后再綁定local PV。
靜態創建PV
通過kubectl命令,靜態創建一個5GiB的PV;該PV使用node ubuntu-1的 /data/local/vol1
目錄;該PV的sc為local-volume。
apiVersion: v1 kind: PersistentVolume metadata: name: example-local-pv spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-volume local: path: /data/local/vol1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - ubuntu-1
Retain(保留)是指,PV跟PVC釋放后,管理員需要手工清理,重新設置該卷。
需要指定PV對應的sc;目錄/data/local/vol1
也需要創建。
kubectl get pv example-local-pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
example-local-pv 5Gi RWO Retain Available local-volume 8d
使用local volume PV
接下來創建一個關聯 sc:local-volume的PVC,然后將該PVC掛到nginx容器里。
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: local-volume --- kind: Pod apiVersion: v1 metadata: name: mypod spec: containers: - name: myfrontend image: nginx volumeMounts: - mountPath: "/usr/share/nginx/html" name: mypd volumes: - name: mypd persistentVolumeClaim: claimName: myclaim
進入到容器里,會看到掛載的目錄,大小其實就是上面創建的PV所在磁盤的size。
/dev/sdb 503G 235M 478G 1% /usr/share/nginx/html
在宿主機的/data/local/vol1
目錄下創建一個index.html
文件:
echo "hello world" > /data/local/vol1/index.html
然后再去curl容器的IP地址,就可以得到剛寫入的字符串了。
刪除Pod/PVC,之后PV狀態改為Released,該PV不會再被綁定PVC了。
動態創建PV
手工管理local PV顯然是很費勁的,社區提供了external storage可以動態的創建PV(實際仍然不夠自動化)。
local volume provisioner的官方編排在local-volume/provisioner/deployment/kubernetes/example/default_example_provisioner_generated.yaml
目錄里,不過官方文檔一會fast-disk,一會local-storage,有點混亂。我這里統一都用local-volume
。
--- apiVersion: v1 kind: ConfigMap metadata: name: local-provisioner-config namespace: default data: storageClassMap: | local-volume: hostDir: /data/local mountDir: /data/local blockCleanerCommand: - "/scripts/shred.sh" - "2" volumeMode: Filesystem fsType: ext4 --- apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: local-volume-provisioner namespace: default labels: app: local-volume-provisioner spec: selector: matchLabels: app: local-volume-provisioner template: metadata: labels: app: local-volume-provisioner spec: serviceAccountName: local-volume-admin containers: - image: "silenceshell/local-volume-provisioner:v2.1.0" imagePullPolicy: "Always" name: provisioner securityContext: privileged: true env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName volumeMounts: - mountPath: /etc/provisioner/config name: provisioner-config readOnly: true - mountPath: /data/local name: local mountPropagation: "HostToContainer" volumes: - name: provisioner-config configMap: name: local-provisioner-config - name: local hostPath: path: /data/local --- apiVersion: v1 kind: ServiceAccount metadata: name: local-volume-admin namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: local-volume-provisioner-pv-binding namespace: default subjects: - kind: ServiceAccount name: local-volume-admin namespace: default roleRef: kind: ClusterRole name: system:persistent-volume-provisioner apiGroup: rbac.authorization.k8s.io --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: local-volume-provisioner-node-clusterrole namespace: default rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: local-volume-provisioner-node-binding namespace: default subjects: - kind: ServiceAccount name: local-volume-admin namespace: default roleRef: kind: ClusterRole name: local-volume-provisioner-node-clusterrole apiGroup: rbac.authorization.k8s.io
kubectl創建后,由於是daemonset類型,每個節點上都會啟動一個provisioner。該provisioner會監視 “discovery directory”,即上面配置的/data/local
。
$ kubectl get pods -o wide|grep local-volume local-volume-provisioner-rrsjp 1/1 Running 0 5m 10.244.1.141 ubuntu-2 <none> local-volume-provisioner-v87b7 1/1 Running 0 5m 10.244.2.69 ubuntu-3 <none> local-volume-provisioner-x65k9 1/1 Running 0 5m 10.244.0.174 ubuntu-1 <none>
前面mypod/myclaim
已經刪除了,我們重新創建一個,此時pvc myclaim是Pending狀態,provisoner並沒有自動供給存儲。為什么呢?
原來external-storage
的邏輯是這樣的:其Provisioner本身其並不提供local volume,但它在各個節點上的provisioner會去動態的“發現”掛載點(discovery directory),當某node的provisioner在/data/local/
目錄下發現有掛載點時,會創建PV,該PV的local.path
就是掛載點,並設置nodeAffinity為該node。
那么如何獲得掛載點呢?
直接去創建目錄是行不通的,因為provsioner希望PV是隔離的,例如capacity,io等。試着在ubuntu-2上的/data/local/
下創建一個xxx
目錄,會得到這樣的告警。
discovery.go:201] Path "/data/local/xxx" is not an actual mountpoint
目錄不是掛載點,不能用。
該目錄必須是真材實料的mount才行。一個辦法是加硬盤、格式化、mount,比較麻煩,實際可以通過本地文件格式化(loopfs)后掛載來“欺騙”provisioner,讓它以為是一個mount的盤,從而自動創建PV,並與PVC綁定。
如下。
將下面的代碼保存為文件 loopmount
,加執行權限並拷貝到/bin
目錄下,就可以使用該命令來創建掛載點了。
#!/bin/bash # Usage: sudo loopmount file size mount-point touch $1 truncate -s $2 $1 mke2fs -t ext4 -F $1 1> /dev/null 2> /dev/null if [[ ! -d $3 ]]; then echo $3 " not exist, creating..." mkdir $3 fi mount $1 $3 df -h |grep $3
使用腳本創建一個6G的文件,並掛載到/data/local
下。之所以要6G,是因為前面PVC需要的是5GB,而格式化后剩余空間會小一點,所以設置文件更大一些,后面才好綁定PVC。
# loopmount xxx 6G /data/local/xxx /data/local/xxx not exist, creating... /dev/loop0 5.9G 24M 5.6G 1% /data/local/x1
查看PV,可見Provisioner自動創建了PV,而kubernetes會將該PV供給給前面的PVC myclam,mypod也run起來了。
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-600377f7 5983Mi RWO Delete Bound default/myclaim local-volume 1s
可見,目前版本的local volume還無法做到像cephfs/RBD一樣的全自動化,仍然需要管理員干涉,顯然這不是一個好的實現。
社區有人提交了基於LVM做local volume動態供給的Proposal,不過進展很緩慢。作者是huawei的員工,應該huawei已經實現了。
除了基於LVM,也可以基於
ext4 project quota
來實現LV的動態供給。
除了使用磁盤,還可以考慮使用內存文件系統,從而獲取更高的io性能,只是容量就沒那么理想了。一些特殊的應用可以考慮。
mount -t tmpfs -o size=1G,nr_inodes=10k,mode=700 tmpfs /data/local/tmpfs
總的來說,local volume本地卷目前不支持動態供給,還無法真正推廣使用,但可以用來解決一些特定問題。
Ref:
參考文檔:
https://kubernetes.io/blog/2019/04/04/kubernetes-1.14-local-persistent-volumes-ga/
https://ieevee.com/tech/2019/01/17/local-volume.html