在kubernetes中Pod的生命周期是很短暫的,會(huì)被頻繁地銷毀和創(chuàng)建。容器銷毀時(shí),保存在容器內(nèi)部文件系統(tǒng)中的數(shù)據(jù)都會(huì)被清除。為了持久化保存容器的數(shù)據(jù),需要使用 Kubernetes Volume數(shù)據(jù)卷,下面文章分別以本地卷,網(wǎng)絡(luò)數(shù)據(jù)卷的方式來(lái)實(shí)踐集群的持久化存儲(chǔ)。
一、hostPath/emptyDir
hostPath:用于將目錄從宿主機(jī)節(jié)點(diǎn)的文件系統(tǒng)掛載到pod中,屬于單節(jié)點(diǎn)集群中的持久化存儲(chǔ),刪除pod后,卷里面的文件會(huì)繼續(xù)保持,在同一節(jié)點(diǎn)運(yùn)行的Pod可以繼續(xù)使用數(shù)據(jù)卷中的文件,但pod被重新調(diào)度到其他節(jié)點(diǎn)時(shí),就無(wú)法訪問(wèn)到原數(shù)據(jù)。不適合作為存儲(chǔ)數(shù)據(jù)庫(kù)數(shù)據(jù)的目錄。
emptyDir: 用于存儲(chǔ)臨時(shí)數(shù)據(jù)的簡(jiǎn)單空目錄,生命周期是和pod捆綁的,隨著pod創(chuàng)建而創(chuàng)建;刪除而銷毀,卷的內(nèi)容將會(huì)丟失。emptyDir卷適用于同一個(gè)pod中運(yùn)行的容器之間共享文件。
hostPath定義如下:
....
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
hostPath:
path: /opt/data
type: Directory
emptyDir定義如下:
....
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
二、PV/PVC
前面使用的 hostPath 和 emptyDir 類型的 Volume 并不具備持久化特征,它們既有可能被 kubelet 清理掉,也不能被“遷移”到其他節(jié)點(diǎn)上。所以,大多數(shù)情況下,持久化 Volume 的實(shí)現(xiàn),往往依賴于一個(gè)遠(yuǎn)程存儲(chǔ)服務(wù),比如:遠(yuǎn)程文件存儲(chǔ)(比如,NFS、GlusterFS)、遠(yuǎn)程塊存儲(chǔ)(比如,公有云提供的遠(yuǎn)程磁盤)等等。而 Kubernetes 需要做的工作,就是使用這些存儲(chǔ)服務(wù),來(lái)為容器準(zhǔn)備一個(gè)持久化的宿主機(jī)目錄,以供將來(lái)進(jìn)行綁定掛載時(shí)使用。而所謂“持久化”,指的是容器在這個(gè)目錄里寫入的文件,都會(huì)保存在遠(yuǎn)程存儲(chǔ)中,從而使得這個(gè)目錄具備了“持久性”。為了屏蔽底層的技術(shù)實(shí)現(xiàn)細(xì)節(jié),讓用戶更加方便的使用,Kubernetes 便引入了 PV 和 PVC 兩個(gè)重要的資源對(duì)象來(lái)實(shí)現(xiàn)對(duì)存儲(chǔ)的管理。
PV:持久化存儲(chǔ)數(shù)據(jù)卷,全稱為PersistentVolume,PV其實(shí)是對(duì)底層存儲(chǔ)的一種抽象,通常是由集群的管理員進(jìn)行創(chuàng)建和配置 ,底層存儲(chǔ)可以是Ceph,GlusterFS,NFS,hostpath等,都是通過(guò)插件機(jī)制完成與共享存儲(chǔ)的對(duì)接。
PVC: 持久化數(shù)據(jù)卷聲明,全稱為PersistentVolumeClaim,PVC 對(duì)象通常由開(kāi)發(fā)人員創(chuàng)建,描述 Pod 所希望使用的持久化存儲(chǔ)的屬性。比如,Volume 存儲(chǔ)的大小、可讀寫權(quán)限等等。PVC綁定PV,消耗的PV資源。
下面創(chuàng)建一個(gè)NFS類型的PV,首先節(jié)點(diǎn)部署NFS,共享/data/k8s目錄
$ systemctl stop firewalld.service
$ yum -y install nfs-utils rpcbind
$ mkdir -p /data/k8s
$ chmod 755 /data/k8s
$ vim /etc/exports
/data/k8s *(rw,sync,no_root_squash)
$ systemctl start rpcbind.service
$ systemctl start nfs.service
$ mount 192.168.16.173:/data/k8s /data/k8s
創(chuàng)建nfspv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.16.173
path: "/data/k8s"
文件的內(nèi)容定義如下:
- spec.storageClassName:定義了名稱為 manual 的 StorageClass,該名稱用來(lái)將 PVC請(qǐng)求綁定到該 PV。
- spec.Capacity:定義了當(dāng)前PV的存儲(chǔ)空間為storage=10G。
- spec.nfs: 定義了PV的類型為NFS,并指定了該卷位于節(jié)點(diǎn)上的 /data/k8s/目錄。
- spec.accessModes:定義了當(dāng)前的訪問(wèn)模式,可定義的模式如下:
- ReadWriteMany(RWX):讀寫權(quán)限,可以被多個(gè)節(jié)點(diǎn)掛載。
- ReadWriteOnce(RWO):讀寫權(quán)限,但是只能被單個(gè)節(jié)點(diǎn)掛載;
- ReadWriteMany(ROX):只讀權(quán)限,可以被多個(gè)節(jié)點(diǎn)掛載。
下圖是一些常用的 Volume 插件支持的訪問(wèn)模式:
創(chuàng)建資源對(duì)象:
$ kubectl create -f nfspv.yaml
persistentvolume/nfs created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs 5Gi RWX Retain Available manual 4s
可以看到創(chuàng)建后的PV的Status為Available 可用狀態(tài),意味著當(dāng)前PV還沒(méi)有被PVC綁定使用。
PV生命周期的不同狀態(tài)如下:
- Available(可用):表示可用狀態(tài),還未被任何 PVC 綁定
- Bound(已綁定):表示 PVC 已經(jīng)被 PVC 綁定
- Released(已釋放):PVC 被刪除,但是資源還未被集群重新聲明
- Failed(失敗): 表示該 PV 的自動(dòng)回收失敗
另外還有一個(gè)RECLAIM POLICY字段輸出內(nèi)容為Retain,這個(gè)字段輸出的其實(shí)是PV的回收策略,目前 PV 支持的策略有三種:
- Retain(保留):保留數(shù)據(jù),需要管理員手工清理數(shù)據(jù)
- Recycle(回收):清除 PV 中的數(shù)據(jù),效果相當(dāng)于執(zhí)行 rm -rf /thevoluem/*
- Delete(刪除):與 PV 相連的后端存儲(chǔ)完成 volume 的刪除操作,當(dāng)然這常見(jiàn)于云服務(wù)商的存儲(chǔ)服務(wù),比如 ASW EBS。
注意:目前只有 NFS 和 HostPath 兩種類型支持回收策略。
現(xiàn)在我們創(chuàng)建一個(gè)PVC來(lái)綁定上面的PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteMany
resources:
requests:
storage: 3Gi
創(chuàng)建PVC
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs 5Gi RWX manual 3m59s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs 5Gi RWX Retain Bound default/nfs-pvc manual 20m
可以看到PV和PVC的狀態(tài)已經(jīng)為Bound綁定狀態(tài),其中綁定需要檢查的條件,包括兩部分:
- 第一個(gè)條件,當(dāng)然是 PV 和 PVC 的 spec 字段。比如,PV 的存儲(chǔ)(storage)大小,權(quán)限等就必須滿足 PVC 的要求。
- 而第二個(gè)條件,則是 PV 和 PVC 的 storageClassName 字段必須一樣。
創(chuàng)建一個(gè)Pod,PVC使用方式和hostpath類似
apiVersion: v1
kind: Pod
metadata:
name: web-front
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-pvc
Pod將PVC掛載到容器的html目錄,我們?cè)赑VC的目錄下創(chuàng)建一個(gè)文件用來(lái)驗(yàn)證
$ echo "hello nginx" > /data/k8s/index.html
創(chuàng)建Pod,并驗(yàn)證是否將文件掛載至容器
$ kubectl create -f nfs-nginxpod.yaml
$ kubectl exec -it web-front -- /bin/bash
root@web-front:/# curl localhost
hello nginx
可以看到輸出結(jié)果是我們前面寫到PVC卷中的index.html 文件內(nèi)容。
三、StorageClass
在上面PV對(duì)象創(chuàng)建的方式為Static Provisioning(靜態(tài)資源調(diào)配),在大規(guī)模的生產(chǎn)環(huán)境里,面對(duì)集群中大量的PVC,需要提前手動(dòng)創(chuàng)建好PV來(lái)與之綁定,這其實(shí)是一個(gè)非常麻煩的工作。還好kubernetes提供了Dynamic Provisioning(動(dòng)態(tài)資源調(diào)配)的機(jī)制,即:StorageClass對(duì)象, 它的作用其實(shí)就是創(chuàng)建 PV 的模板。
StorageClass 對(duì)象會(huì)定義如下兩個(gè)部分內(nèi)容:
- PV 的屬性。比如,存儲(chǔ)類型、Volume 的大小等等。
- 創(chuàng)建這種 PV 需要用到的存儲(chǔ)插件。比如,Ceph 等等。
Kubernetes 有了這樣兩個(gè)信息之后,就能夠根據(jù)用戶提交的 PVC,找到一個(gè)對(duì)應(yīng)的StorageClass,然后Kubernetes 就會(huì)調(diào)用該 StorageClass 聲明的存儲(chǔ)插件,自動(dòng)創(chuàng)建出需要的 PV。
現(xiàn)在我們創(chuàng)建一個(gè)NFS類型的StorageClass,首先需要?jiǎng)?chuàng)建nfs-client-provisioner(存儲(chǔ)插件):nfs-client 的自動(dòng)配置程序
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 192.168.16.173 # 修改成自己的 IP
- name: NFS_PATH
value: /data/k8s
volumes:
- name: nfs-client-root
nfs:
server: 192.168.16.173 # 修改成自己的 IP
path: /data/k8s
為nfs-client程序綁定相應(yīng)的集群操作權(quán)限 :
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
創(chuàng)建StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: es-data-db
provisioner: fuseim.pri/ifs
provisioner 字段的值是:fuseim.pri/ifs,這個(gè)是NFS提供的分配器,kubernetes也內(nèi)置了一些存儲(chǔ)的分配器:https://kubernetes.io/zh/docs/concepts/storage/storage-classes/
創(chuàng)建資源對(duì)象
$ kubectl create -f nfs-client-Provisioner.yaml
$ kubectl create -f nfs-client-sa.yaml
$ kubectl create -f nfs-storageclass.yaml
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-data-db fuseim.pri/ifs Delete Immediate false 136m
StorageClass創(chuàng)建完成后,開(kāi)發(fā)人員只需要在 PVC 里指定要使用的 StorageClass 名字即可,PV則會(huì)根據(jù)PVC的屬性定義自動(dòng)創(chuàng)建。
創(chuàng)建nfs-pvc02.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc02
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-data-db
resources:
requests:
storage: 1Gi
創(chuàng)建PVC并查看是否綁定相應(yīng)的PV
$ kubectl create -f nfs-pvc02.yaml
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs 5Gi RWX manual 7h49m
nfs-pvc02 Bound pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0 1Gi RWO nfs-data-db 7s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs 5Gi RWX Retain Bound default/nfs-pvc manual 8h
pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0 1Gi RWO Delete Bound default/nfs-pvc02 nfs-data-db 17s
$ ls /data/k8s
default-nfs-pvc02-pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0 index.html
可以看到創(chuàng)建完P(guān)VC后,StorageClass自動(dòng)為PVC創(chuàng)建并綁定了對(duì)應(yīng)的PV,而且PV的屬性是和PVC相同的,在共享卷中也創(chuàng)建了相關(guān)的PV目錄,這樣我們創(chuàng)建Pod時(shí)只需要指定PVC的名字即可,不用再去手動(dòng)的為PVC創(chuàng)建PV。
注意:Kubernetes 只會(huì)將StorageClass 定義相同的 PVC 和 PV 綁定起來(lái)
總結(jié):
hostPath:屬于單節(jié)點(diǎn)集群中的持久化存儲(chǔ),Pod需要綁定集群節(jié)點(diǎn)。刪除pod后,卷里面的文件會(huì)繼續(xù)保持,但pod被重新調(diào)度到其他節(jié)點(diǎn)時(shí),就無(wú)法訪問(wèn)到原數(shù)據(jù)。不適合作為存儲(chǔ)數(shù)據(jù)庫(kù)數(shù)據(jù)的目錄。
emptyDir: 用于存儲(chǔ)臨時(shí)數(shù)據(jù)的簡(jiǎn)單空目錄,生命周期是和pod捆綁的,隨著pod創(chuàng)建而創(chuàng)建;刪除而銷毀,卷的內(nèi)容將會(huì)丟失。emptyDir卷適用于同一個(gè)pod中運(yùn)行的容器之間共享文件。
PVC 描述的,是 Pod 想要使用的持久化存儲(chǔ)的屬性,比如存儲(chǔ)的大小、讀寫權(quán)限等。
PV 描述的,則是一個(gè)具體的 Volume 的屬性,比如 Volume 的類型、掛載目錄、遠(yuǎn)程存儲(chǔ)服務(wù)器地址等。
StorageClass 的作用,則是充當(dāng) PV 的模板。并且,只有同屬于一個(gè) StorageClass 的 PV 和 PVC,才可以綁定在一起。當(dāng)然,StorageClass 的另一個(gè)重要作用,是指定 PV 的 Provisioner(存儲(chǔ)插件)。這時(shí)候,如果你的存儲(chǔ)插件支持 Dynamic Provisioning 的話,Kubernetes 就可以自動(dòng)為你創(chuàng)建 PV 了。
參考資料:
深入剖析Kubernetes-張磊
關(guān)注公眾號(hào)回復(fù)【k8s】獲取視頻教程及更多資料: