Kubernetes容器要持久化數據,離不開volume,k8s的volume和Docker原生概念中的volume有一些差別,不過本次不講這個,本次要明確的是k8s持久化數據用到的幾個對象PersistentVolume、PersistentVolumeClaim和StorageClass,首先明確這既然都是k8s對象,就可以通過API來創建的。
k8s的volume支持的類型有很多,例如emptyDir、hostPath、nfs等,這些相對好理解,還有一種就是ersistentVolumeClaim,剛開始接觸的時候不太理解這個對象該如何使用,本次主要介紹ersistentVolumeClaim相關的概念和使用方法。
開始先要提一下PersistentVolume(PV)對象,PersistentVolume和Volume一樣是群集中的一塊存儲區域,然而Kubernetes將PersistentVolume抽象成了一種集群資源,類似於集群中的節點(Node)對象,這意味着我們可以使用Kubernetes API來創建PersistentVolume對象。PV與Volume最大的不同是PV擁有着獨立於Pod的生命周期。
而PersistentVolumeClaim(PVC)代表了用戶對PV資源的請求。用戶需要使用PV資源時,只需要創建一個PVC對象(包括指定使用何種存儲資源,使用多少GB,以何種模式使用PV等信息),Kubernetes會自動為我們分配我們所需的PV。如果把PersistentVolume類比成集群中的Node,那么PersistentVolumeClaim就相當於集群中的Pod,Kubernetes為Pod分配可用的Node,類似的也可以理解成為PersistentVolumeClaim分配可用的PersistentVolume。
1、靜態創建PV對象
可以直接靜態創建一個PV對象,作為一個存儲供PVC使用,創建PV主要有下面幾個參數
accessModes 訪問模式有下面三種:
- ReadWriteOnce(RWO):是最基本的方式,可讀可寫,但只支持被單個 Pod 掛載。
- ReadOnlyMany(ROX):只讀模式,可以被多個 Pod 掛載。
- ReadWriteMany(RWX):可讀可寫,並且可以被被多個 Pod 掛載。
- Retain,不清理,刪除PVC時,PV仍然存在並標記為“released”(需要刪除時需要手動清理)
- Recycle,刪除數據,對卷執行清理(rm -rf / thevolume / *),並使其再次可用於新索引(只有 NFS 和 HostPath 支持)
- Delete,刪除存儲資源,會從Kubernetes中刪除PV對象,以及外部基礎結構中的關聯存儲資產,例如AWS EBS,GCE PD,Azure磁盤或Cinder卷
#靜態創建PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /tmp
server: 172.17.0.2
定義PV時,我們需要指定其底層存儲的類型,例如上文中創建的PV,底層使用nfs存儲,支持的類型很多,例如awsElasticBlockStore、FC、nfs、RBD、CephFS、Hostpath、StorageOS等等,可以查看官方文檔。
可以查看當前集群下創建的PV對象,kubectl get PersistentVolume --all-namespaces
2、靜態創建PVC對象
創建PV之后,並沒有被使用,如果想使用這個PV就需要創建PVC了,最后在pod中指定使用這個PVC而建立起pod和PV的關系。
#靜態創建PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 8Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
#pod使用PVC
kind: Pod
apiVersion: v1
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: dockerfile/nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
分析一下上面的代碼:
創建PVC時指定了accessModes
= ReadWriteOnce
。這表明這個PVC希望使用accessModes = ReadWriteOnce
的PV。創建PVC時指定了volumeMode
= Filesystem。這表明這個PVC希望使用
volumeMode= Filesystem的PV。
- 創建PVC時指定了
storageClassName: slow
,此配置用於綁定PVC和PV,意思是這個PVC希望使用storageClassName=slow
的PV。我們可以看到最上面創建PV時也包含storageClassName=slow
的配置。 - PVC還可以指定PV必須滿足的Label,如加了selector匹配
matchLabels: release: "stable"
。這表明此PVC希望使用Label:release: "stable"
的PV。 - 最后是resources聲明,跟pod一樣可以聲明使用特定數量的資源,
storage: 8Gi
表明此PVC希望使用8G的Volume資源。
通過上面的分析,我們可以看到PVC和PV的綁定,不是簡單的通過Label來進行。而是要綜合storageClassName,accessModes,matchLabels以及storage來匹配符合條件的PV進行綁定。
3、動態創建PV對象
上面我們通過描述文件靜態創建PV對象最終完成和pod的綁定,這種直接通過描述文件創建PV的方式稱為靜態創建,這樣的創建方式有弊端,假如我們創建PV時指定大小為50G,而PVC請求80G的PV,那么此PVC就無法找到合適的PV來綁定。因此實際生產中更多的使用PV的動態創建。
PV的動態創建依賴於StorageClass對象。我們不需要手動創建任何PV,所有的工作都由StorageClass為我們完成,可以查看集群中的StorageClass信息 ,kubectl get StorageClass --all-namespaces
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://192.168.10.100:8080"
restuser: ""
secretNamespace: ""
secretName: ""
reclaimPolicy: Retain
allowVolumeExpansion: true
這個例子使用創建了一個基於glusterfs分布式存儲的StorageClass,只有allowVolumeExpansion=teue時,才能擴展PVC,要為PVC請求更大的卷,請編輯PVC對象並指定更大的大小,這會觸發底層PersistentVolume的卷的擴展。永遠不會創建新的,而是調整現有卷的大小。
StorageClass的定義包含四個部分:
- provisioner:指定 Volume 插件的類型,包括內置插件(如kubernetes.io/glusterfs、kubernetes.io/aws-ebs)和外部插件(如 external-storage 提供的 ceph.com/cephfs)。
- parameters:指定 provisioner 的選項,比如 glusterfs 支持 resturl、restuser 等參數。
- mountOptions:指定掛載選項,當 PV 不支持指定的選項時會直接失敗。比如 NFS 支持 hard 和 nfsvers=4.1 等選項。
- reclaimPolicy:指定回收策略,同 PV 的回收策略。
手動創建的PV時,我們指定了storageClassName=slow的配置項,然后Pod定義中也通過指定storageClassName=slow,從而完成綁定。而通過StorageClass實現動態PV時,我們只需要指定StorageClass的metadata.name即可,這個名稱非常重要,用戶通過名稱類請求特定的存儲類,儲類的對象一旦被創建,name將不能再更改。
回到上文中創建PVC的例子,此時PVC指定了storageClassName=slow。那么Kubernetes會在集群中尋找是否存在metadata.name=slow的StorageClass,如果存在,此StorageClass會自動為此PVC創建一個accessModes = ReadWriteOnce,並且大小為8GB的PV。
或者直接寫到一起
#上面的部分省略了
volumeMounts:
- name: data-dir
mountPath: /var/mysql/data
#volumes:
#- name: data-dir
# hostPath:
# path: /opt/mysql
volumeClaimTemplates:
- metadata:
#annotations: volume.alpha.kubernetes.io/storage-class: xxxx
creationTimestamp: null
name: data-dir
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: slow
volumeMode: Filesystem
status:
phase: Pending
下面是參考官方的一種比較通用的寫法,基於helm的,直接將PVC的代碼寫到volumes的后面,根據values.yaml中的參數生成是掛載PVC的存儲還是其他存儲,可以作為以后自己寫腳本的參考
volumes:
- name: config
configMap:
name: {{ template "redis-ha.fullname" . }}-configmap
- name: probes
configMap:
name: {{ template "redis-ha.fullname" . }}-probes
{{- if .Values.sysctlImage.mountHostSys }}
- name: host-sys
hostPath:
path: /sys
{{- end }}
{{- if .Values.persistentVolume.enabled }}
volumeClaimTemplates:
- metadata:
name: data
annotations:
{{- range $key, $value := .Values.persistentVolume.annotations }}
{{ $key }}: {{ $value }}
{{- end }}
spec:
accessModes:
{{- range .Values.persistentVolume.accessModes }}
- {{ . | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistentVolume.size | quote }}
{{- if .Values.persistentVolume.storageClass }}
{{- if (eq "-" .Values.persistentVolume.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.persistentVolume.storageClass }}"
{{- end }}
{{- end }}
{{- else if .Values.hostPath.path }}
- name: data
hostPath:
path: {{ tpl .Values.hostPath.path .}}
{{- else }}
- name: data
emptyDir: {}
{{- end }}
總結一下整個過程
1)集群管理員預先創建存儲類(StorageClass);
2)用戶創建使用存儲類的持久化存儲聲明(PVC:PersistentVolumeClaim);
3)存儲持久化聲明通知系統,它需要一個持久化存儲(PV: PersistentVolume);
4)系統讀取存儲類的信息;
5)系統基於存儲類的信息,在后台自動創建PVC需要的PV;
6)用戶創建一個使用PVC的Pod;
7)Pod中的應用通過PVC進行數據的持久化;
8)而PVC使用PV進行數據的最終持久化處理。
官方文檔 https://kubernetes.io/docs/concepts/storage/persistent-volumes/