本篇已加入《.NET Core on K8S學習實踐系列文章索引》,可以點擊查看更多容器化技術相關系列文章。
在Docker中我們知道,要想實現數據的持久化(所謂Docker的數據持久化即數據不隨着Container的結束而結束),需要將數據從宿主機掛載到容器中,常用的手段就是Volume數據卷。在K8S中,也提供了存儲模型Volume,支持我們將應用中的數據持久化存儲到容器中。
一、Volume
1.1 關於K8S Volume
為了持久化保存容器的數據,我們可以使用K8S Volume,其本質上也是一個目錄,與Docker Volume沒有什么區別。
需要注意的是:K8S Volume的生命周期獨立於容器,Pod中的容器可能被銷毀和重建,但Volume會被保留。
當Volume被mount到Pod中,Pod中的的所有容器都可以訪問這個Volume。在K8S中,支持多種backend類型,例如emptyDir, hostPath, NFS, Ceph以及一些雲服務商提供的存儲服務等等。對Pod來說,它不需要關心到底數據會被存儲在本地文件系統中還是遠程雲端硬盤中,它認為所有類型的Volume都只是一個目錄而已。
1.2 使用K8S Volume
(1)emptyDir
作為K8S最基礎的Volume類型,emptyDir提供了最基礎的持久化方案,但是這個方案不怎么好。因為,emptyDir對於Pod來說並非持久的(它對於容器來說是持久化的),因為當Pod從節點刪除時,Volume的內容也會被刪除。但如果只是容器被銷毀而Pod還在,則Volume不會受影響。
換句話說:emptyDir Volume的生命周期與Pod一致。鑒於此特性,不建議在實際中使用此類型Volume。
(2)hostPath
相對於emptyDir,hotPath則克服了其生命周期的弱點,如果Pod被銷毀,hostPath對應的目錄還是會被保留。不過,如果一旦Host崩潰,hostPath也就無法訪問了。因為,hostPath是將Docker Host文件系統中已經存在的目錄mount給Pod的容器,所以會依賴於Host。
在K8S中,那些需要訪問K8S或Docker內部數據(配置文件和二進制庫)的應用需要使用到hostPath,比如kube-apiserver和kube-controller-manager這樣的應用。下面的配置就是kube-apiserver的持久化設置,其定義了3個hostPath:ca-certs, etc-pki以及k8s-certs,分別對應Host目錄為/etc/ssl/certs, /etc/pki 以及 /etc/kubernetes/pki。
volumeMounts: - mountPath: /etc/ssl/certs name: ca-certs readOnly: true - mountPath: /etc/pki name: etc-pki readOnly: true - mountPath: /etc/kubernetes/pki name: k8s-certs readOnly: true dnsPolicy: ClusterFirst enableServiceLinks: true hostNetwork: true nodeName: k8s-master priority: 2000000000 nodeName: k8s-master priority: 2000000000 priorityClassName: system-cluster-critical restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 tolerations: - effect: NoExecute operator: Exists volumes: - hostPath: path: /etc/ssl/certs type: DirectoryOrCreate name: ca-certs - hostPath: path: /etc/pki type: DirectoryOrCreate name: etc-pki - hostPath: path: /etc/kubernetes/pki type: DirectoryOrCreate name: k8s-certs
(3)外部Storage Povider
如果我們的K8S是部署在AWS、GCE、Azure等公有雲上,那么可以直接使用雲硬盤做為Volume。這里由於我沒有使用,所以跳過,有使用的朋友可以直接參考各雲服務提供商的參考文檔進行配置。
二、PersistentVolume與PersistentVolumeClaim
2.1 關於PersistentVolume與PersistentVolumeClaim
前面提到的幾種方案在可管理性上均有不足,特別是當集群規模較大的時候,效率和安全性均有待提高。因此,K8S提供了一個解決方案:PersistentVolume 和 PersistentVolumeClaim,以下簡稱PV和PVC。
PV是外部存儲系統中的一塊存儲空間,由管理員創建和維護。與Volume一樣,PV具有持久性,生命周期獨立於Pod。
PVC則是對PV的申請(Claim),PVC通常由普通用戶創建和維護。當需要為Pod分配存儲資源的時候,用戶就可以創建一個PVC,指明存儲資源的容量大小和訪問方式(比如ReadOnly)等信息,K8S會查找並提供滿足條件的PV。
了解ASP.NET Identity的童鞋應該對Claim這個詞不陌生,如果把我們的認證信息看成一個Claims,那么其中的一個一個的鍵值對就是Claim。我們常用的ClaimTypes如下圖所示,我們可以通過Claim定位到認證信息中的Value。
同理,我們知道了Claim也就可以定位到我們要使用的哪個PV的地址。
與K8S Volume一樣,K8S PersistentVolume也支持多種類型的存儲,比如NFS、AWS EBS、Ceph等等。
2.2 NFS PV的使用
NFS是網絡文件系統 (Network File System), 它允許系統將本地目錄和文件共享給網絡上的其他系統。通過 NFS,用戶和應用程序可以訪問遠程系統上的文件,就象它們是本地文件一樣。
關於如何為CentOS配置NFS,請參考這一篇文章《CentOS7安裝NFS服務》。
這里假設已經為我們的k8s-master節點搭建了一個NFS服務器,目錄為/edc/k8s/nfsdata,如下圖所示:
(1)創建一個PV
接下來我們就來創建一個PV,其yaml配置文件如下:
apiVersion: v1 kind: PersistentVolume metadata: name: edc-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: nfs nfs: path: /edc/k8s/nfsdata/edc-pv server: 192.168.2.100
其中:
- capacity指定了PV的容量為1GB
- accessModes指定訪問模式為ReadWriteOnce,表示PV能夠以Read-Write模式mount到單個節點。此外,還支持ReadOnlyMany和ReadWriteMany,分別代表PV能以Read-Only模式或者Read-Write模式mount到多個節點。這里ReadWriteOnce只mount到單個節點,即k8s-master(192.168.2.100)。
- persistentVolumeReclaimPolicy指定了此PV的回收策略為Recycle,表示清除PV中的數據。此外,還支持Retain和Delete,Retain表示需要管理員手動回收,類似於你用C/C++還需要手動寫free代碼釋放空間。而Delete呢,表示刪除Storage Provider中的對應存儲資源,如果你使用的是外部雲服務提供商的存儲空間的話。
- storageClassName指定了PV的class為nfs。
- nfs配置項指定了PV在NFS服務器上對應的目錄,如果沒有可以事先創建一下。
理解了其中的配置項,我們創建該PV,可以看到其狀態Status變為了Available,表示可以被PVC申請啦。
(2)創建一個PVC
與創建PV不同,創建PVC只需指定PV容量、訪問模式以及class即可:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: edc-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: nfs
有了配置文件,就可以創建PVC了:
可以看到,edc-pvc已經Bound到edc-pv了,申請PV成功。
申請成功之后,我們就可以在Pod中使用了,下面是一個示例Pod的配置文件:
apiVersion: v1 kind: Pod metadata: name: edc-pv-pod spec: containers: - name: edc-pv-pod image: busybox args: - /bin/sh - -c - sleep 30000 volumeMounts: - mountPath: "/mydata" name: mydata volumes: - name: mydata persistentVolumeClaim: claimName: edc-pvc
通過kubectl創建該pod,如下所示:
接下來驗證一下PV是否可用:
可以看到,在Pod中創建的文件/mydata/hello已經保存到了NFS服務器目錄的edc-pv目錄下了。
2.2 NFS PV的回收
當我們不再需要某個PV時,也可以使用PVC來回收PV,如下所示:
kubectl delete pvc edc-pvc
當edc-pvc被刪除后,我們會發現K8S啟動了一個新Pod,這個Pod就是用來清除edc-pv的數據的。數據的清理需要一個過程,完成后edc-pv的狀態會重新恢復為Available,此時可以被新的PVC申請。
此外,由於我們設置的回收策略為Recycle,所以Pod中的數據也被清除了:
如果希望能夠保留這些數據,那么我們需要將回收策略改為Retain:
apiVersion: v1 kind: PersistentVolume metadata: name: edc-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /edc/k8s/nfsdata/edc-pv server: 192.168.2.100
這里就不再驗證Retain的效果了。
三、MySQL持久化存儲案例
3.1 准備工作
這里我們來演示一個MySQL持久化存儲的案例:
(1)創建PV和PVC
准備PV和PVC的yaml:
-- mysql-pv apiVersion: v1 kind: PersistentVolume metadata: name: mysql-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /edc/k8s/nfsdata/mysql-pv server: k8s-master -- mysql-pvc apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: nfs
通過kubectl apply創建PV和PVC:
kubectl apply -f mysql-pv.yaml
kubectl apply -f mysql-pvc.yaml
可以看到,mysql-pvc已經申請到了mysql-pv。
(2)部署MySQL
准備yaml配置文件:
apiVersion: v1 kind: Service metadata: name: mysql-service spec: ports: - port: 3306 selector: app: mysql --- apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - image: mysql:5.6 name: mysql env: - name: MYSQL_ROOT_PASSWORD value: password ports: - containerPort: 3306 name: mysql-container volumeMounts: - name: mysql-storage mountPath: /var/lib/mysql volumes: - name: mysql-storage persistentVolumeClaim: claimName: mysql-pvc
重點關注其中的volumeMounts和volumes配置,其中mysql-pvc申請Bound的mysql-pv將會被mount到MySQL的數據目錄/var/lib/mysql下。
通過kubectl創建MySQL:
kubectl apply -f mysql-service.yaml
kubectl get pod -o wide
可以看到,MySQL被部署到了k8s-node1節點上。
(3)客戶端訪問MySQL
下面我們在k8s-master上通過客戶端訪問MySQL Service:
kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql-service -ppassword
如下圖所示,進入了MySQL數據庫:
接下來我們更新一下數據庫,如下圖所示:
新建了一張表edc_test,插入了一行數據1110.
3.2 快速驗證
(1)模擬k8s-node1故障
接下來我們模擬一下k8s-node1宕機,這樣在k8s-node1上運行的MySQL服務就會受到影響,不過根據之前的了解,K8S會幫我們將MySQL遷移到k8s-node2上從而保證服務可用。
首先,關閉k8s-node1:
shutdown now
其次,驗證K8S遷移MySQL:
(2)驗證數據一致性
雖然k8s-node1掛了,但是K8S幫我們遷移了MySQL到k8s-node2,而且數據也是完好無損,如下圖所示:
(3)驗證數據持久性
如果我們將部署的Service和Deployment刪掉,那么其Pod也會停止被刪除,但是由於我們的PV的回收策略是Retain,因此其數據不會被清除:
四、小結
本文探索了K8S的數據管理方案Volume,其中普通類型的Volume如emptyDir和hostPath雖然使用方便,但是可持久性不強,而外部雲存儲Volume Provider則提供了更好的持久化存儲。PV和PVC的模式,更加適合於我們使用在實際環境中,最后還通過了一個MySQL持久化案例演示了如何應用PV和PVC實現持久化。
參考資料
(1)CloudMan,《每天5分鍾玩轉Kubernetes》
(2)李振良,《一天入門Kubernets教程》
(3)馬哥(馬永亮),《Kubernetes快速入門》
(4)~信~仰~,《CentOS7安裝NFS服務》