前言
本篇是Kubernetes第十三篇,大家一定要把環境搭建起來,看是解決不了問題的,必須實戰。
Kubernetes系列文章:
-
Kubernetes介紹 -
Kubernetes環境搭建 -
Kubernetes-kubectl介紹 -
Kubernetes-Pod介紹(-) -
Kubernetes-Pod介紹(二)-生命周期 -
Kubernetes-Pod介紹(三)-Pod調度 -
Kubernetes-Pod介紹(四)-Deployment -
Kubernetes-Service介紹(一)-基本概念 -
Kubernetes-Service介紹(二)-服務發現 -
Kubernetes-Service介紹(三)-Ingress(含最新版安裝踩坑實踐) -
Kubernetes-網絡 -
Kubernetes-存儲(一)
發展歷程
為了能夠屏蔽底層存儲實現的細節,便於使用和管理,Kubernetes從1.0版本就引入PersistentVolume(PV)和PersistentVolumeClaim(PVC)兩個資源對象來實現對存儲的管理子系統。
PV是對底層網絡共享存儲的抽象,將共享存儲定義為一種“資源”,比如Node也是一種容器應用可以“消費”的資源。PV由管理員創建和配置,它與共享存儲的具體實現直接相關,例如GlusterFS、iSCSI、RBD或GCE或AWS公有雲提供的共享存儲,通過插件式的機制完成與共享存儲的對接,以供應用訪問和使用。
PVC則是用戶對存儲資源的一個“申請”。就像Pod“消費”Node的資源一樣,PVC能夠“消費”PV資源。PVC可以申請特定的存儲空間和訪問模式。
若使用PVC“申請”到一定的存儲空間仍然不能滿足應用對存儲設備的需求。比如通常應用程序都會對存儲設備的特性和性能有不同的要求,包括讀寫速度、並發性能、數據冗余等更高的要求,因此Kubernetes從1.4版本開始引入了一個新的資源對象StorageClass,用於標記存儲資源的特性和性能。
Kubernetes 1.6版本時,StorageClass和動態資源供應的機制得到了完善,實現了存儲卷的按需創建。通過StorageClass的定義,管理員可以將存儲資源定義為某種類別(Class),正如存儲設備對於自身的配置描述(Profile),例如“快速存儲”“慢速存儲”“有數據冗余”“無數據冗余”等。用戶根據StorageClass的描述就能夠直觀地得知各種存儲資源的特性,就可以根據應用對存儲資源的需求去申請存儲資源。
Kubernetes從1.9版本開始引入容器存儲接口Container Storage Interface(CSI)機制,目標是在Kubernetes和外部存儲系統之間建立一套標准的存儲管理接口,通過該接口為容器提供存儲服務,類似於CRI(容器運行時接口)和CNI(容器網絡接口)。
PV和PVC使用介紹
對於PV和PVC使用有兩種使用方式,一種靜態方式創建,一種是動態方式創建;
靜態方式創建
我們創建一個 hostPath 類型的 PersistentVolume。Kubernetes 支持 hostPath 類型的 PersistentVolume 使用節點上的文件或目錄來模擬附帶網絡的存儲,但是需要注意的是在生產集群中,我們不會使用 hostPath,集群管理員會提供網絡存儲資源,比如 NFS 共享卷或 Ceph 存儲卷,集群管理員還可以使用 StorageClasses 來設置動態提供存儲。因為 Pod 並不是始終固定在某個節點上面的,所以要使用 hostPath 的話我們就需要將 Pod 固定在某個節點上,這樣顯然降低了應用的容錯性。

-
我們在demo-slave-2節點上創建如下目錄/data/k8s/hostpath/index.html;
<!DOCTYPE html>
<html>
<head>
<title>hello world</title>
</head>
<body>
<p>hello world</p>
</body>
</html>
-
創建一個 hostPath 類型的 PV 資源對象,配置文件中指定了該卷位於集群節點上的 /data/k8s/hostpath 目錄,還指定了 10G 大小的空間和 ReadWriteOnce 的訪問模式,定義了名稱為 manual 的 StorageClass,該名稱用來將 PersistentVolumeClaim 請求綁定到該 PersistentVolum;
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath
labels:
type: local
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
storageClassName: slow
hostPath:
path: "/data/k8s/hostpath"
-
創建PV資源;
kubectl apply -f pv-hostpath.yaml
-
查看PersistentVolume的狀態,PersistentVolume的狀態為Available,表示可用狀態,還未被任何 PVC 綁定,Reclaim Policy的狀態為Retain,表示保留數據,需要管理員手工清理數據;
kubectl get pv pv-hostpath

-
創建一個對應的PVC來和PV進行綁定;
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-hostpath
spec:
resources:
requests:
storage: 1Gi
storageClassName: slow
accessModes:
- ReadWriteOnce
-
創建 PVC 之后,Kubernetes 就會去查找滿足我們聲明要求的PV;
kubectl create -f pvc-hostpath.yaml
-
接下來我們再次查看 PV 的信息,我們會發現PV已經處於被綁定狀態;

-
接下來我們也看下PVC的信息,我們會發現PVC已經處於被綁定狀態;

-
接下來我們創建Pod,聲明的PVC作為存儲卷,我們通過nodeSelector綁定demo-slave-2節點;
apiVersion: v1
kind: Pod
metadata:
name: pv-hostpath-pod
spec:
volumes:
- name: pv-hostpath
persistentVolumeClaim:
claimName: pvc-hostpath
nodeSelector:
kubernetes.io/hostname: demo-slave-2
containers:
- name: pv-hostpath-pod
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: pv-hostpath
-
查看Pod創建的狀態;
kubectl get pod pv-hostpath-pod

-
進入容器驗證是否Pod是否從hostPath卷提供index.html文件,我們可以看到輸出結果是我們前面寫到 hostPath卷種的index.html 文件中的內容;
#進入容器
kubectl exec -it pv-hostpath-pod -- /bin/bash
#安裝curl
apt-get update
apt-get install curl -y
#訪問
curl localhost

動態方式創建
一個大規模的 Kubernetes 集群里很可能有成千上萬個 PVC,這就意味着運維人員必須得事先創建出成千上萬個 PV。更麻煩的是,隨着新的 PVC 不斷被提交,運維人員就不得不繼續添加新的、能滿足條件的 PV,否則新的 Pod 就會因為 PVC 綁定不到 PV 而失敗。在實際操作中,這幾乎沒辦法靠人工做,Kubernetes 為我們提供了一套可以自動創建 PV 的機制,這里我們采用Local Persistent Volum先做簡單實驗,但是Local Persistent Volum不支持StorageClass動態綁定,這里我們首先創建PV,先講明白用法,當使用Kubernetes的支持的持久卷的是不需要創建PV的,動態創建的流程如下:

實驗前Local Persistent Volum創建准備:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: "/data/k8s/local"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- demo-slave-1
-
創建StorageClass對象,StorageClass配置了volumeBindingMode=WaitForFirstConsumer,告訴 Kubernetes在發現這個StorageClass關聯的PVC與PV進行的延遲綁定,通過這個延遲綁定機制;
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
-
查看StorageClass創建情況;
kubectl apply -f pv-local.yaml

-
創建PVC資源;
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-local
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: local-storage
-
檢查綁定狀況,PVC處於Pending狀態,也就是等待綁定的狀態,原本實時發生的 PVC 和 PV 的綁定過程,就被延遲到了 Pod 第一次調度的時候在調度器中進行,從而保證了這個綁定結果不會影響 Pod 的正常調度;
kubectl get pvc

-
創建一個Pod來使用pvc-local這個PVC;
apiVersion: v1
kind: Pod
metadata:
name: pv-local-pod
spec:
volumes:
- name: pv-local-demo
persistentVolumeClaim:
claimName: pvc-local
containers:
- name: pv-local-pod
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: pv-local-demo
-
查看PVC狀況,這個時候我們會發現被綁定了;

核心概念介紹

PV
PV是對存儲資源的抽象,將存儲定義為容器可以使用的資源,管理員進行創建和配置。主要包括存儲能力、訪問模式、存儲類型、回收策略、后端存儲類型等關鍵信息的設置。

生命周期
PV在生命周期中可能處於以下4個階段:
-
Available:可用狀態,還未與某個PVC綁定;
-
Bound:已與某個PVC綁定;
-
Released:綁定的PVC已經刪除,資源已釋放,但沒有被集群回收;
-
Failed:資源回收失敗;
創建好一個 PV 以后,我們就處於一個 Available 的狀態,當一個 PVC 和一個 PV 綁定的時候,這個 PV 就進入了 Bound 的狀態,此時如果我們把 PVC 刪掉,Bound 狀態的 PV 就會進入 Released 的狀態。
一個 Released 狀態的 PV 會根據自己定義的 ReclaimPolicy 字段來決定自己是進入一個 Available 的狀態還是進入一個 Deleted 的狀態。如果 ReclaimPolicy 定義的是 recycle類型,它會進入一個Available狀態,如果轉變失敗,就會進入 Failed 的狀態。
PVC
PVC是用戶對存儲資源的一個申請,PVC消耗PV的資源。PVC可以申請存儲大小和訪問模式等。

生命周期介紹
PVC在生命周期中可能處於以下3個階段:
-
Pending:等待與PV進行綁定;
-
Bound: PVC找到對應PV進行綁定;
-
Lost: 綁定狀態的PVC,PV被刪除掉;
一個創建好的 PVC 會處於 Pending 狀態,當一個 PVC 與 PV 綁定之后,PVC 就會進入 Bound 的狀態,當一個 Bound 狀態的 PVC 的 PV 被刪掉之后,該 PVC 就會進入一個 Lost 的狀態。對於一個 Lost 狀態的 PVC,它的 PV 如果又被重新創建,並且重新與該 PVC 綁定之后,該 PVC 就會重新回到 Bound 狀態。
StorageClass
StorageClass作為對存儲資源的抽象定義,充當 PV 的模板,對用戶設置的PVC申請屏蔽后端存儲的細節,既減少了用戶對於存儲資源細節的關注,又減輕了管理員手工管理PV的工作,由系統自動完成PV的創建和綁定,實現了動態的資源供應。StorageClass的定義主要包括名稱、后端存儲的提供者、后端存儲的相關參數配置以及回收策略。
CSI
CSI是什么
CSI是Container Storage Interface(容器存儲接口)的簡寫。CSI的目的是定義行業標准容器存儲接口,使存儲供應商能夠開發一個符合CSI標准的插件並使其可以在多個容器編排系統中工作。Kubernetes將通過CSI接口來跟第三方存儲廠商進行通信,來操作存儲,從而提供容器存儲服務。
為什么要有CSI
其實在沒有CSI之前Kubernetes就已經提供了強大的存儲卷插件系統,但是這些插件系統實現是Kubernetes代碼的一部分,需要隨Kubernetes組件二進制文件一起發布,這樣就會存在一些問題。
-
如果第三方存儲廠商發現有問題需要修復或者優化,即使修復后也不能單獨發布,需要與Kubernetes一起發布,對於k8s本身而言,不僅要考慮自身的正常迭代發版,還需要考慮到第三方存儲廠商的迭代發版,這里就存在雙方互相依賴、制約的問題,不利於雙方快速迭代; -
另外第三方廠商的代碼跟Kubernetes代碼耦合在一起,還會引起安全性、可靠性問題,還增加了Kubernetes代碼的復雜度以及后期的維護成本等等。
基於以上問題,Kubernetes將存儲體系抽象出了外部存儲組件接口即CSI,Kubernetes通過grpc接口與第三方存儲廠商的存儲卷插件系統進行通信。
這樣一來,對於第三方存儲廠商來說,既可以單獨發布和部署自己的存儲插件,進行正常迭代,而又無需接觸Kubernetes核心代碼,降低了開發的復雜度。同時,對於Kubernetes來說,這樣不僅降低了自身的維護成本,還能為用戶提供更多的存儲選項。
CSI系統架構

核心概念介紹
volume plugin
擴展各種存儲類型的卷的管理能力,實現第三方存儲的各種操作能力與Kubernetes存儲系統的結合。調用第三方存儲的接口或命令,從而提供數據卷的創建/刪除、attach/detach、mount/umount的具體操作實現,可以認為是第三方存儲的代理人。根據源碼所在位置,volume plugin分為in-tree與out-of-tree。
in-tree
在Kubernetes源碼內部實現,和Kubernetes一起發布、管理,更新迭代慢、靈活性差;
out-of-tree
代碼獨立於Kubernetes,由存儲廠商實現,有CSI、FlexVolume兩種實現;
CSI-plugin
Kubernetes獨立拆分出來,實現 CSI 標准規范接口的邏輯控制與調用,是整個 CSI 控制邏輯的核心樞紐;
Node-driver-registrar
一個由官方 Kubernetes維護的輔助容器(sidecar),它使用 kubelet 插件注冊機制向 kubelet 注冊插件,需要請求 CSI 插件的 Identity 服務來獲取插件信息;
external-provisioner
一個由官方 Kubernetes維護的輔助容器(sidecar),主要功能是實現持久卷的創建(Create)、刪除(Delete);
external-attacher
一個由官方 Kubernetes維護的輔助容器(sidecar),主要功能是實現持久卷的附着(Attach)、分離(Detach);
external-snapshotter
一個由官方 Kubernetes小組維護的輔助容器(sidecar),主要功能是實現持久卷的快照(VolumeSnapshot)、備份恢復等能力;
external-resizer
一個由官方Kubernetes小組維護的輔助容器(sidecar),主要功能是實現持久卷的彈性擴縮容,需要雲廠商插件提供相應的能力;
kube-controller-manager
Kubernetes資源控制器,主要通過 PV Controller, AttachDetach 實現持久卷的綁定(Bound)/解綁(Unbound)、附着(Attach)/分離(Detach);
PV Controller
負責pv、pvc的綁定與生命周期管理(如創建/刪除底層存儲,創建/刪除pv對象,pv與pvc對象的狀態變更);
in-tree:創建/刪除底層存儲、創建/刪除pv對象的操作,由PV controller調用volume plugin(in-tree)來完成;
out-tree CSI:創建/刪除底層存儲、創建/刪除pv對象的操作由external-provisioner與csi plugin共同來完成;
AD Controller
AD Cotroller全稱Attachment/Detachment 控制器,主要負責創建、刪除VolumeAttachment對象,並調用volume plugin來做存儲設備的Attach/Detach操作(將數據卷掛載到特定node節點上/從特定node節點上解除掛載),以及更新node.Status.VolumesAttached等;
不同的volume plugin的Attach/Detach操作邏輯有所不同,對於csi plugin(out-tree volume plugin)來說,AD controller的Attach/Detach操作只是修改VolumeAttachment對象的狀態,而不會真正的將數據卷掛載到節點/從節點上解除掛載,真正的節點存儲掛載/解除掛載操作由kubelet中volume manager調用csi plugin來完成;
volume manager
主要是管理卷的Attach/Detach(與AD controller作用相同,通過kubelet啟動參數控制哪個組件來做該操作)、mount/umount等操作;
對於CSI來說,volume manager的Attach/Detach操作只創建/刪除VolumeAttachment對象,而不會真正的將數據卷掛載到節點/從節點上解除掛載;csi-attacer組件也不會做掛載/解除掛載操作,只是更新VolumeAttachment對象,真正的節點存儲掛載/解除掛載操作由kubelet中volume manager調用調用csi plugin來完成。
Kubernetes創建與掛載Volume
in-tree

-
用戶創建PVC;
-
PV Controller watch到PVC的創建,尋找合適的PV與之綁定;
-
當找不到合適的PV時,將調用volume plugin來創建volume,並創建PV對象,之后該PV對象與PVC對象綁定;
-
用戶創建掛載PVC的Pod;
-
kube-scheduler watch到Pod的創建,為其尋找合適的Node調度;
-
Pod調度完成后,AD controller/volume manager watch到Pod聲明的volume沒有進行attach操作,將調用volume plugin來做attach操作;
-
volume plugin進行attach操作,將volume掛載到pod所在node節點,成為如/dev/vdb的設備;
-
attach操作完成后,volume manager watch到Pod聲明的volume沒有進行mount操作,將調用volume plugin來做mount操作;
-
volume plugin進行mount操作,將Node節點上的得到的/dev/vdb設備掛載到指定目錄;
out-of-tree

-
用戶創建PVC;
-
PV Controller watch到PVC的創建,尋找合適的PV與之綁定。當尋找不到合適的PV時,讓external-provisioner組件開始開始創建存儲與PV對象的操作;
-
external-provisioner watch到PVC的創建/更新事件,通過判斷PVC的annotation[volume.beta.kubernetes.io/storage-provisioner]是否與自己的provisioner名稱相等,用來判斷PVC是否需要動態創建存儲卷,是則調用csi-plugin ControllerServer來創建存儲,並創建PV對象;
-
PV Controller將上一步創建的PV與PVC綁定;
-
用戶創建掛載PVC的Pod;
-
kube-scheduler watch到Pod的創建,為其尋找合適的Node調度;
-
Pod調度完成后,AD Controller/volume manager watch到Pod聲明的volume沒有進行attach操作,將調用csi-attacher來做attach操作,實際上只是創建VolumeAttachement對象;
-
external-attacher組件watch到VolumeAttachment對象的創建,調用csi-plugin進行attach操作;
-
csi-plugin ControllerServer進行attach操作,將volume掛載到Pod所在Node節點,成為如/dev/vdb的設備;
-
attach操作完成后,volume manager watch到Pod聲明的volume沒有進行mount操作,將調用csi-mounter來做mount操作;
-
csi-mounter調用csi-plugin NodeServer進行mount操作,將Node節點上的/dev/vdb設備掛載到指定目錄;

參考
從零開始入門 K8s:Kubernetes 存儲架構及插件使用
結束
歡迎大家點點關注,點點贊!