做者 | 闞俊寶 阿里巴巴高級技術專家node
本文整理自《CNCF x Alibaba 雲原生技術公開課》第 21 講。git
導讀:容器存儲是 Kubernetes 系統中提供數據持久化的基礎組件,是實現有狀態服務的重要保證。Kubernetes 默認提供了主流的存儲卷接入方案(In-Tree),同時也提供了插件機制(Out-Of-Tree),容許其餘類型的存儲服務接入 Kubernetes 系統服務。本文將從 Kubernetes 存儲架構、存儲插件原理、實現等方面進行講解,但願你們有所收穫。github
首先以一個 Volume 的掛載例子來做爲引入。shell
以下圖所示,左邊的 YAML 模板定義了一個 StatefulSet 的一個應用,其中定義了一個名爲 disk-pvc 的 volume,掛載到 Pod 內部的目錄是 /data。disk-pvc 是一個 PVC 類型的數據卷,其中定義了一個 storageClassName。安全
所以這個模板是一個典型的動態存儲的模板。右圖是數據卷掛載的過程,主要分爲 6 步:數據結構
第一步:用戶建立一個包含 PVC的 Pod;架構
第二步:PV Controller 會不斷觀察 ApiServer,若是它發現一個 PVC 已經建立完畢但仍然是未綁定的狀態,它就會試圖把一個 PV 和 PVC 綁定;less
PV Controller 首先會在集羣內部找到一個適合的 PV 進行綁定,若是未找到相應的 PV,就調用 Volume Plugin 去作 Provision。Provision 就是從遠端上一個具體的存儲介質建立一個 Volume,而且在集羣中建立一個 PV 對象,而後將此 PV 和 PVC 進行綁定;dom
咱們知道,當一個 Pod 運行的時候,須要選擇一個 Node,這個節點的選擇就是由 Scheduler 來完成的。Scheduler 進行調度的時候會有多個參考量,好比 Pod 內部所定義的 nodeSelector、nodeAffinity 這些定義以及 Volume 中所定義的一些標籤等。異步
咱們能夠在數據卷中添加一些標籤,這樣使用這個 pv 的 Pod 就會因爲標籤的限制,被調度器調度到指望的節點上。
第四步:若是有一個 Pod 調度到某個節點以後,它所定義的 PV 尚未被掛載(Attach),此時 AD Controller 就會調用 VolumePlugin,把遠端的 Volume 掛載到目標節點中的設備上(如:/dev/vdb);
**第五步:**當 Volum Manager 發現一個 Pod 調度到本身的節點上而且 Volume 已經完成了掛載,它就會執行 mount 操做,將本地設備(也就是剛纔獲得的 /dev/vdb)掛載到 Pod 在節點上的一個子目錄中。同時它也可能會作一些像格式化、是否掛載到 GlobalPath 等這樣的附加操做。
第六步:綁定操做,就是將已經掛載到本地的 Volume 映射到容器中。
接下來,咱們一塊兒看一下 Kubernetes 的存儲架構。
PV Controller: 負責 PV/PVC 的綁定、生命週期管理,並根據需求進行數據卷的 Provision/Delete 操做;
AD Controller:負責存儲設備的 Attach/Detach 操做,將設備掛載到目標節點;
Volume Manager:管理卷的 Mount/Unmount 操做、卷設備的格式化以及掛載到一些公用目錄上的操做;
Volume Plugins:它主要是對上面全部掛載功能的實現;
PV Controller、AD Controller、Volume Manager 主要是進行操做的調用,而具體操做則是由 Volume Plugins 實現的。
接下來,咱們分別介紹上面這幾部分的功能。
首先咱們先來回顧一下幾個基本概念:
例如,咱們去掛載一個遠端的 NAS 的時候,這個 NAS 的具體參數就要定義在 PV 中。一個 PV 是沒有 NameSpace 限制的,它通常由 Admin 來建立與維護;
它是用戶所使用的存儲接口,對存儲細節無感知,主要是定義一些基本存儲的 Size、AccessMode 參數在裏面,而且它是屬於某個 NameSpace 內部的。
一個動態存儲卷會按照 StorageClass 所定義的模板來建立一個 PV,其中定義了建立模板所須要的一些參數和建立 PV 的一個 Provisioner(就是由誰去建立的)。
PV Controller 的主要任務就是完成 PV、PVC 的生命週期管理,好比建立、刪除 PV 對象,負責 PV、PVC 的狀態遷移;另外一個任務就是綁定 PVC 與 PV 對象,一個 PVC 必須和一個 PV 綁定後才能被應用使用,它們是一一綁定的,一個 PV 只能被一個 PVC 綁定,反之亦然。<br /> <br />接下來,咱們看一下一個 PV 的狀態遷移圖。
建立好一個 PV 之後,咱們就處於一個 Available 的狀態,當一個 PVC 和一個 PV 綁定的時候,這個 PV 就進入了 Bound 的狀態,此時若是咱們把 PVC 刪掉,Bound 狀態的 PV 就會進入 Released 的狀態。
一個 Released 狀態的 PV 會根據本身定義的 ReclaimPolicy 字段來決定本身是進入一個 Available 的狀態仍是進入一個 Deleted 的狀態。若是 ReclaimPolicy 定義的是 "recycle" 類型,它會進入一個 Available 狀態,若是轉變失敗,就會進入 Failed 的狀態。
相對而言,PVC 的狀態遷移圖就比較簡單。
一個建立好的 PVC 會處於 Pending 狀態,當一個 PVC 與 PV 綁定以後,PVC 就會進入 Bound 的狀態,當一個 Bound 狀態的 PVC 的 PV 被刪掉以後,該 PVC 就會進入一個 Lost 的狀態。對於一個 Lost 狀態的 PVC,它的 PV 若是又被從新建立,而且從新與該 PVC 綁定以後,該 PVC 就會從新回到 Bound 狀態。
下圖是一個 PVC 去綁定 PV 時對 PV 篩選的一個流程圖。就是說一個 PVC 去綁定一個 PV 的時候,應該選擇一個什麼樣的 PV 進行綁定。
首先它會檢查 VolumeMode 這個標籤,PV 與 PVC 的 VolumeMode 標籤必須相匹配。VolumeMode 主要定義的是咱們這個數據卷是文件系統 (FileSystem) 類型仍是一個塊 (Block) 類型;
第二個部分是 LabelSelector。當 PVC 中定義了 LabelSelector 以後,咱們就會選擇那些有 Label 而且與 PVC 的 LabelSelector 相匹配的 PV 進行綁定;
第三個部分是 StorageClassName 的檢查。若是 PVC 中定義了一個 StorageClassName,則必須有此相同類名的 PV 才能夠被篩選中。
這裏再具體解釋一下 StorageClassName 這個標籤,該標籤的目的就是說,當一個 PVC 找不到相應的 PV 時,咱們就會用該標籤所指定的 StorageClass 去作一個動態建立 PV 的操做,同時它也是一個綁定條件,當存在一個知足該條件的 PV 時,就會直接使用現有的 PV,而再也不去動態建立。
AccessMode 就是平時咱們在 PVC 中定義的如 "ReadWriteOnce"、"RearWriteMany" 這樣的標籤。該綁定條件就是要求 PVC 和 PV 必須有匹配的 AccessMode,即 PVC 所需求的 AccessMode 類型,PV 必須具備。
一個 PVC 的 Size 必須小於等於 PV 的 Size,這是由於 PVC 是一個聲明的 Volume,實際的 Volume 必需要大於等於聲明的 Volume,才能進行綁定。
接下來,咱們看一個 PV Controller 的一個實現。
PV Controller 中主要有兩個實現邏輯:一個是 ClaimWorker;一個是 VolumeWorker。
ClaimWorker 實現的是 PVC 的狀態遷移。
經過系統標籤 "pv.kubernetes.io/bind-completed" 來標識一個 PVC 的狀態。
這個時候就須要檢查整個集羣中的 PV 去進行篩選。經過 findBestMatch 就能夠去篩選全部的 PV,也就是按照以前提到的五個綁定條件來進行篩選。若是篩選到 PV,就執行一個 Bound 操做,不然就去作一個 Provision 的操做,本身去建立一個 PV。
再看 VolumeWorker 的操做。它實現的則是 PV 的狀態遷移。
經過 PV 中的 ClaimRef 標籤來進行判斷,若是該標籤爲空,就說明該 PV 是一個 Available 的狀態,此時只須要作一個同步就能夠了;若是該標籤非空,這個值是 PVC 的一個值,咱們就會去集羣中查找對應的 PVC。若是存在該 PVC,就說明該 PV 處於一個 Bound 的狀態,此時會作一些相應的狀態同步;若是找不到該 PVC,就說明該 PV 處於一個綁定過的狀態,相應的 PVC 已經被刪掉了,這時 PV 就處於一個 Released 的狀態。此時再根據 ReclaimPolicy 是不是 Delete 來決定是刪掉仍是隻作一些狀態的同步。<br /> <br />以上就是 PV Controller 的簡要實現邏輯。
AD Controller 是 Attach/Detach Controller 的一個簡稱。
它有兩個核心對象,即 DesiredStateofWorld 和 ActualStateOfWorld。
它有兩個核心邏輯,desiredStateOfWorldPopulator 和 Reconcile。
desiredStateOfWorldPopulator 主要是用來同步集羣的一些數據以及 DSW、ASW 數據的更新,它會把集羣裏面,好比說咱們建立一個新的 PVC、建立一個新的 Pod 的時候,咱們會把這些數據的狀態同步到 DSW 中;
Reconcile 則會根據 DSW 和 ASW 對象的狀態作狀態同步。它會把 ASW 狀態變成 DSW 狀態,在這個狀態的轉變過程當中,它會去執行 Attach、Detach 等操做。
下面這個表分別給出了 desiredStateOfWorld 以及 actualStateOfWorld 對象的一個具體例子。
下圖是 AD Controller 實現的邏輯框圖。
從中咱們能夠看到,AD Controller 中有不少 Informer,Informer 會把集羣中的 Pod 狀態、PV 狀態、Node 狀態、PVC 狀態同步到本地。
在初始化的時候會調用 populateDesireStateofWorld 以及 populateActualStateofWorld 將 desireStateofWorld、actualStateofWorld 兩個對象進行初始化。
在執行的時候,經過 desiredStateOfWorldPopulator 進行數據同步,即把集羣中的數據狀態同步到 desireStateofWorld 中。reconciler 則經過輪詢的方式把 actualStateofWorld 和 desireStateofWorld 這兩個對象進行數據同步,在同步的時候,會經過調用 Volume Plugin 進行 attach 和 detach 操做,同時它也會調用 nodeStatusUpdater 對 Node 的狀態進行更新。
以上就是 AD Controller 的簡要實現邏輯。
Volume Manager 其實是 Kubelet 中一部分,是 Kubelet 中衆多 Manager 的一個。它主要是用來作本節點 Volume 的 Attach/Detach/Mount/Unmount 操做。
它和 AD Controller 同樣包含有 desireStateofWorld 以及 actualStateofWorld,同時還有一個 volumePluginManager 對象,主要進行節點上插件的管理。在覈心邏輯上和 AD Controller 也相似,經過 desiredStateOfWorldPopulator 進行數據的同步以及經過 Reconciler 進行接口的調用。
這裏咱們須要講一下 Attach/Detach 這兩個操做:
以前咱們提到 AD Controller 也會作 Attach/Detach 操做,因此究竟是由誰來作呢?咱們能夠經過 "--enable-controller-attach-detach" 標籤進行定義,若是它爲 True,則由 AD Controller 來控制;若爲 False,就由 Volume Manager 來作。
它是 Kubelet 的一個標籤,只能定義某個節點的行爲,因此若是假設一個有 10 個節點的集羣,它有 5 個節點定義該標籤爲 False,說明這 5 個節點是由節點上的 Kubelet 來作掛載,而其它 5 個節點是由 AD Controller 來作掛載。
下圖是 Volume Manager 實現邏輯圖。
咱們能夠看到,最外層是一個循環,內部則是根據不一樣的對象,包括 desireStateofWorld, actualStateofWorld 的不一樣對象作一個輪詢。
例如,對 actualStateofWorld 中的 MountedVolumes 對象作輪詢,對其中的某一個 Volume,若是它同時存在於 desireStateofWorld,這就說明實際的和指望的 Volume 均是處於掛載狀態,所以咱們不會作任何處理。若是它不存在於 desireStateofWorld,說明指望狀態中該 Volume 應該處於 Umounted 狀態,就執行 UnmountVolume,將其狀態轉變爲 desireStateofWorld 中相同的狀態。
因此咱們能夠看到:實際上,該過程就是根據 desireStateofWorld 和 actualStateofWorld 的對比,再調用底層的接口來執行相應的操做,下面的 desireStateofWorld.UnmountVolumes 和 actualStateofWorld.AttachedVolumes 的操做也是一樣的道理。
咱們以前提到的 PV Controller、AD Controller 以及 Volume Manager 其實都是經過調用 Volume Plugin 提供的接口,好比 Provision、Delete、Attach、Detach 等去作一些 PV、PVC 的管理。而這些接口的具體實現邏輯是放在 VolumePlugin 中的
根據源碼的位置可將 Volume Plugins 分爲 In-Tree 和 Out-of-Tree 兩類:
從位置上咱們能夠看到,Volume Plugins 實際上就是 PV Controller、AD Controller 以及 Volume Manager 所調用的一個庫,分爲 In-Tree 和 Out-of-Tree 兩類 Plugins。它經過這些實現來調用遠端的存儲,好比說掛載一個 NAS 的操做 "mount -t nfs ***",該命令其實就是在 Volume Plugins 中實現的,它會去調用遠程的一個存儲掛載到本地。
從類型上來看,Volume Plugins 能夠分爲不少種。In-Tree 中就包含了 幾十種常見的存儲實現,但一些公司的本身定義私有類型,有本身的 API 和參數,公共存儲插件是沒法支持的,這時就須要 Out-of-Tree 類的存儲實現,好比 CSI、FlexVolume。
Volume Plugins 的具體實現會放到後面去講。這裏主要看一下 Volume Plugins 的插件管理。
Kubernetes會在 PV Controller、AD Controller 以及 Volume Manager 中來作插件管理。經過 VolumePlguinMg 對象進行管理。主要包含 Plugins 和 Prober 兩個數據結構。
Plugins 主要是用來保存 Plugins 列表的一個對象,而 Prober 是一個探針,用於發現新的 Plugin,好比 FlexVolume、CSI 是擴展的一種插件,它們是動態建立和生成的,因此一開始咱們是沒法預知的,所以須要一個探針來發現新的 Plugin。
下圖是插件管理的整個過程。
PV Controller、AD Controller 以及 Volume Manager 在啓動的時候會執行一個 InitPlugins 方法來對 VolumePluginsMgr 作一些初始化。
它首先會將全部 In-Tree 的 Plugins 加入到咱們的插件列表中。同時會調用 Prober 的 init 方法,該方法會首先調用一個 InitWatcher,它會時刻觀察着某一個目錄 (好比圖中的 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/),當這個目錄每生成一個新文件的時候,也就是建立了一個新的 Plugins,此時就會生成一個新的 FsNotify.Create 事件,並將其加入到 EventsMap 中;同理,若是刪除了一個文件,就生成一個 FsNotify.Remove 事件加入到 EventsMap 中。
當上層調用 refreshProbedPlugins 時,Prober 就會把這些事件進行一個更新,若是是 Create,就將其添加到插件列表;若是是 Remove,就從插件列表中刪除一個插件。
以上就是 Volume Plugins 的插件管理機制。
咱們以前說到 Pod 必須被調度到某個 Worker 上才能去運行。在調度 Pod 時,咱們會使用不一樣的調度器來進行篩選,其中有一些與 Volume 相關的調度器。例如 VolumeZonePredicate、VolumeBindingPredicate、CSIMaxVolumLimitPredicate 等。
VolumeZonePredicate 會檢查 PV 中的 Label,好比 failure-domain.beta.kubernetes.io/zone 標籤,若是該標籤訂義了 zone 的信息,VolumeZonePredicate 就會作相應的判斷,即必須符合相應的 zone 的節點才能被調度。
好比下圖左側的例子,定義了一個 label 的 zone 爲 cn-shenzhen-a。右側的 PV 則定義了一個 nodeAffinity,其中定義了 PV 所指望的節點的 Label,該 Label 是經過 VolumeBindingPredicate 進行篩選的。
存儲卷具體調度信息的實現能夠參考《從零開始入門 K8s | 應用存儲和持久化數據卷:存儲快照與拓撲調度》,這裏會有一個更加詳細的介紹。
Flexvolume 是 Volume Plugins 的一個擴展,主要實現 Attach/Detach/Mount/Unmount 這些接口。咱們知道這些功能本是由 Volume Plugins 實現的,可是對於某些存儲類型,咱們須要將其擴展到 Volume Plugins 之外,因此咱們須要把接口的具體實現放到外面。
在下圖中咱們能夠看到,Volume Plugins 其實包含了一部分 Flexvolume 的實現代碼,但這部分代碼其實只有一個 「Proxy」的功能。
好比當 AD Controller 調用插件的一個 Attach 時,它首先會調用 Volume Plugins 中 Flexvolume 的 Attach 接口,但這個接口只是把調用轉到相應的 Flexvolume 的Out-Of-Tree實現上。
Flexvolume是可被 Kubelet 驅動的可執行文件,每一次調用至關於執行一次 shell 的 ls 這樣的腳本,都是可執行文件的命令行調用,所以它不是一個常駐內存的守護進程。
Flexvolume 的 Stdout 做爲 Kubelet 調用的返回結果,這個結果須要是 JSON 格式。
Flexvolume默認的存放地址爲 "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/alicloud~disk/disk"。
下面是一個命令格式和調用的實例。
Flexvolum 包含如下接口:
init: 主要作一些初始化的操做,好比部署插件、更新插件的時候作 init 操做,返回的時候會返回剛纔咱們所說的 DriveCapabilities 類型的數據結構,用來講明咱們的 Flexvolume 插件有哪些功能;
GetVolumeName: 返回插件名;
Attach: 掛載功能的實現。根據 --enable-controller-attach-detach 標籤來決定是由 AD Controller 仍是 Kubelet 來發起掛載操做;
WaitforAttach: Attach 常常是異步操做,所以須要等待掛載完成,才能須要進行下面的操做;
MountDevice:它是 mount 的一部分。這裏咱們將 mount 分爲 MountDevice 和 SetUp 兩部分,MountDevice 主要作一些簡單的預處理工做,好比將設備格式化、掛載到 GlobalMount 目錄中等;
GetPath:獲取每一個 Pod 對應的本地掛載目錄;
Setup:使用 Bind 方式將 GlobalPath 中的設備掛載到 Pod 的本地目錄;
TearDown、UnmountDevice、Detach 實現的是上面一些藉口的逆過程;
ExpandVolumeDevice:擴容存儲卷,由 Expand Controller 發起調用;
NodeExpand: 擴容文件系統,由 Kubelet 發起調用。
<br />上面這些接口不必定須要所有實現,若是某個接口沒有實現的話,能夠將返回結果定義成:
{ "status": "Not supported", "message": "error message" }
告訴調用者沒有實現這個接口。此外,Volume Plugins 中的 Flexvolume 接口除了做爲一個 Proxy 外,它也提供了一些默認實現,好比 Mount 操做。因此若是你的 Flexvolume 中沒有定義該接口,該默認實現就會被調用。
在定義 PV 時能夠經過 secretRef 字段來定義一些 secret 的功能。好比掛載時所需的用戶名和密碼,就能夠經過 secretRef 傳入。
從掛載流程和卸載流程兩個方向來分析 Flexvolume 的掛載過程。
咱們首先看 Attach 操做,它調用了一個遠端的 API 把咱們的 Storage 掛載到目標節點中的某個設備上去。而後經過 MountDevice 將本地設備掛載到 GlobalPath 中,同時也會作一些格式化這樣的操做。Mount 操做(SetUp),它會把 GlobalPath 掛載 PodPath 中,PodPath 就是 Pod 啓動時所映射的一個目錄。
下圖給出了一個例子,好比咱們一個雲盤,其 Volume ID 爲 d-8vb4fflsonz21h31cmss,在執行完 Attach 和 WaitForAttach 操做以後,就會將其掛載到目標節點上的 /dec/vdc 設備中。執行 MountDevice 以後,就會把上述設備格式化,掛載到一個本地的 GlobalPath 中。而執行完 Mount 以後,就會將 GlobalPath 映射到 Pod 相關的一個子目錄中。最後執行 Bind 操做,將咱們的本地目錄映射到容器中。這樣完成一次掛載過程。
卸載流程就是一個逆過程。上述過程描述的是一個塊設備的掛載過程,對於文件存儲類型,就無需 Attach、MountDevice操做,只須要 Mount 操做,所以文件系統的 Flexvolume 實現較爲簡單,只須要 Mount 和 Unmount 過程便可。
其中主要實現的是 init()、doMount()、doUnmount() 方法。在執行該腳本的時候對傳入的參數進行判斷來決定執行哪個命令。<br /> <br />在 Github 上還有不少 Flexvolume 的示例,你們能夠自行參考查閱。阿里雲提供了一個 Flexvolume 的實現,有興趣的能夠參考一下。
下圖給出了一個 Flexvolume 類型的 PV 模板。它和其它模板實際上沒有什麼區別,只不過類型被定義爲 flexVolume 類型。flexVolume 中定義了 driver、fsType、options。
咱們也能夠像其它類型同樣,經過 selector 中的 matchLabels 定義一些篩選條件。一樣也能夠定義一些相應的調度信息,好比定義 zone 爲 cn-shenzhen-a。
下面是一個具體的運行結果。在 Pod 內部咱們掛載了一個雲盤,其所在本地設備爲 /dev/vdb。經過 mount | grep disk 咱們能夠看到相應的掛載目錄,首先它會將 /dev/vdb 掛載到 GlobalPath 中;其次會將 GlobalPath 經過 mount 命令掛載到一個 Pod 所定義的本地子目錄中去;最後會把該本地子目錄映射到 /data 上。
和 Flexvolume 相似,CSI 也是爲第三方存儲提供數據卷實現的抽象接口。
有了 Flexvolume,爲什麼還要 CSI 呢?<br /> <br />Flexvolume 只是給 kubernetes 這一個編排系統來使用的,而 CSI 能夠知足不一樣編排系統的需求,好比 Mesos,Swarm。
其次 CSI 是容器化部署,能夠減小環境依賴,加強安全性,豐富插件的功能。咱們知道,Flexvolume 是在 host 空間一個二進制文件,執行 Flexvolum 時至關於執行了本地的一個 shell 命令,這使得咱們在安裝 Flexvolume 的時候須要同時安裝某些依賴,而這些依賴可能會對客戶的應用產生一些影響。所以在安全性上、環境依賴上,就會有一個很差的影響。
同時對於豐富插件功能這一點,咱們在 Kubernetes 生態中實現 operator 的時候,常常會經過 RBAC 這種方式去調用 Kubernetes 的一些接口來實現某些功能,而這些功能必需要在容器內部實現,所以像 Flexvolume 這種環境,因爲它是 host 空間中的二進制程序,就無法實現這些功能。而 CSI 這種容器化部署的方式,能夠經過 RBAC 的方式來實現這些功能。
CSI 主要包含兩個部分:CSI Controller Server 與 CSI Node Server。
下圖給出了 CSI 接口通訊的描述。CSI Controller Server 和 External CSI SideCar 是經過 Unix Socket 來進行通訊的,CSI Node Server 和 Kubelet 也是經過 Unix Socket 來通訊,以後咱們會講一下 External CSI SiderCar 的具體概念。
下圖給出了 CSI 的接口。主要分爲三類:通用管控接口、節點管控接口、中心管控接口。
通用管控接口主要返回 CSI 的一些通用信息,像插件的名字、Driver 的身份信息、插件所提供的能力等;
節點管控接口的 NodeStageVolume 和 NodeUnstageVolume 就至關於 Flexvolume 中的 MountDevice 和 UnmountDevice。NodePublishVolume 和 NodeUnpublishVolume 就至關於 SetUp 和 TearDown 接口;
中心管控接口的 CreateVolume 和 DeleteVolume 就是咱們的 Provision 和 Delete 存儲卷的一個接口,ControllerPublishVolume 和 ControllerUnPublishVolume 則分別是 Attach 和 Detach 的接口。
CSI 是經過 CRD 的形式實現的,因此 CSI 引入了這麼幾個對象類型:VolumeAttachment、CSINode、CSIDriver 以及 CSI Controller Server 與 CSI Node Server 的一個實現。
在 CSI Controller Server 中,有傳統的相似 Kubernetes 中的 AD Controller 和 Volume Plugins,VolumeAttachment 對象就是由它們所建立的。
此外,還包含多個 External Plugin組件,每一個組件和 CSI Plugin 組合的時候會完成某種功能。好比:
CSI Node Server 中主要包含 Kubelet 組件,包括 VolumeManager 和 VolumePlugin,它們會去調用 CSI Plugin 去作 mount 和 unmount 操做;另一個組件 Driver Registrar 主要實現的是 CSI Plugin 註冊的功能。
以上就是 CSI 的整個拓撲結構,接下來咱們將分別介紹不一樣的對象和組件。
咱們將介紹 3 種對象:VolumeAttachment,CSIDriver,CSINode。
VolumeAttachment 描述一個 Volume 卷在一個 Pod 使用中掛載、卸載的相關信息。例如,對一個卷在某個節點上的掛載,咱們經過 VolumeAttachment 對該掛載進行跟蹤。AD Controller 建立一個 VolumeAttachment,而 External-attacher 則經過觀察該 VolumeAttachment,根據其狀態來進行掛載和卸載操做。
下圖就是一個 VolumeAttachment 的例子,其類別 (kind) 爲 VolumeAttachment,spec 中指定了 attacher 爲 ossplugin.csi.alibabacloud.com,即指定掛載是由誰操做的;指定了 nodeName 爲 cn-zhangjiakou.192.168.1.53,即該掛載是發生在哪一個節點上的;指定了 source 爲 persistentVolumeName 爲 oss-csi-pv,即指定了哪個數據捲進行掛載和卸載。
status 中 attached 指示了掛載的狀態,若是是 False, External-attacher 就會執行一個掛載操做。
第二個對象是 CSIDriver,它描述了集羣中所部署的 CSI Plugin 列表,須要管理員根據插件類型進行建立。
例以下圖中建立了一些 CSI Driver,經過 kuberctl get csidriver
咱們能夠看到集羣裏面建立的 3 種類型的 CSI Driver:一個是雲盤;一個是 NAS;一個是 OSS。
在 CSI Driver 中,咱們定義了它的名字,在 spec 中還定義了 attachRequired 和 podInfoOnMount 兩個標籤。
第三個對象是 CSINode,它是集羣中的節點信息,由 node-driver-registrar 在啓動時建立。它的做用是每個新的 CSI Plugin 註冊後,都會在 CSINode 列表裏添加一個 CSINode 信息。
例以下圖,定義了 CSINode 列表,每個 CSINode 都有一個具體的信息(左側的 YAML)。以 一 cn-zhangjiakou.192.168.1.49 爲例,它包含一個雲盤的 CSI Driver,還包含一個 NAS 的 CSI Driver。每一個 Driver 都有本身的 nodeID 和它的拓撲信息 topologyKeys。若是沒有拓撲信息,能夠將 topologyKeys 設置爲 "null"。也就是說,假若有一個有 10 個節點的集羣,咱們能夠只定義一部分節點擁有 CSINode。
Node-Driver-Registrar 主要實現了 CSI Plugin 註冊的一個機制。咱們來看一下下圖中的流程圖。
啓動 Node-Driver-Registrar,它首先會向 CSI-Plugin 發起一個接口調用 GetPluginInfo,這個接口會返回 CSI 所監聽的地址以及 CSI-Plugin 的一個 Driver name;
第 2 步,Node-Driver-Registrar 會監聽 GetInfo 和 NotifyRegistrationStatus 兩個接口;
第 3 步,會在 /var/lib/kuberlet/plugins_registry
這個目錄下啓動一個 Socket,生成一個 Socket 文件 ,例如:"diskplugin.csi.alibabacloud.com-reg.sock",此時 Kubelet 經過 Watcher 發現這個 Socket 後,它會經過該 Socket 向 Node-Driver-Registrar 的 GetInfo 接口進行調用。GetInfo 會把剛纔咱們所得到的的 CSI-Plugin 的信息返回給 Kubelet,該信息包含了 CSI-Plugin 的監聽地址以及它的 Driver name;
第 4 步,Kubelet 經過獲得的監聽地址對 CSI-Plugin 的 NodeGetInfo 接口進行調用;
第 5 步,調用成功以後,Kubelet 會去更新一些狀態信息,好比節點的 Annotations、Labels、status.allocatable 等信息,同時會建立一個 CSINode 對象;
第 6 步,經過對 Node-Driver-Registrar 的 NotifyRegistrationStatus 接口的調用告訴它咱們已經把 CSI-Plugin 註冊成功了。
經過以上 6 步就實現了 CSI Plugin 註冊機制。
External-Attacher 主要是經過 CSI Plugin 的接口來實現數據卷的掛載與卸載功能。它經過觀察 VolumeAttachment 對象來實現狀態的判斷。VolumeAttachment 對象則是經過 AD Controller 來調用 Volume Plugin 中的 CSI Attacher 來建立的。CSI Attacher 是一個 In-Tree 類,也就是說這部分是 Kubernetes 完成的。
當 VolumeAttachment 的狀態是 False 時,External-Attacher 就去調用底層的一個 Attach 功能;若指望值爲 False,就經過底層的 ControllerPublishVolume 接口實現 Detach 功能。同時,External-Attacher 也會同步一些 PV 的信息在裏面。
咱們如今來看一下塊存儲的部署狀況。
以前提到 CSI 的 Controller 分爲兩部分,一個是 Controller Server Pod,一個是 Node Server Pod。
咱們只須要部署一個 Controller Server,若是是多備份的,能夠部署兩個。Controller Server 主要是經過多個外部插件來實現的,好比說一個 Pod 中能夠定義多個 External 的 Container 和一個包含 CSI Controller Server 的 Container,這時候不一樣的 External 組件會和 Controller Server 組成不一樣的功能。
而 Node Server Pod 是個 DaemonSet,它會在每一個節點上進行註冊。Kubelet 會直接經過 Socket 的方式直接和 CSI Node Server 進行通訊、調用 Attach/Detach/Mount/Unmount 等。
Driver Registrar 只是作一個註冊的功能,會在每一個節點上進行部署。
文件存儲和塊存儲的部署狀況是相似的。只不過它會把 Attacher 去掉,也沒有 VolumeAttachment 對象。
和 Flexvolume 同樣,咱們看一下它的定義模板。
能夠看到,它和其它的定義並沒什麼區別。主要的區別在於類型爲 CSI,裏面會定義 driver,volumeHandle,volumeAttribute,nodeAffinity 等。
中間的圖給出了一個動態調度的例子,它和其它類型的動態調度是同樣的。只不過在定義 provisioner 的時候指定了一個 CSI 的 provisioner。
下面給出了一個具體的掛載例子。
Pod 啓動以後,咱們能夠看到 Pod 已經把一個 /dev/vdb 掛載到 /data 上了。同理,它有一個 GlobalPath 和一個 PodPath 的集羣在裏面。咱們能夠把一個 /dev/vdb 掛載到一個 GlobalPath 裏面,它就是一個 CSI 的一個 PV 在本節點上惟一肯定的目錄。一個 PodPath 就是一個 Pod 所肯定的一個本地節點的目錄,它會把 Pod 所對應的目錄映射到咱們的容器中去。
除了掛載、卸載以外,CSI 化提供了一些附加的功能。例如,在定義模板的時候每每須要一些用戶名和密碼信息,此時咱們就可經過 Secret 來進行定義。以前咱們所講的 Flexvolume 也支持這個功能,只不過 CSI 能夠根據不一樣的階段定義不一樣的 Secret 類型,好比掛載階段的 Secret、Mount 階段的 Secret、Provision 階段的 Secret。
**Topology **是一個拓撲感知的功能。當咱們定義一個數據卷的時候,集羣中並非全部節點都能知足該數據卷的需求,好比咱們須要掛載不一樣的 zone 的信息在裏面,這就是一個拓撲感知的功能。這部分在第 10 講已有詳細的介紹,你們能夠進行參考。
**Block Volume **就是 volumeMode 的一個定義,它能夠定義成 Block 類型,也能夠定義成文件系統類型,CSI 支持 Block 類型的 Volume,就是說掛載到 Pod 內部時,它是一個塊設備,而不是一個目錄。
**Skip Attach **和 PodInfo On Mount 是剛纔咱們所講過的 CSI Driver 中的兩個功能。
CSI 仍是一個比較新的實現方式。近期也有了不少更新,好比 ExpandCSIVolumes 能夠實現文件系統擴容的功能;VolumeSnapshotDataSource 能夠實現數據卷的快照功能;VolumePVCDataSource 實現的是能夠定義 PVC 的數據源;咱們之前在使用 CSI 的時候只能經過 PVC、PV 的方式定義,而不能直接在 Pod 裏面定義 Volume,CSIInlineVolume 則可讓咱們能夠直接在 Volume 中定義一些 CSI 的驅動。
阿里雲在 GitHub 上開源了 CSI 的實現,你們有興趣的能夠看一下,作一些參考。
本文主要介紹了 Kubernetes 集羣中存儲卷相關的知識,主要有如下三點內容:
但願上述知識點能讓各位同窗有所收穫,特別是在處理存儲卷相關的設計、開發、故障處理等方面有所幫助。
「阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術圈。」