您好,登錄后才能下訂單哦!
作者 | 闞俊寶? 阿里巴巴高級技術專家
本文整理自《CNCF x Alibaba 云原生技術公開課》第 21 講。<br />關注“阿里巴巴云原生”公眾號,回復關鍵詞“入門”,即可下載從零入門 K8s 系列文章 PPT。
導讀:容器存儲是 Kubernetes 系統中提供數據持久化的基礎組件,是實現有狀態服務的重要保證。Kubernetes 默認提供了主流的存儲卷接入方案(In-Tree),同時也提供了插件機制(Out-Of-Tree),允許其他類型的存儲服務接入 Kubernetes 系統服務。本文將從 Kubernetes 存儲架構、存儲插件原理、實現等方面進行講解,希望大家有所收獲。
首先以一個 Volume 的掛載例子來作為引入。
如下圖所示,左邊的 YAML 模板定義了一個 StatefulSet 的一個應用,其中定義了一個名為 disk-pvc 的 volume,掛載到 Pod 內部的目錄是 /data。disk-pvc 是一個 PVC 類型的數據卷,其中定義了一個 storageClassName。
因此這個模板是一個典型的動態存儲的模板。右圖是數據卷掛載的過程,主要分為 6 步:
第一步:用戶創建一個包含 PVC的 Pod;
PV Controller 首先會在集群內部找到一個適合的 PV 進行綁定,如果未找到相應的 PV,就調用 Volume Plugin 去做 Provision。Provision 就是從遠端上一個具體的存儲介質創建一個 Volume,并且在集群中創建一個 PV 對象,然后將此 PV 和 PVC 進行綁定;
我們知道,當一個 Pod 運行的時候,需要選擇一個 Node,這個節點的選擇就是由 Scheduler 來完成的。Scheduler 進行調度的時候會有多個參考量,比如 Pod 內部所定義的 nodeSelector、nodeAffinity 這些定義以及 Volume 中所定義的一些標簽等。
我們可以在數據卷中添加一些標簽,這樣使用這個 pv 的 Pod 就會由于標簽的限制,被調度器調度到期望的節點上。<br />?
第四步:如果有一個 Pod 調度到某個節點之后,它所定義的 PV 還沒有被掛載(Attach),此時 AD Controller 就會調用?VolumePlugin,把遠端的 Volume 掛載到目標節點中的設備上(如:/dev/vdb);
第五步:當?Volum Manager 發現一個 Pod 調度到自己的節點上并且 Volume 已經完成了掛載,它就會執行?mount 操作,將本地設備(也就是剛才得到的 /dev/vdb)掛載到 Pod 在節點上的一個子目錄中。同時它也可能會做一些像格式化、是否掛載到 GlobalPath 等這樣的附加操作。
接下來,我們一起看一下 Kubernetes 的存儲架構。
PV Controller: 負責 PV/PVC 的綁定、生命周期管理,并根據需求進行數據卷的 Provision/Delete 操作;
AD Controller:負責存儲設備的 Attach/Detach 操作,將設備掛載到目標節點;
Volume Manager:管理卷的 Mount/Unmount 操作、卷設備的格式化以及掛載到一些公用目錄上的操作;
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 找不到相應的 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 中;
下面這個表分別給出了 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 發起調用;
?<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-8vb4fflsonz21h41cmss,在執行完 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 接口;
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 步就實現了 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 集群中存儲卷相關的知識,主要有以下三點內容:
希望上述知識點能讓各位同學有所收獲,特別是在處理存儲卷相關的設計、開發、故障處理等方面有所幫助。
Kubernetes SIG-Cloud-Provider-Alibaba 介紹
2020 年 2 月 12 日 10:00(時區:北京)
中文
SIG Cloud Provider 是 Kubernetes 的重要興趣小組,致力于推動所有云廠商以標準的能力提供 Kubernetes 服務。SIG-Cloud-Provider-Alibaba 是 SIG Cloud Provider 在國內唯一的子項目。
本次研討會將首次完整介紹阿里云對 Kubernetes 社區的布局。在產品層面,阿里云提供了完整的容器產品家族;在開源領域,阿里云也圍繞 Kubernetes 提供了十個類別,20 多個開源項目,提供了完整的 Kubernetes 生命周期管理。阿里云會依托 SIG-Cloud-Provider-Alibaba,尋求和開發者更密切的互動,也號召更多的開發者一起貢獻。
點擊注冊報名參會:<br />https://zoom.com.cn/webinar/register/8015799062779/WN_dIrSRs1zQ-uXNXmuAThuog<br />
阿里云 - 云原生應用平臺 - 基礎軟件中臺團隊(原容器平臺基礎軟件團隊)誠邀 Kubernetes/容器/ Serverless/應用交付技術領域專家( P6-P8 )加盟。
工作年限:建議 P6-7 三年起,P8 五年起,具體看實際能力。<br />工作地點:
簡歷立刻回復,2~3 周出結果。節后入職。
基礎產品事業部是阿里云智能事業群的核心研發部門,負責計算、存儲、網絡、安全、中間件、系統軟件等研發。而云原生應用平臺基礎軟件終態團隊致力于打造穩定、標準、先進的云原生應用系統平臺,推動行業面向云原生技術升級與革命。
在這里,既有 CNCF TOC 和 SIG 聯席主席,也有 etcd 創始人、K8s Operator 創始人與 Kubernetes 核心維護成員組成的、國內最頂尖的 Kubernetes 技術團隊。
在這里,你將同來自全球的云原生技術領域專家們(如 Helm 項目的創始人、Istio 項目的創始人)密切合作,在獨一無二的場景與規模中從事 Kubernetes、Service Mesh、Serverless、Open Application Model ( OAM )等云計算生態核心技術的研發與落地工作,在業界標桿級的平臺上,既賦能阿里巴巴全球經濟體,更服務全世界的開發者用戶。
技術要求:Go/Rust/Java/C++,Linux,分布式系統
lei.zhang AT alibaba-inc.com
“阿里巴巴云原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦云原生流行技術趨勢、云原生大規模的落地實踐,做最懂云原生開發者的技術圈。”
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。