k8s之應用存儲和持久化數據卷:核心知識

本次課程的分享主要圍繞以下三個部分:

  1. K8s Volume 使用場景
  2. PVC/PV/StorageClass 基本操作和概念解析
  3. PVC+PV 體系的設計與實現原理

Volumes 介紹#

Pod Volumes#

首先來看一下 Pod Volumes 的使用場景:

  • 場景一:如果 pod 中的某一個容器在運行時異常退出,被 kubelet 重新拉起之後,如何保證之前容器產生的重要數據沒有丟失?
  • 場景二:如果同一個 pod 中的多個容器想要共享數據,應該如何去做?

以上兩個場景,其實都可以藉助 Volumes 來很好地解決,接下來首先看一下 Pod Volumes 的常見類型:

  1. 本地存儲,常用的有 emptydir/hostpath;
  2. 網絡存儲:網絡存儲當前的實現方式有兩種,一種是 in-tree,它的實現的代碼是放在 K8s 代碼倉庫中的,隨著k8s對存儲類型支持的增多,這種方式會給k8s本身的維護和發展帶來很大的負擔;而第二種實現方式是 out-of-tree,它的實現其實是給 K8s 本身解耦的,通過抽象接口將不同存儲的driver實現從k8s代碼倉庫中剝離,因此out-of-tree 是後面社區主推的一種實現網絡存儲插件的方式;
  3. Projected Volumes:它其實是將一些配置信息,如 secret/configmap 用卷的形式掛載在容器中,讓容器中的程序可以通過POSIX接口來訪問配置數據;
  4. PV 與 PVC 就是今天要重點介紹的內容。

Persistent Volumes#

Pod中聲明的volume的生命週期與Pod相同,以下常見場景:

  1. Pod銷燬重建(Deployment管理的Pod鏡像升級)
  2. 宿主機故障遷移(如StatefulSet管理的 Pod帶遠程volume遷移)
  3. 多Pod共享同一個數據volume
  4. 數據volume snapshot,resize等功能的擴展實現

不足之處:使用pod volumes無法準確表達數據volume複用/共享語義,新功能擴展很難實現。

優化:如果能將存儲與計算分離,使用不同的組件(Controllers)管理存儲和計算資源,解耦Pod與Volume的聲明週期關聯,可以很好的解決這些場景下的問題。

接下來看一下 PV(Persistent Volumes)。既然已經有了 Pod Volumes,為什麼又要引入 PV 呢?我們知道 pod 中聲明的 volume 生命週期與 pod 是相同的,以下有幾種常見的場景:

  • 場景一:pod 重建銷燬,如用 Deployment 管理的 pod,在做鏡像升級的過程中,會產生新的 pod並且刪除舊的 pod ,那新舊 pod 之間如何複用數據?
  • 場景二:宿主機宕機的時候,要把上面的 pod 遷移,這個時候 StatefulSet 管理的 pod,其實已經實現了帶卷遷移的語義。這時通過 Pod Volumes 顯然是做不到的;
  • 場景三:多個 pod 之間,如果想要共享數據,應該如何去聲明呢?我們知道,同一個 pod 中多個容器想共享數據,可以藉助 Pod Volumes 來解決;當多個 pod 想共享數據時,Pod Volumes 就很難去表達這種語義;
  • 場景四:如果要想對數據卷做一些功能擴展性,如:snapshot、resize 這些功能,又應該如何去做呢?

以上場景中,通過 Pod Volumes 很難準確地表達它的複用/共享語義,對它的擴展也比較困難。因此 K8s 中又引入了 **Persistent Volumes **概念,它可以將存儲和計算分離,通過不同的組件來管理存儲資源和計算資源,然後解耦 pod 和 Volume 之間生命週期的關聯。這樣,當把 pod 刪除之後,它使用的PV仍然存在,還可以被新建的 pod 複用。

PVC 設計意圖#

有了PV,為什麼有設計了PVC?

  1. 職責分離,PVC中只需用自己需要的存儲size、access mode(單node獨佔還是多node共享?只讀還是讀寫訪問?)等業務真正關心的存儲需求(不用關心存儲實現細節),PV和其後端的存儲信息則由交給cluster admin統一運維和管控,安全訪問策略更容易控制。
  2. PVC簡化了User對於存儲的需求,PV才是存儲的實際信息的承載體,通過kube-controller-manager中的Persisent中的PersisentVolumeController將PVC與合適的PV bound到一起,從而滿足User對存儲的實際需求。
  3. PVC像是面向對象編程中抽象出來的接口,PV是接口對應的實現。

瞭解 PV 後,應該如何使用它呢?

用戶在使用 PV 時其實是通過 PVC,為什麼有了 PV 又設計了 PVC 呢?主要原因是為了簡化K8s用戶對存儲的使用方式,做到職責分離。通常用戶在使用存儲的時候,只用聲明所需的存儲大小以及訪問模式。

訪問模式是什麼?其實就是:我要使用的存儲是可以被多個node共享還是隻能單node獨佔訪問(注意是node level而不是pod level)?只讀還是讀寫訪問?用戶只用關心這些東西,與存儲相關的實現細節是不需要關心的。

通過 PVC 和 PV 的概念,將用戶需求和實現細節解耦開,用戶只用通過 PVC 聲明自己的存儲需求。PV是有集群管理員和存儲相關團隊來統一運維和管控,這樣的話,就簡化了用戶使用存儲的方式。可以看到,PV 和 PVC 的設計其實有點像面向對象的接口與實現的關係。用戶在使用功能時,只需關心用戶接口,不需關心它內部複雜的實現細節。

既然 PV 是由集群管理員統一管控的,接下來就看一下 PV 這個對象是怎麼產生的。

PV和PVC的生命週期#

1.資源供應 (Provisioning)Kubernetes支持兩種資源的供應模式:靜態模式(Staic)和動態模式(Dynamic)。資源供應的結果就是創建好的PV。

  • 靜態模式:集群管理員手工創建許多PV,在定義PV時需要將後端存儲的特性進行設置
  • 動態模式:集群管理員無須手工創建PV,而是通過StorageClass的設置對後端存儲進行描述,標記為某種 "類型(Class)"。此時要求PVC對存儲的類型進行聲明,系統將自動完成PV的創建及PVC的綁定。PVC可以聲明Class為"",說明該PVC禁止使用動態模式

2.資源綁定 (Binding)在用戶定義好PVC後,系統將根據PVC對存儲資源的請求 (存儲空間和訪問模式)在已存在的PV中選擇一個滿足PVC要求的PV,一旦找到,就將該PV與用戶定義的PVC進行綁定,然後用戶的應用就可以使用這個PVC了。如果系統中沒有滿足PVC要求的PV,PVC則會無限期處於Pending狀態,直到等到系統管理員創建了一個符合要求的PV。PV一旦綁定在某個PVC上,就被這個PVC獨佔,不能再與其他PVC進行綁定了。在這種情況下,當PVC申請的存儲空間比PV的少時,整個PV的空間都能夠為PVC所用,可能會造成資源的浪費。如果資源供應使用的是動態模式,則系統在PVC找到合適的StorageClass後,將會自動創建PV並完成PVC的綁定

3.資源使用 (Using)Pod 使用volume的定義,將PVC掛載到容器內的某個路徑進行使用。volume的類型為persistentVoulumeClaim,在容器應用掛載了一個PVC後,就能被持續獨佔使用。不過,多個Pod可以掛載同一個PVC,應用程序需要考慮多個實例共同訪問一塊存儲空間的問題

4.資源釋放 (Releasing)當用戶對存儲資源使用哪個完畢後,用戶可以刪除PVC,與該PVC綁定的PV將會被標記為已釋放,但還不能立刻與其他PVC進行綁定。通過之前PVC寫入的數據可能還留在存儲設備上,只有在清除之後該PV才能繼續使用

5.資源回收 (Reclaiming)對於PV,管理員可以設定回收策略(Reclaim Policy)用於設置與之綁定的PVC釋放資源之後,對於遺留數據如何處理。只有PV的存儲空間完成回收,才能供新的PVC綁定和使用。

Static Volume Provisioning#

第一種產生方式:靜態產生方式 - 靜態 Provisioning。

k8s之應用存儲和持久化數據卷:核心知識

靜態 Provisioning:由集群管理員事先去規劃這個集群中的用戶會怎樣使用存儲,它會先預分配一些存儲,也就是預先創建一些 PV;然後用戶在提交自己的存儲需求(也就是 PVC)的時候,K8s 內部相關組件會幫助它把 PVC 和 PV 做綁定;之後用戶再通過 pod 去使用存儲的時候,就可以通過 PVC 找到相應的 PV,它就可以使用了。

靜態產生方式有什麼不足呢?可以看到,首先需要集群管理員預分配,預分配其實是很難預測用戶真實需求的。舉一個最簡單的例子:如果用戶需要的是 20G,然而集群管理員在分配的時候可能有 80G 、100G 的,但沒有 20G 的,這樣就很難滿足用戶的真實需求,也會造成資源浪費。有沒有更好的方式呢?

Dynamic Volume Provisioning#

第二種訪問方式:動態 Dynamic Provisioning。

k8s之應用存儲和持久化數據卷:核心知識

動態供給是什麼意思呢?就是說現在集群管理員不預分配 PV,他寫了一個模板文件,這個模板文件是用來表示創建某一類型存儲(塊存儲,文件存儲等)所需的一些參數,這些參數是用戶不關心的,給存儲本身實現有關的參數。用戶只需要提交自身的存儲需求,也就是PVC文件,並在 PVC 中指定使用的存儲模板(StorageClass)。

K8s 集群中的管控組件,會結合 PVC 和 StorageClass 的信息動態,生成用戶所需要的存儲(PV),將 PVC 和 PV 進行綁定後,pod 就可以使用 PV 了。通過 StorageClass 配置生成存儲所需要的存儲模板,再結合用戶的需求動態創建 PV 對象,做到按需分配,在沒有增加用戶使用難度的同時也解放了集群管理員的運維工作。

用例解讀#

接下來看一下 Pod Volumes、PV、PVC 及 StorageClass 具體是如何使用的。

Pod Volumes 的使用#

<code>

CopyapiVersion:

v1

kind:

Pod

metadata:

name:

test-pod

spec:

containers:

-

name:

container-1

iamge: ubuntu:

18.04

volumeMount:

-

name:

cache-volume

mountPath:

/cache

subPath:

cache1

-

name:

hostpath-volume

mountPath:

/data

readOnly:

true

-

name:

container-2

image:

ubuntu:18.04

volumeMounts:

-

mountPath:

/cache

name:

cache-volume

subPath:

cache2

volumes:

-

name:

cache-volume

emptyDir:

{}

-

name:

hostpath-volume

hostPath:

path:

/tmp/data/

/<code>

主要參數:

  • .spec.volumes聲明pod的volumes信息
  • .spec.containers.volumesMounts聲明container如何使用pod的volumes
  • 多個container共享同一個volume是,可以通過.spec.containers.volumesMounts.subPath隔離不同容器在同個volume上數據存儲的路徑

首先來看一下 Pod Volumes 的使用。如上圖左側所示,我們可以在 pod yaml 文件中的 Volumes 字段中,聲明我們卷的名字以及卷的類型。聲明的兩個卷,一個是用的是 emptyDir,另外一個用的是 hostPath,這兩種都是本地卷。在容器中應該怎麼去使用這個卷呢?它其實可以通過 volumeMounts 這個字段,volumeMounts 字段裡面指定的 name 其實就是它使用的哪個卷,mountPath 就是容器中的掛載路徑。

這裡還有個 subPath,subPath 是什麼?

先看一下,這兩個容器都指定使用了同一個卷,就是這個 cache-volume。那麼,在多個容器共享同一個卷的時候,為了隔離數據,我們可以通過 subPath 來完成這個操作。它會在卷裡面建立兩個子目錄,然後容器 1 往 cache 下面寫的數據其實都寫在子目錄 cache1 了,容器 2 往 cache 寫的目錄,其數據最終會落在這個卷裡子目錄下面的 cache2 下。

還有一個 readOnly 字段,readOnly 的意思其實就是隻讀掛載,這個掛載你往掛載點下面實際上是沒有辦法去寫數據的。

另外emptyDir、hostPath 都是本地存儲,它們之間有什麼細微的差別呢?emptyDir 其實是在 pod 創建的過程中會臨時創建的一個目錄,這個目錄隨著 pod 刪除也會被刪除,裡面的數據會被清空掉;hostPath 顧名思義,其實就是宿主機上的一個路徑,在 pod 刪除之後,這個目錄還是存在的,它的數據也不會被丟失。這就是它們兩者之間一個細微的差別。

靜態 PV 使用#

以使用阿里雲存儲(NAS)為例:

Cluster Admin:

  1. 通過阿里雲文件存儲控制檯,創建NAS文件系統和掛載點。
  2. 創建PV對象,將NAS文件系統大小,掛載點, 以及PV的access mode,reclaim policy等信息添加到PV對象中。

User:

  1. 創建PVC對象,聲明存儲需求。
  2. 創建應用Pod並通過.spec.volumes中通過PVC聲明volume,通過.spec.containers.columeMounts聲明container掛載使用該volume。

接下來再看一下,PV 和 PVC 是怎麼使用的。

先看一個靜態 PV 創建方式。靜態 PV 的話,首先是由管理員來創建的,管理員我們這裡以 NAS,就是阿里雲文件存儲為例。我需要先在阿里雲的文件存儲控制檯上去創建 NAS 存儲,然後把 NAS 存儲的相關信息要填到 PV 對象中,這個 PV 對象預創建出來後,用戶可以通過 PVC 來聲明自己的存儲需求,然後再去創建 pod。創建 pod 還是通過我們剛才講解的字段把存儲掛載到某一個容器中的某一個掛載點下面。

那麼接下來看一下 yaml 怎麼寫。集群管理員首先是在雲存儲廠商那邊先去把存儲創建出來,然後把相應的信息填寫到 PV 對象中。

系統管理員預先創建PV

<code>

CopyapiVersion:

v1

kind:

PersistentVolume

metadata:

name:

nas-csi-pv

spec:

capacity:

storage:

5Gi

accessModes:

-

ReadWriteMay

persistentVolumeReclaimPolicy:

Retain

csi:

driver:

nasplugin.csi.alibabacloud.com

volumeHandle:

data-id

volumeAttributes:

host:

"***.cn-beijing.nas.aliyuncs.com"

path:

"/k8s"

vers:

"4.0"

/<code>

剛剛創建的阿里雲 NAS 文件存儲對應的PV,有個比較重要的字段:capacity,即創建的這個存儲的大小,accessModes,創建出來的這個存儲它的訪問方式,我們後面會講解總共有幾種訪問方式。

然後有個 ReclaimPolicy,ReclaimPolicy 的意思就是:這塊存儲在被使用後,等它的使用方 pod 以及 PVC 被刪除之後,這個 PV 是應該被刪掉還是被保留呢?其實就是PV的回收策略。

接下來看看用戶怎麼去使用該PV對象。用戶在使用存儲的時候,需要先創建一個 PVC 對象。PVC 對象裡面,只需要指定存儲需求,不用關心存儲本身的具體實現細節。存儲需求包括哪些呢?首先是需要的大小,也就是
resources.requests.storage;然後是它的訪問方式,即需要這個存儲的訪問方式,這裡聲明為ReadWriteMany,也即支持多node讀寫訪問,這也是文件存儲的典型特性。

用戶創建PVC

nas-pvc與nas-csi-pv匹配將
PersistentVolumeController將兩者bound到一起

<code>

CopyapiVersion

: v1

kind

: PersistentVolumeClaim

metadata

:

name

: nas-pvc

spec

:

accessModes

: - ReadWriteMany

resources

:

request

:

storage

:

5

Gi /<code>

用戶創建Pod

<code>

Copyspec:

containers:

-

name:

image:

nginx

ports:

-

containersPort:

80

volumeMounts:

-

name:

nas-pvc

mountPath:

/data

volumes:

-

name:

nas-pvc

persistentVolumeClaim:

claimName:

nas-pvc

/<code>

上圖中左側,可以看到這個聲明:它的 size 和它的access mode,跟我們剛才靜態創建這塊 PV 其實是匹配的。這樣的話,當用戶在提交 PVC 的時候,K8s 集群相關的組件就會把 PV 的 PVC bound 到一起。之後,用戶在提交 pod yaml 的時候,可以在卷裡面寫上 PVC聲明,在 PVC聲明裡面可以通過 claimName 來聲明要用哪個 PVC。這時,掛載方式其實跟前面講的一樣,當提交完 yaml 的時候,它可以通過 PVC 找到 bound 著的那個 PV,然後就可以用那塊存儲了。這是靜態 Provisioning到被pod使用的一個過程。

動態 PV 使用#

然後再看一下動態 Provisioning。動態 Provisioning 上面提到過,系統管理員不再預分配 PV,而只是創建一個模板文件。

k8s之應用存儲和持久化數據卷:核心知識

這個模板文件叫 StorageClass,在StorageClass裡面,我們需要填的重要信息:第一個是 provisioner,provisioner 是什麼?它其實就是說我當時創建 PV 和對應的存儲的時候,應該用哪個存儲插件來去創建。

這些參數是通過k8s創建存儲的時候,需要指定的一些細節參數。對於這些參數,用戶是不需要關心的,像這裡 regionld、zoneld、fsType 和它的類型。ReclaimPolicy跟我們剛才講解的 PV 裡的意思是一樣的,就是說動態創建出來的這塊 PV,當使用方使用結束、Pod 及 PVC 被刪除後,這塊 PV 應該怎麼處理,我們這個地方寫的是 delete,意思就是說當使用方 pod 和 PVC 被刪除之後,這個 PV 也會被刪除掉。

接下來看一下,集群管理員提交完 StorageClass,也就是提交創建 PV 的模板之後,用戶怎麼用,首先還是需要寫一個 PVC 的文件。

k8s之應用存儲和持久化數據卷:核心知識

PVC 的文件裡存儲的大小、訪問模式是不變的。現在需要新加一個字段,叫 StorageClassName,它的意思是指定動態創建PV的模板文件的名字,這裡StorageClassName填的就是上面聲明的csi-disk。

在提交完 PVC之後,K8s 集群中的相關組件就會根據 PVC 以及對應的 StorageClass 動態生成這塊 PV 給這個 PVC 做一個綁定,之後用戶在提交自己的 yaml 時,用法和接下來的流程和前面的靜態使用方式是一樣的,通過 PVC 找到我們動態創建的 PV,然後把它掛載到相應的容器中就可以使用了。

PV Spec 重要字段解析#

接下來,我們講解一下 PV 的一些重要字段:

k8s之應用存儲和持久化數據卷:核心知識

  • Capacity:這個很好理解,就是存儲對象的大小;
  • AccessModes:也是用戶需要關心的,就是說我使用這個 PV 的方式。它有三種使用方式。
  • 一種是單 node 讀寫訪問;
  • 第二種是多個 node 只讀訪問,是常見的一種數據的共享方式;
  • 第三種是多個 node 上讀寫訪問。

用戶在提交 PVC 的時候,最重要的兩個字段 —— Capacity 和 AccessModes。在提交 PVC後,k8s集群中的相關組件是如何去找到合適的PV呢?首先它是通過為PV建立的AccessModes索引找到所有能夠滿足用戶的 PVC 裡面的 AccessModes 要求的PV list,然後根據PVC的 Capacity,StorageClassName, Label Selector 進一步篩選PV,如果滿足條件的PV有多個,選擇PV的size最小的,accessmodes列表最短的PV,也即最小適合原則。

  • ReclaimPolicy:這個就是剛才提到的,我的用戶方 PV 的 PVC 在刪除之後,我的 PV 應該做如何處理?常見的有三種方式。
  • 第一種方式我們就不說了,現在 K8s 中已經不推薦使用了;
  • 第二種方式 delete,也就是說 PVC 被刪除之後,PV 也會被刪除;
  • 第三種方式 Retain,就是保留,保留之後,後面這個 PV 需要管理員來手動處理。
  • StorageClassName:StorageClassName 這個我們剛才說了,我們動態 Provisioning 時必須指定的一個字段,就是說我們要指定到底用哪一個模板文件來生成 PV ;
  • NodeAffinity:就是說我創建出來的 PV,它能被哪些 node 去掛載使用,其實是有限制的。然後通過 NodeAffinity 來聲明對node的限制,這樣其實對 使用該PV的pod調度也有限制,就是說 pod 必須要調度到這些能訪問 PV 的 node 上,才能使用這塊 PV,這個字段在我們下一講講解存儲拓撲調度時在細說。

PV 狀態流轉#

k8s之應用存儲和持久化數據卷:核心知識

接下來我們看一下 PV 的狀態流轉。首先在創建 PV 對象後,它會處在短暫的pending 狀態;等真正的 PV 創建好之後,它就處在 available 狀態。

available 狀態意思就是可以使用的狀態,用戶在提交 PVC 之後,被 K8s 相關組件做完 bound(即:找到相應的 PV),這個時候 PV 和 PVC 就結合到一起了,此時兩者都處在 bound 狀態。當用戶在使用完 PVC,將其刪除後,這個 PV 就處在 released 狀態,之後它應該被刪除還是被保留呢?這個就會依賴我們剛才說的 ReclaimPolicy。

這裡有一個點需要特別說明一下:當 PV 已經處在 released 狀態下,它是沒有辦法直接回到 available 狀態,也就是說接下來無法被一個新的 PVC 去做綁定。如果我們想把已經 released 的 PV 複用,我們這個時候通常應該怎麼去做呢?第一種方式:我們可以新建一個 PV 對象,然後把之前的 released 的 PV 的相關字段的信息填到新的 PV 對象裡面,這樣的話,這個 PV 就可以結合新的 PVC 了;第二種是我們在刪除 pod 之後,不要去刪除 PVC 對象,這樣給 PV 綁定的 PVC 還是存在的,下次 pod 使用的時候,就可以直接通過 PVC 去複用。K8s中的StatefulSet管理的Pod帶存儲的遷移就是通過這種方式。

操作演示#

雲端環境示例操作示例#

  1. Static Volume Provisioning示例阿里雲NAS
  2. Dynamic Volume Provistoning示例阿里云云盤

接下來,我會在實際的環境中給大家演示一下,靜態 Provisioning 以及動態 Provisioning 具體操作方式。

靜態 Provisioning 例子#

靜態 Provisioning 主要用的是阿里雲的 NAS 文件存儲;動態 Provisioning 主要用了阿里雲的雲盤。它們需要相應存儲插件,插件我已經提前部署在我的 K8s 集群中了(csi-nasplugin是為了在 k8s 中使用阿里雲 NAS 所需的插件,csi-disk是為了在 k8s 中使用阿里云云盤所需要的插件)。

k8s之應用存儲和持久化數據卷:核心知識

我們接下來先看一下靜態 Provisioning 的 PV 的 yaml 文件。

k8s之應用存儲和持久化數據卷:核心知識

volumeAttributes是我在阿里雲nas控制檯預先創建的 NAS 文件系統的相關信息,我們主要需要關心的有 capacity 為5Gi; accessModes 為多node讀寫訪問; reclaimPolicy:Retain,也就是當我使用方的 PVC 被刪除之後,我這個 PV 是要保留下來的;以及在使用這個卷的過程中使用的driver。

然後我們把對應的 PV 創建出來:

k8s之應用存儲和持久化數據卷:核心知識

我們看一下上圖 PV 的狀態,已經處在 Available,也就是說它已經可以被使用了。

再創建出來 nas-pvc:

k8s之應用存儲和持久化數據卷:核心知識

我們看這個時候 PVC 已經新創建出來了,而且也已經和我們上面創建的PV綁定到一起了。我們看一下 PVC 的 yaml 裡面寫的什麼。

k8s之應用存儲和持久化數據卷:核心知識

其實很簡單 ,就是我需要的大小以及我需要的 accessModes。提交完之後,它就與我們集群中已經存在的 PV 做匹配,匹配成功之後,它就會做 bound。

接下來我們去創建使用 nas-fs 的 pod:

k8s之應用存儲和持久化數據卷:核心知識

上圖看到,這兩個 Pod 都已經處在 running 狀態了。

我們先看一下這個 pod yaml:

k8s之應用存儲和持久化數據卷:核心知識

pod yaml 裡面聲明瞭剛才我們創建出來的PVC對象,然後把它掛載到nas-container容器中的 /data 下面。我們這個 pod 是通過前面課程中講解 deployment 創建兩個副本,通過反親和性,將兩個副本調度在不同的 node 上面。

k8s之應用存儲和持久化數據卷:核心知識

上圖我們可以看一下,兩個Pod所在的宿主機是不一樣的。

如下圖所示:我們登陸到第一個上面,findmnt 看一下它的掛載信息,這個其實就掛載在我聲明的 nas-fs 上,那我們再在下面 touch 個 test.test.test 文件,我們也會登陸到另外一個容器看一下,它有沒有被共享。

k8s之應用存儲和持久化數據卷:核心知識

我們退出再登陸另外一個 pod(剛才登陸的是第一個,現在登陸第二個)。

如下圖所示:我們也 findmnt 一下,可以看到,這兩個 pod 的遠程掛載路徑一樣,也就是說我們用的是同一個 NAS PV,我們再看一下剛才創建出來的那個是否存在。

k8s之應用存儲和持久化數據卷:核心知識

可以看到,這個也是存在的,就說明這兩個運行在不同node上的 pod 共享了同一個 nas 存儲。

接下來我們看一下把兩個 pod 刪掉之後的情況。先刪Pod,接著再刪一下對應的 PVC(K8s內部對pvc對象由保護機制,在刪除pvc對象時如果發現有pod在使用pvc,pvc是刪除不掉的),這個可能要稍等一下。

k8s之應用存儲和持久化數據卷:核心知識

看一下下圖對應的 PVC 是不是已經被刪掉了。

k8s之應用存儲和持久化數據卷:核心知識

上圖顯示,它已經被刪掉了。再看一下,剛才的 nas PV 還是在的,它的狀態是處在 Released 狀態,也就是說剛才使用它的 PVC 已經被刪掉了,然後它被 released 了。又因為我們 RECLAIN POLICY 是 Retain,所以它這個 PV 是被保留下來的。

動態 Provisioning 例子#

接下來我們來看第二個例子,動態 Provisioning 的例子。我們先把保留下來的 PV 手動刪掉,可以看到集群中沒有 PV了。接下來演示一下動態 Provisioning。

首先,先去創建一個生成 PV 的模板文件,也就是 storageclass。看一下 storageclass 裡面的內容,其實很簡單。

k8s之應用存儲和持久化數據卷:核心知識

如上圖所示,我事先指定的是我要創建存儲的卷插件(阿里云云盤插件,由阿里雲團隊開發),這個我們已經提前部署好了;我們可以看到,parameters部分是創建存儲所需要的一些參數,但是用戶不需要關心這些信息;然後是 reclaimPolicy,也就是說通過這個 storageclass 創建出來的 PV 在給綁定到一起的 PVC 刪除之後,它是要保留還是要刪除。

k8s之應用存儲和持久化數據卷:核心知識

如上圖所示:現在這個集群中是沒有 PV 的,我們動態提交一個 PVC 文件,先看一下它的 PVC 文件。它的 accessModes-ReadWriteOnce(因為阿里云云盤其實只能是單node讀寫的,所以我們聲明這樣的方式),它的存儲大小需求是 30G,它的 storageClassName 是 csi-disk,就是我們剛才創建的 storageclass,也就是說它指定要通過這個模板去生成 PV。

k8s之應用存儲和持久化數據卷:核心知識

這個 PVC 此時正處在 pending 狀態,這就說明它對應的 PV 還在創建過程中。

k8s之應用存儲和持久化數據卷:核心知識

稍過一會,我們看到已經有一個新的 PV 生成,這個 PV 其實就是根據我們提交的 PVC 以及 PVC 裡面指定的storageclass 動態生成的。之後k8s會將生成的 PV 以及我們提交的 PVC,就是這個 disk PVC 做綁定,之後我們就可以通過創建 pod 來使用了。

再看一下 pod yaml:

k8s之應用存儲和持久化數據卷:核心知識

pod yaml 很簡單,也是通過 PVC 聲明,表明使用這個 PVC。然後是掛載點,下面我們可以創建看一下。

k8s之應用存儲和持久化數據卷:核心知識

如下圖所示:我們可以大概看一下 Events,首先被調度器調度,調度完之後,接下來會有個 attachdetach controller,它會去做 disk的attach操作,就是把我們對應的 PV 掛載到調度器調度的 node 上,然後Pod對應的容器才能啟動,啟動容器才能使用對應的盤。

k8s之應用存儲和持久化數據卷:核心知識

接下來我會把 PVC 刪掉,看一下PV 會不會根據我們的 reclaimPolicy 隨之刪掉呢?我們先看一下,這個時候 PVC 還是存在的,對應的 PV 也是存在的。

k8s之應用存儲和持久化數據卷:核心知識

然後刪一下 PVC,刪完之後再看一下:我們的 PV 也被刪了,也就是說根據 reclaimPolicy,我們在刪除 PVC 的同時,PV 也會被刪除掉。

k8s之應用存儲和持久化數據卷:核心知識

我們的演示部分就到這裡了。

架構設計#

PV 和 PVC 的處理流程#

我們接下來看一下 K8s 中的 PV 和 PVC 體系的完整處理流程。我首先看一下這張圖的右下部分裡面提到的 csi。

k8s之應用存儲和持久化數據卷:核心知識

csi 是什麼?csi 的全稱是 container storage interface,它是K8s社區後面對存儲插件實現(out of tree)的官方推薦方式。csi 的實現大體可以分為兩部分:

  • 第一部分是由k8s社區驅動實現的通用的部分,像我們這張圖中的 csi-provisioner和 csi-attacher controller;
  • 另外一種是由雲存儲廠商實踐的,對接雲存儲廠商的 OpenApi,主要是實現真正的 create/delete/mount/unmount 存儲的相關操作,對應到上圖中的csi-controller-server和csi-node-server。

接下來看一下,當用戶提交 yaml 之後,k8s內部的處理流程。用戶在提交 PVCyaml 的時候,首先會在集群中生成一個 PVC 對象,然後 PVC 對象會被 csi-provisioner controller watch到,csi-provisioner 會結合 PVC 對象以及 PVC 對象中聲明的 storageClass,通過 GRPC 調用 csi-controller-server,然後,到雲存儲服務這邊去創建真正的存儲,並最終創建出來 PV 對象。最後,由集群中的 PV controller 將 PVC 和 PV 對象做 bound 之後,這個 PV 就可以被使用了。

用戶在提交 pod 之後,首先會被調度器調度選中某一個合適的node,之後該 node 上面的 kubelet 在創建 pod 流程中會通過首先 csi-node-server 將我們之前創建的 PV 掛載到我們 pod 可以使用的路徑,然後kubelet開始 create && start pod中的所有container。

PV、PVC 以及通過 csi 使用存儲流程#

我們接下來通過另一張圖來更加詳細看一下我們 PV、PVC 以及通過 CSI 使用存儲的完整流程。

k8s之應用存儲和持久化數據卷:核心知識

主要分為三個階段:

  • 第一個階段(Create階段)是用戶提交完 PVC,由 csi-provisioner 創建存儲,並生成 PV 對象,之後 PV controller 將 PVC 及生成的 PV 對象做 bound,bound 之後,create 階段就完成了;
  • 之後用戶在提交 pod yaml 的時候,首先會被調度選中某一個 合適的node,等 pod 的運行 node 被選出來之後,會被 AD Controller watch 到 pod 選中的 node,它會去查找 pod 中使用了哪些 PV。然後它會生成一個內部的對象叫 VolumeAttachment 對象,從而去觸發 csi-attacher去調用csi-controller-server 去做真正的 attache 操作,attach操作調到雲存儲廠商OpenAPI。這個 attach 操作就是將存儲 attach到 pod 將會運行的 node 上面。第二個階段 —— attach階段完成;
  • 然後我們接下來看第三個階段。第三個階段 發生在kubelet 創建 pod的過程中,它在創建 pod 的過程中,首先要去做一個 mount,這裡的 mount 操作是為了將已經attach到這個 node 上面那塊盤,進一步 mount 到 pod 可以使用的一個具體路徑,之後 kubelet 才開始創建並啟動容器。這就是 PV 加 PVC 創建存儲以及使用存儲的第三個階段 —— mount 階段。

總的來說,有三個階段:第一個 create 階段,主要是創建存儲;第二個 attach 階段,就是將那塊存儲掛載到 node 上面(通常為將存儲load到node的/dev下面);第三個 mount 階段,將對應的存儲進一步掛載到 pod 可以使用的路徑。這就是我們的 PVC、PV、已經通過CSI實現的卷從創建到使用的完整流程。

結束語#

我們今天的內容大概就到這裡,下一節我將為大家來分享 Volume Snapshot 以及 Volume Topology-aware Scheduling 相關的知識以及具體處理流程,謝謝大家~

本節總結(需補充)#

本節課的主要內容就到此為止了,這裡為大家簡單總結一下。

  • K8s Volume是用戶Pod保存業務數據的重要接口和手段
  • PVC和PV體系增強了K8s Volumes在多Pod共享/遷移/存儲擴展等場景下的能力
  • PV(存儲)的不同供給模式(static and dynamic)可以通過多種方式為集群中的Pod供給所需的存儲


分享到:


相關文章: