Kubernetes系列學習文章 – 存儲實現(九)
- 2020 年 4 月 3 日
- 筆記
| 導語 數據,是無價的。了解清楚底層的存儲實現方式對於數據的使用、保護以及IO性能的優化有很大的幫助。本篇文章從三個大點來講講K8S的存儲實現機制。
一、存儲虛擬化介紹
在虛擬化領域有這麼一個故事:一個好的虛擬化解決方案就好像遊歷一個虛擬現實的主題公園。當遊客想像他正在城市上空滑翔時,感測器就會把相應的真實感覺傳遞給遊客,並同時隱藏真實的力學環境。這好比黑客帝國里,虛擬世界裡的人完全感受不到我是在虛擬的環境中。
基於上面的故事,存儲的虛擬化要解決的是上層的用戶對底層硬碟、磁帶完全不可知。屏蔽掉底層複雜的物理設備後,把零散的硬體存儲資源抽象成一個「存儲池」,這個「存儲池」能靈活的分配給用戶。

SNIA(存儲網路工業協會,非盈利行業組織)對存儲虛擬化的定義:通過對存儲(子)系統或存儲服務的內部功能進行抽象、隱藏或隔離,進而使應用、伺服器、網路等資源進行分離,從而實現這些資源的獨立管理。
目前這個定義是相對權威和中立的,裡面說的「抽象」可以理解為是對底層硬體存儲資源的共同屬性的整合,把所有存儲資源抽象成一個虛擬的存儲池;「隱藏」意思是對於上層用戶底層的硬體需要屏蔽掉,讓用戶不需要關心底層;「隔離」的意思是虛擬的存儲池資源需要根據上層用戶的需求劃分成小塊,塊與塊之間相互獨立相互不影響。
抽象->隱藏->隔離,這三步是存儲虛擬化機制要達到的最基本要求,實現了這三步能實現設備使用效率提升、實現統一數據管理、實現設備構件化、實現數據跨設備流動、提高可擴展性、降低數據管理難度等功能。
1. 存儲I/O虛擬化
I/O虛擬化是存儲虛擬化的基石,I/O虛擬化的優劣將直接影響整體存儲虛擬化方案的好壞。I/O的虛擬化跟CPU虛擬化一樣經歷過全虛擬、半虛擬、硬體輔助虛擬的過程。全虛擬化模式,全部處理請求要經過VMM,這就讓I/O路徑變得很長,造成的結果就是性能大大降低。所以,後面改進出現了半虛擬化I/O技術——virtio(Virtual I/O Device)。virtio是前後端架構,相關請求路徑減少了很多,但還是需要經過VMM和Kernel的,性能或多或少還是會損耗。到最後,英特爾推出了硬體輔助式的VT-D技術,這項技術極大的減少了VMM參與I/O請求操作,讓路徑變得最短,而且還提供了專門的記憶體空間處理I/O請求,性能得到了最大的提升。
2. 當前主流存儲虛擬化方式
目前存儲虛擬化方式主要有三種,分別是:基於主機的存儲虛擬化、基於網路的存儲虛擬化、基於存儲設備的存儲虛擬化。這三種存儲虛擬化方式各有優劣,我們可以用下面的表格來清晰的觀察:
名稱 |
定義 |
實現方式 |
優勢 |
劣勢 |
---|---|---|---|---|
基於主機 |
安裝在一個或多個主機上,實現存儲虛擬化的控制和管理。 |
一般是作業系統下的邏輯卷管理軟體實現,不同作業系統下,邏輯卷管理軟體也不同。 |
支援不同架構的存儲系統。 |
佔用主機資源、可擴充性較差、性能不是很好、存在作業系統和存儲管理應用之間兼容性問題、需要複雜的數據遷移過程、影響業務連續性。 |
基於網路 |
基於網路的虛擬化方法是在網路設備之間實現存儲虛擬化功能。 |
通過存儲網路(一般是SAN網路)中添加虛擬化引擎實現。 |
與主機無關,不佔用主機資源;支援不同主機、不同存儲設備;有統一的管理平台、擴展性好。 |
廠商之間不同存儲虛擬化產品性能和功能參差不齊,好的產品成本高、價格貴。 |
基於存儲設備 |
基於存儲設備的存儲虛擬化方法依賴於提供相關功能的存儲模組。 |
在存儲控制器上添加虛擬化功能。 |
與主機無關,不佔用主機資源;數據管理功能豐富。 |
不同廠商間的數據管理功能不相互兼容;多套存儲設備需要多套數據管理軟體,成本高。 |
3. 存儲虛擬化的價值
據統計,存儲的需求量年增長率達50%~60%。面對新的技術應用以及不斷增加的存儲容量,企業用戶很需要虛擬化技術來降低管理的複雜性,同時提高效率。據有關報告表明,過早的採用存儲虛擬化能在原來的存儲成本上降低至少50%的成本。但是,從目前企業對存儲掌握形式來看,很多企業還遇到很多問題,總結起來有三點:一是存儲數據的成本在不斷地增加;二是數據存儲容量爆炸性增長;三是越來越複雜的環境使得存儲的數據無法管理。存儲虛擬化首先要解決的難題就是不同架構平台的數據管理問題。數據管理沒有有效便捷的方法,那麼對數據的安全性會產生很大的威脅。當前雲時代背景下,大部分客戶已選擇上雲(公有雲or私有雲),這也是讓存儲走向虛擬化的一個表現。雲環境下,公有雲廠商有自己很強的存儲虛擬化技術體系來支撐用戶需求,私有雲下當前的ceph和glusterfs技術也很好的解決了這個問題。
二、Kubernetes存儲機制設計
前面有提到過,企業選擇虛擬化技術能大大的降低IT成本。K8S是容器資源調度管理平台,因此它也是虛擬化資源的管理平台。存儲虛擬化資源在K8S下是如何運作和管理的,接下來我們這裡可以加以講述。
在傳統虛擬機模式下,我們可以分配塊存儲、文件存儲掛載到VM裡面供給使用。同理在容器K8S模式下,Pod也需要存儲資源來實現數據的持久化。在K8S里,存儲資源是以數據卷volume的形式與Pod做綁定。底層通過塊存儲和分散式文件系統來提供數據卷volume,而塊存儲和分散式文件系統我們第一點提到過,這些需要更底層的存儲虛擬化機制來實現。

那麼K8S到底是通過什麼機制方式把底層塊存儲或分散式文件系統做成一份份的volume給到Pod使用呢?接下來我們通過PV、PVC、StorageClass等概念給大家說明。
1. PV
PV是 PersistentVolume 的縮寫,Persistent是持續的意思,因此PV可以解釋為「持久化的數據卷」。PV是對底層共享存儲資源的抽象,它能對接多種類型的存儲實現類型,主要有:Ceph、Glusterfs、NFS、vSphereVolume(VMWare存儲系統)、Local(本地存儲設備)、HostPath(宿主機目錄)、iSCSI、FC(光纖存儲設備)、AWSElasticBlockStore(AWS彈性塊設備)、AzureDisk(微軟雲Disk)、AzureFile(微軟雲File)、GCEPersistentDisk(Google雲永久磁碟)。
PV代表著K8S的存儲能力、訪問模式、存儲類型、回收策略、後端存儲類型等機制體現。定義一個PV,我們可以參考下面:
apiVersion: v1 kind: PersistentVolume metadata: name: pv0003 //定義PV的名字 spec: capacity: storage: 5Gi //定義了PV為5GB volumeMode: Filesystem //定義volume模式類型 accessModes: - ReadWriteOnce //定義讀寫許可權, 並且只能被單個node掛載 persistentVolumeReclaimPolicy: Recycle //定義回收策略 storageClassName: slow //定義storageClass名字 mountOptions: - hard //定義NFS掛載模式,硬掛載 - nfsvers=4.1 //定義NFS的版本號 nfs: path: /tmp //定義NFS掛載目錄 server: 172.17.0.2 //定義NFS服務地址
同樣的,當我們第一次寫PV的YAML可能不清楚到底有哪些關鍵參數,因此下面整理了PV的關鍵配置參數,讀者可以參考:
類型 |
參數名 |
說明 |
---|---|---|
存儲能力 |
Capacity |
描述存儲設備具備的能力,目前僅支援對存儲空間的設置 (storage=xx),未來可能加入IOPS、吞吐率等指標的設置。 |
存儲卷模式 |
Volume Mode |
Kubernetes從1.13版本開始引入存儲卷類型的設置 (volumeMode=xxx),可選項包括Filesystem(文件系統)和Block(塊 設備),默認值為Filesystem。 |
訪問模式 |
Access Modes |
對PV進行訪問模式的設置,用於描述用戶的應用對存儲資源的訪 問許可權。 ReadWriteOnce(RWO):讀寫許可權,並且只能被單個Node掛 載。 ReadOnlyMany(ROX):只讀許可權,允許被多個Node掛載。 ReadWriteMany(RWX):讀寫許可權,允許被多個Node掛載。 這裡要注意,不同的存儲類型,訪問模式是不同的,具體可以參考下面的表 |
存儲類別 |
Class |
PV可以設定其存儲的類別,通過storageClassName參數指定一個 StorageClass資源對象的名稱(StorageClass會預先創建好,指定一個名字)。具有特定類別的PV只能與請求了該類別的PVC進行綁定。未設定類別的PV則只能與不請求任何類別的PVC進行綁定。 |
回收策略 |
Reclaim Policy |
通過PV定義中的persistentVolumeReclaimPolicy欄位進行設置,可 選項如下: Retain 保留:保留數據,需要手工處理。 Recycle 回收空間:簡單清除文件的操作(例如執行rm -rf /thevolume/* 命令)。 Delete 刪除:與PV相連的後端存儲完成Volume的刪除操作。 目前,只有NFS和HostPath兩種類型的存儲支援Recycle策略;AWS EBS、GCE PD、Azure Disk和Cinder volumes支援Delete策略。 |
掛載參數 |
Mount Options |
在將PV掛載到一個Node上時,根據後端存儲的特點,可能需要設 置額外的掛載參數,可以根據PV定義中的mountOptions欄位進行設置。 |
節點親和性 |
Node Affinity |
可以設置節點親和性來限制只能讓某些Node訪問Volume,可 以在PV定義中的nodeAffinity欄位進行設置。使用這些Volume的Pod將 被調度到滿足條件的Node上。 |
每種存儲類型的訪問模式是否支援是不同的,不支援的需要看「 depends on the driver」 意思是取決具體的驅動能力。

另外,PV的生命周期也是需要關注,這個在問題排障查看相關PV那時的狀態非常有用。
狀態名 |
描述 |
---|---|
Available |
可用狀態,還未與某個PVC綁定。 |
Bound |
已與某個PVC綁定。 |
Released |
綁定的PVC已經刪除,資源已釋放,但沒有被集群 回收。 |
Failed |
自動資源回收失敗。 |
2. PVC
PVC是Persistent Volume Claim 的縮寫,多了一個Claim(聲明、索要、申請)。PVC實則是在PV基礎上的資源申請。那麼有了PV為何還要用PVC呢?因為PV一般是由運維人員設定和維護,PVC則是由上層K8S用戶根據存儲需求向PV側申請,你可以聯想下Linux下的LVM,K8S里的PV好比LVM的物理卷(PV),K8S里的PVC好比LVM里的邏輯卷(LV)。


PVC的申請也是配置YAML,舉例如下:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim //設置PVC的名字 spec: accessModes: - ReadWriteOnce //設置訪問模式 volumeMode: Filesystem //定義volume模式類型 resources: requests: storage: 8Gi //設置PVC的空間大小 storageClassName: slow //設置存儲類別為slow,注意PVC這裡一般跟storageClass結合 selector: matchLabels: release: "stable" //匹配標籤stable matchExpressions: - {key: environment, operator: In, values: [dev]} //同時包含environment環境有[dev]的標籤
PVC是用戶對存儲空間請求、訪問模式、PV選擇、存儲類別等資訊設定的機制,它的YAML關鍵參數如下:
類型 |
參數名 |
說明 |
---|---|---|
資源申請 |
Resources |
描述對存儲資源的請求,申請多大的資源空間大小,目前僅支援request.storage的設置。 |
訪問模式 |
Access Modes |
設置訪問模式,用於設置app對存儲資源的訪問許可權:ReadWriteOnce(RWO):讀寫許可權 ReadOnlyMany(ROX):只讀許可權 ReadWriteMany(RWX):讀寫許可權 |
存儲卷模式 |
Volume Modes |
PVC也可以設置存儲卷模式,用於描述希望使用的PV存儲卷模式,兩種選擇:文件系統Filesystem和塊設備Block。 |
PV選擇條件 |
Selector |
通過對Label Selector的設置,可使PVC對於系統中已存在的各種PV進行過濾篩選。目前有兩種方式:matchLabels(該卷必須帶有此值的標籤);matchExpressions(通過指定key,值列表以及與鍵和值的運算符得出的要求列表。 運算符包括In,NotIn,Exists和DidNotExist) |
存儲類別 |
Class |
通過設置storageClassName指定StorageClass的名稱來請求特定的StorageClass。 只能將所請求類的PV(具有與PVC相同的storageClassName的PV)綁定到PVC。 |
未啟用DefaultStorageClass |
storageClassName |
等效於PVC設置storageClassName 的值為空(storageClassName=""),即只能選擇未設定Class的PV與之 匹配和綁定。 |
啟用DefaultStorageClass |
DefaultStorageClass |
要求集群管理員已定義默認的StorageClass。如果在系統中不存在默認的StorageClass,則等效於不啟用DefaultStorageClass的情況。如果存在默認的StorageClass,則系統將自動為PVC創建一個PV(使用默認StorageClass的後端存儲),並將它們進行綁定。集群管理員設置默認StorageClass的方法為,在StorageClass的定義中加上一個annotation「storageclass.kubernetes.io/is-default-class= true」。如果管理員將多個StorageClass都定義為default,則由於不唯一,系統將無法為PVC創建相應的PV。 |
PVC目前一般會跟StorageClass一起結合使用,關於StorageClass的講解,具體請看接下來的第3點。
3. StorageClass
在K8S里,存儲資源的供給分為兩種模式,一種是靜態模式,另外一種是動態模式。靜態模式是K8S集群管理員(一般是運維人員)手工創建和設置好後端存儲特性的PV,然後PVC再申請使用。動態模式是集群管理員無需手工創建和設置好PV,這裡集群管理將定義好不同類型的StorageClass,StorageClass會早早的與後端存儲類型做好關聯;待用戶創建PVC後,系統將自動完成PV的創建還有PVC的綁定。在自動化成熟趨勢發展的今天,基本上手動不靈活的方式都會被替代捨棄,靜態模式在集群規模小、架構簡單的測試環境下可以採用,但是面對規模宏大、架構複雜的環境,就需要動態的管理方式。
下面是定義一個StorageClass的YAML文件:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard //定義StorageClass的名字為standard provisioner: kubernetes.io/aws-ebs //定義後端存儲驅動,這裡用的是AWS的ebs盤 parameters: type: gp2 //aws的磁碟有5種類型,gp2是其中一種,這裡定義的類型是gp2 reclaimPolicy: Retain //設置PV的數據回收策略,默認DELETE,這裡設置的是Retain(保留,K8S 1.8版以後才支援) allowVolumeExpansion: true //設置只有從StorageClass中創建的PVC才允許使用卷擴展 mountOptions: - debug volumeBindingMode: Immediate //設置何時應進行卷綁定和動態預配置,Immediate(立即的)
集群管理員預先定義好StorageClass,後面創建PVC的時候,有符合條件的StorageClass將自動創建好PV與PVC綁定使用。
StorageClass的YAML關鍵參數關鍵有兩大塊,一個是Provisioner,另外一個是Parameters。
Provisioner:K8S底層存儲資源的提供者,其實就是後端存儲的類型;這裡定義的時候需要以kubernetes.io開頭,後面接上存儲類型。
Parameters:後端存儲的類型的具體參數設置,這裡不同的存儲類型參數也不一樣。
StorageClass YAML文件Parameters參數那塊定義比較複雜,想清楚了解具體怎麼設置,比如Glusterfs、Ceph RBD、Openstack Cinder的StorageClass參數是怎樣的,可以參考官網文檔的YAML例子:https://kubernetes.io/docs/concepts/storage/storage-classes/
4. PV、PVC、StorageClass之間關係
前面我們詳細了解了PV、PVC、StorageClass的相關資訊,那麼它們三者之間的關係我們通過幾張圖從高維度來整體認識下。
首先是它們之間相互依賴的關係,我們可以通過下面的一張圖很清晰的了解它們的關係。

其次是它們總體的架構層次關係,我們可以看下面的圖來理解:

最後是它們的生命周期,這個也需要了解:

K8S里只要掌握了StorageClass、PVC、PV的定義和設置,搞清楚它們之間的內在關係和生命周期,那麼K8S存儲這塊基本就了解了。當然,關於這塊的一些性能上的優化,具體還得看底層存儲的能力。當前雲環境下,底層存儲各具特色,各大雲廠商有自己的實現機制和性能特色;私有雲下,在完善底層硬體性能的同時,通常IAAS層都會採用ceph來做分散式存儲,進而再給PAAS層使用。
三、Kubernetes CSI
前面一章我們在學習K8S網路的時候有了解過CNI,那麼在存儲這一塊,K8S也有一套介面管理規範機制,那就是CSI。CSI是「Container Storage Interface」 的縮寫,中文意思就是「容器存儲介面」。CNI是為了統一網路規範介面的,CSI自然是想統一標準化存儲方面的介面,方便管理。
1. 為什麼要發展CSI
CSI v1.0版本是在2017年12月發布的。在沒有CSI之前,K8S已經提供了功能強大的卷插件機制(In-tree Volume Plugin 和 Out-of-tree Provisioner),但由於In-tree的這種模式程式碼主要是K8S維護,這意味著插件的程式碼是Kubernetes核心程式碼的一部分,並會隨核心Kubernetes二進位文件一起發布。 此外,第三方插件程式碼在核心Kubernetes二進位文件中可能會引起可靠性和安全性問題,由於程式碼的審核和測試又是K8S側人員做的,對於第三方插件程式碼因為不熟等原因,有些錯誤是很難發現的。

因此,CSI的出現非常有必要。CSI的設計目的是定義一個行業標準,該標準將使第三方存儲提供商能夠自己實現、維護和部署他們的存儲插件。藉助CSI,第三方存儲提供商而無需接觸K8S的核心程式碼。這為K8S用戶提供了更多的存儲選項,並使系統更加安全可靠。
2. CSI 架構
CSI目前包括三部分:Identity、Controller、Node
- CSI Identity的主要功能是負責認證插件的狀態資訊。
- CSI Controller的主要功能是對存儲資源和存儲卷進行管理和操作。
- CSI Node的主要功能是對Node上的Volume進行管理和操作。

3. CSI新特性
CSI目前已經GA,目前CSI有如下幾點功能的改進:
- Kubernetes當前與CSI的規範v1.0和v0.3版本兼容(取代CSI v0.2)。 CSI v0.3.0跟v1.0.0之間有重大的變化,Kubernetes v1.13同時支援這兩個版本; 隨著CSI 1.0 API的發布,K8S不在支援低於和等於v0.3的CSI API的驅動程式,K8S v1.15後會徹底不支援; CSI v0.2和v0.3之間沒有重大變化,因此v0.2驅動程式可以跟K8S v1.10.0+ 集合使用; CSI v0.1和v0.2之間有重大變化,因此,在使用K8S v1.10.0+ 之前的版本,必須將非常老的CSI 0.1的驅動程式更新為至少兼容0.2。
- Kubernetes VolumeAttachment 對象(在storage v1alpha1 group v1.9中引入,並已添加到v1beta1 group v1.10)已添加到storage v1 group v1.13中。
- Kubernetes CSIPersistentVolumeSource 卷類型已提升為GA。
- kubelet發現新的CSI驅動程式方法:「Kubelet設備插件註冊機制」,已在Kubernetes v1.13中GA。
總結
本篇文章從基本的存儲虛擬化概念講起,依次講述了K8S的存儲實現方式和CSI存儲介面規範。存儲跟數據關聯,數據是公司最貴的資產。容器雲時代跟之前傳統的IT時代一樣,數據存儲的重要性觀念一直沒有變。掌握好K8S的存儲機制原理將會很好的為雲原生存儲學習打下很好的基礎。