從零開始入門 K8s | 應用存儲和持久化數據卷:存儲快照與拓撲調度

做者 | 至天 阿里巴巴高級研發工程師node

1、基本知識

存儲快照產生背景

在使用存儲時,爲了提升數據操做的容錯性,咱們一般有須要對線上數據進行 snapshot ,以及能快速 restore 的能力。另外,當須要對線上數據進行快速的複製以及遷移等動做,如進行環境的複製、數據開發等功能時,均可以經過存儲快照來知足需求,而 K8s 中經過 CSI Snapshotter controller 來實現存儲快照的功能。服務器

存儲快照用戶接口-Snapshot

咱們知道,K8s 中經過 pvc 以及 pv 的設計體系來簡化用戶對存儲的使用,而存儲快照的設計實際上是仿照  pvc & pv 體系的設計思想。當用戶須要存儲快照的功能時,能夠經過 VolumeSnapshot 對象來聲明,並指定相應的 VolumeSnapshotClass 對象,以後由集羣中的相關組件動態生成存儲快照以及存儲快照對應的對象 VolumeSnapshotContent 。以下對比圖所示,動態生成 VolumeSnapshotContent 和動態生成 pv 的流程是很是類似的。微信

1

存儲快照用戶接口-Restore

有了存儲快照以後,如何將快照數據快速恢復過來呢?以下圖所示:網絡

2

如上所示的流程,能夠藉助 PVC 對象將其的 dataSource 字段指定爲 VolumeSnapshot 對象。這樣當 PVC 提交以後,會由集羣中的相關組件找到 dataSource 所指向的存儲快照數據,而後新建立對應的存儲以及 pv 對象,將存儲快照數據恢復到新的 pv 中,這樣數據就恢復回來了,這就是存儲快照的 restore 用法。less

Topolopy-含義

首先了解一下拓撲是什麼意思:這裏所說的拓撲是 K8s 集羣中爲管理的 nodes 劃分的一種「位置」關係,意思爲:能夠經過在 node 的 labels 信息裏面填寫某一個 node 屬於某一個拓撲。<br /> <br />常見的有三種,這三種在使用時常常會遇到的:dom

  • 第一種,在使用雲存儲服務的時候,常常會遇到 region,也就是地區的概念,在 K8s 中常經過 label failure-domain.beta.kubernetes.io/region 來標識。這個是爲了標識單個 K8s 集羣管理的跨 region 的 nodes 到底屬於哪一個地區;socket

  • 第二種,比較經常使用的是可用區,也就是 available zone,在 K8s 中常經過 label failure-domain.beta.kubernetes.io/zone 來標識。這個是爲了標識單個 K8s 集羣管理的跨 zone 的 nodes 到底屬於哪一個可用區;分佈式

  • 第三種,是 **hostname,**就是單機維度,是拓撲域爲 node 範圍,在 K8s 中常經過 label kubernetes.io/hostname 來標識,這個在文章的最後講 local pv 的時候,會再詳細描述。微服務

上面講到的三個拓撲是比較經常使用的,而拓撲實際上是能夠本身定義的。能夠定義一個字符串來表示一個拓撲域,這個 key 所對應的值其實就是拓撲域下不一樣的拓撲位置。性能

舉個例子:能夠用 **rack,**也就是機房中的機架這個緯度來作一個拓撲域。這樣就能夠將不一樣機架 ( rack ) 上面的機器標記爲不一樣的拓撲位置,也就是說能夠將不一樣機架上機器的位置關係經過 rack 這個緯度來標識。屬於 rack1 上的機器,node label 中都添加 rack 的標識,它的 value 就標識成 rack1,即 rack=rack1;另一組機架上的機器能夠標識爲 rack=rack2,這樣就能夠經過機架的緯度就來區分來 K8s 中的 node 所處的位置。

接下來就一塊兒來看看拓撲在 K8s 存儲中的使用。

存儲拓撲調度產生背景

上一節課咱們說過,K8s 中經過 PV 的 PVC 體系將存儲資源和計算資源分開管理了。若是建立出來的 PV有"訪問位置"的限制,也就是說,它經過 nodeAffinity 來指定哪些 node 能夠訪問這個 PV。爲何會有這個訪問位置的限制?

由於在 K8s 中建立 pod 的流程和建立 PV 的流程,其實能夠認爲是並行進行的,這樣的話,就沒有辦法來保證 pod 最終運行的 node 是能訪問到 有位置限制的 PV 對應的存儲,最終致使 pod 無法正常運行。這裏來舉兩個經典的例子:

首先來看一下 Local PV 的例子,Local PV 是將一個 node 上的本地存儲封裝爲 PV,經過使用 PV 的方式來訪問本地存儲。爲何會有 Local PV 的需求呢?簡單來講,剛開始使用 PV 或 PVC 體系的時候,主要是用來針對分佈式存儲的,分佈式存儲依賴於網絡,若是某些業務對 I/O 的性能要求很是高,可能經過網絡訪問分佈式存儲沒辦法知足它的性能需求。這個時候須要使用本地存儲,刨除了網絡的 overhead,性能每每會比較高。可是用本地存儲也是有壞處的!分佈式存儲能夠經過多副原本保證高可用,但本地存儲就須要業務本身用相似 Raft 協議來實現多副本高可用。

接下來看一下 Local PV 場景可能若是沒有對 PV 作「訪問位置」的限制會遇到什麼問題?

3

當用戶在提交完 PVC 的時候,K8s PV controller可能綁定的是 node2 上面的 PV。可是,真正使用這個 PV 的 pod,在被調度的時候,有可能調度在 node1 上,最終致使這個 pod 在起來的時候沒辦法去使用這塊存儲,由於 pod 真實狀況是要使用 node2 上面的存儲。

第二個(若是不對 PV 作「訪問位置」的限制會出問題的)場景:

4

若是搭建的 K8s 集羣管理的 nodes 分佈在單個區域多個可用區內。在建立動態存儲的時候,建立出來的存儲屬於可用區 2,但以後在提交使用該存儲的 pod,它可能會被調度到可用區 1 了,那就可能沒辦法使用這塊存儲。所以像阿里雲的雲盤,也就是塊存儲,當前不能跨可用區使用,若是建立的存儲其實屬於可用區 2,可是 pod 運行在可用區 1,就沒辦法使用這塊存儲,這是第二個常見的問題場景。

接下來咱們來看看 K8s 中如何經過存儲拓撲調度來解決上面的問題的。

存儲拓撲調度

首先總結一下以前的兩個問題,它們都是 PV 在給 PVC 綁定或者動態生成 PV 的時候,我並不知道後面將使用它的 pod 將調度在哪些 node 上。但 PV 自己的使用,是對 pod 所在的 node 有拓撲位置的限制的,如 Local PV 場景是我要調度在指定的 node 上我才能使用那塊 PV,而對第二個問題場景就是說跨可用區的話,必需要在將使用該 PV 的 pod 調度到同一個可用區的 node 上才能使用阿里云云盤服務,那 K8s 中怎樣去解決這個問題呢?

簡單來講,在 K8s 中將 PV 和 PVC 的 binding 操做和動態建立 PV 的操做作了 delay,delay 到 pod 調度結果出來以後,再去作這兩個操做。這樣的話有什麼好處?

  • 首先,若是要是所要使用的 PV 是預分配的,如 Local PV,其實使用這塊 PV 的 pod 它對應的 PVC 其實尚未作綁定,就能夠經過調度器在調度的過程當中,結合 pod 的計算資源需求(如 cpu/mem) 以及 pod 的 PVC 需求,選擇的 node 既要知足計算資源的需求又要 pod 使用的 pvc 要能 binding 的 pv 的 nodeaffinity 限制;
  • 其次對動態生成 PV 的場景其實就至關因而若是知道 pod 運行的 node 以後,就能夠根據 node 上記錄的拓撲信息來動態的建立這個 PV,也就是保證新建立出來的 PV 的拓撲位置與運行的 node 所在的拓撲位置是一致的,如上面所述的阿里云云盤的例子,既然知道 pod 要運行到可用區 1,那以後建立存儲時指定在可用區 1 建立便可。

爲了實現上面所說的延遲綁定和延遲建立 PV,須要在 K8s 中的改動涉及到的相關組件有三個:

  • PV Controller 也就是 persistent volume controller,它須要支持延遲 Binding 這個操做。
  • 另外一個是動態生成 PV 的組件,若是 pod 調度結果出來以後,它要根據 pod 的拓撲信息來去動態的建立 PV。
  • 第三組件,也是最重要的一個改動點就是 kube-scheduler。在爲 pod 選擇 node 節點的時候,它不只要考慮 pod 對 CPU/MEM 的計算資源的需求,它還要考慮這個 pod 對存儲的需求,也就是根據它的 PVC,它要先去看一下當前要選擇的 node,可否知足能和這個 PVC 能匹配的 PV 的 nodeAffinity;或者是動態生成 PV 的過程,它要根據 StorageClass 中指定的拓撲限制來 check 當前的 node 是否是知足這個拓撲限制,這樣就能保證調度器最終選擇出來的 node 就能知足存儲自己對拓撲的限制。

這就是存儲拓撲調度的相關知識。

2、用例解讀

接下來經過 yaml 用例來解讀一下第一部分的基本知識。

Volume Snapshot/Restore示例

5

下面來看一下存儲快照如何使用:首先須要集羣管理員,在集羣中建立 VolumeSnapshotClass 對象,VolumeSnapshotClass 中一個重要字段就是 Snapshot,它是指定真正建立存儲快照所使用的卷插件,這個卷插件是須要提早部署的,稍後再說這個卷插件。

接下來用戶他若是要作真正的存儲快照,須要聲明一個 VolumeSnapshotClass , VolumeSnapshotClass 首先它要指定的是 VolumeSnapshotClassName,接着它要指定的一個很是重要的字段就是 source,這個 source 其實就是指定快照的數據源是啥。這個地方指定 name 爲 disk-pvc,也就是說經過這個 pvc 對象來建立存儲快照。提交這個 VolumeSnapshot 對象以後,集羣中的相關組件它會找到這個 PVC 對應的 PV 存儲,對這個 PV 存儲作一次快照。

有了存儲快照以後,那接下來怎麼去用存儲快照恢復數據呢?這個其實也很簡單,經過聲明一個新的 PVC 對象並在它的 spec 下面的 DataSource 中來聲明個人數據源來自於哪一個 VolumeSnapshot,這裏指定的是 disk-snapshot 對象,當我這個 PVC 提交以後,集羣中的相關組件,它會動態生成新的 PV 存儲,這個新的 PV 存儲中的數據就來源於這個 Snapshot 以前作的存儲快照。

Local PV 的示例

以下圖看一下 Local PV 的 yaml 示例:

6

Local PV 大部分使用的時候都是經過靜態建立的方式,也就是要先去聲明 PV 對象,既然 Local PV 只能是本地訪問,就須要在聲明 PV 對象的,在 PV 對象中經過 nodeAffinity 來限制我這個 PV 只能在單 node 上訪問,也就是給這個 PV 加上拓撲限制。如上圖拓撲的 key 用 kubernetes.io/hostname 來作標記,也就是隻能在 node1 訪問。若是想用這個 PV,你的 pod 必需要調度到 node1 上。

既然是靜態建立 PV 的方式,這裏爲何還須要 storageClassname 呢?前面也說了,在 Local PV 中,若是要想讓它正常工做,須要用到延遲綁定特性才行,那既然是延遲綁定,當用戶在寫完 PVC 提交以後,即便集羣中有相關的 PV 能跟它匹配,它也暫時不能作匹配,也就是說 PV controller 不能立刻去作 binding,這個時候你就要經過一種手段來告訴 PV controller,什麼狀況下是不能當即作 binding。這裏的 storageClass 就是爲了起到這個反作用,咱們能夠看到 storageClass 裏面的 provisioner 指定的是 no-provisioner,其實就是至關於告訴 K8s 它不會去動態建立 PV,它主要用到 storageclass 的VolumeBindingMode 字段,叫 WaitForFirstConsumer,能夠先簡單地認爲它是延遲綁定。

當用戶開始提交 PVC 的時候,pv controller 在看到這個 pvc 的時候,它會找到相應的 storageClass,發現這個 BindingMode 是延遲綁定,它就不會作任何事情。

以後當真正使用這個 pvc 的 pod,在調度的時候,當它剛好調度在符合 pv nodeaffinity 的 node 的上面後,這個 pod 裏面所使用的 PVC 纔會真正地與 PV 作綁定,這樣就保證我 pod 調度到這臺 node 上以後,這個 PVC 才與這個 PV 綁定,最終保證的是建立出來的 pod 能訪問這塊 Local PV,也就是靜態 Provisioning 場景下怎麼去知足 PV 的拓撲限制。

限制 Dynamic Provisioning PV 拓撲示例

再看一下動態 Provisioning PV 的時候,怎麼去作拓撲限制的?

7

動態就是指動態建立 PV 就有拓撲位置的限制,那怎麼去指定?

首先在 storageclass 仍是須要指定 BindingMode,就是 WaitForFirstConsumer,就是延遲綁定。

其次特別重要的一個字段就是 allowedTopologies,限制就在這個地方。上圖中能夠看到拓撲限制是可用區的級別,這裏其實有兩層意思:

  1. 第一層意思就是說我在動態建立 PV 的時候,建立出來的 PV 必須是在這個可用區能夠訪問的;
  2. 第二層含義是由於聲明的是延遲綁定,那調度器在發現使用它的 PVC 正好對應的是該 storageclass 的時候,調度 pod 就要選擇位於該可用區的 nodes。

總之,就是要從兩方面保證,一是動態建立出來的存儲時要能被這個可用區訪問的,二是我調度器在選擇 node 的時候,要落在這個可用區內的,這樣的話就保證個人存儲和我要使用存儲的這個 pod 它所對應的 node,它們之間的拓撲域是在同一個拓撲域,用戶在寫 PVC 文件的時候,寫法是跟之前的寫法是同樣的,主要是在 storageclass 中要作一些拓撲限制。

3、操做演示

本節將在線上環境來演示一下前面講解的內容。

首先來看一下個人阿里雲服務器上搭建的 K8s 服務。總共有 3 個 node 節點。一個 master 節點,兩個 node。其中 master 節點是不能調度 pod 的。

8

再看一下,我已經提早把我須要的插件已經布好了,一個是 snapshot 插件  ( csi-external-snapshot* ) ,一個是動態雲盤的插件  ( csi-disk* ) 。

9

如今開始 snapshot 的演示。首先去動態建立雲盤,而後才能作 snapshot。動態建立雲盤須要先建立  storageclass,而後去根據 PVC 動態建立 PV,而後再建立一個使用它的 pod 了。

10

有個以上對象,如今就能夠作 snapshot 了,首先看一下作 snapshot 須要的第一個配置文件:snapshotclass.yaml。

11

其實裏面就是指定了在作存儲快照的時候須要使用的插件,這個插件剛纔演示了已經部署好了,就是 csi-external-snapshot-0 這個插件。

12

接下來建立 volume-snapshotclass 文件,建立完以後就開始了 snapshot。

13

而後看 snapshot.yaml,Volumesnapshot 聲明建立存儲快照了,這個地方就指定剛纔建立的那個 PVC 來作的數據源來作 snapshot,那咱們開始建立。

14

咱們看一下 Snapshot 有沒有建立好,以下圖所示,content 已經在 11 秒以前建立好了。

15

能夠看一下它裏面的內容,主要看 volumesnapshotcontent 記錄的一些信息,這個是我 snapshot 出來以後,它記錄的就是雲存儲廠商那邊返回給個人 snapshot 的 ID。而後是這個 snapshot 數據源,也就是剛纔指定的 PVC,能夠經過它會找到對應的 PV。

16

snapshot 的演示大概就是這樣,把剛纔建立的 snapshot 刪掉,仍是經過 volumesnapshot 來刪掉。而後看一下,動態建立的這個 volumesnapshotcontent 也被刪掉。

17

接下來看一下動態 PV 建立的過程加上一些拓撲限制,首先將的 storageclass 建立出來,而後再看一下 storageclass 裏面作的限制,storageclass 首先仍是指定它的 BindingMode 爲 WaitForFirstConsumer,也就是作延遲綁定,而後是對它的拓撲限制,我這裏面在 allowedTopologies 字段中配置了一個可用區級別的限制。

18

來嘗試建立一下的 PVC,PVC 建立出來以後,理論上它應該處在 pending 狀態。看一下,它如今由於它要作延遲綁定,因爲如今沒有使用它的 pod,暫時沒辦法去作綁定,也沒辦法去動態建立新的 PV。

19

接下來建立使用該 pvc 的 pod 看會有什麼效果,看一下 pod,pod 也處在 pending了。

20

那來看一下 pod 爲啥處在 pending 狀態,能夠看一下是調度失敗了,調度失敗緣由:一個 node 因爲 taint 不能調度,這個實際上是 master,另外兩個 node 也是沒有說是可綁定的 PV。

21

爲何會有兩個 node 出現沒有可綁定的 pv 的錯誤?不是動態建立麼?

咱們來仔細看看 storageclass 中的拓撲限制,經過上面的講解咱們知道,這裏限制使用該 storageclass 建立的 PV 存儲必須在可用區 cn-hangzhou-d 可訪問的,而使用該存儲的 pod 也必須調度到 cn-hangzhou-d 的 node 上。

22

那就來看一下 node 節點上有沒有這個拓撲信息,若是它沒有固然是不行了。

看一下第一個 node 的全量信息吧,主要找它的 labels 裏面的信息,看 lables 裏面的確有一個這樣的 key。也就是說有一個這樣的拓撲,可是這指定是 cn-hangzhou-b,剛纔 storageclass 裏面指定的是 cn-hangzhou-d。

23

那看一下另外的一個 node 上的這個拓撲信息寫的也是 hangzhou-b,可是咱們那個 storageclass 裏面限制是 d。

24

這就致使最終沒辦法將 pod 調度在這兩個 node 上。如今咱們修改一下 storageclass 中的拓撲限制,將 cn-hangzhou-d 改成 cn-hangzhou-b。

25

改完以後再看一下,其實就是說我動態建立出來的 PV 要能被 hangzhou-b 這個可用區訪問的,使用該存儲的 pod 要調度到該可用區的 node 上。把以前的 pod 刪掉,讓它從新被調度看一下有什麼結果,好,如今這個已經調度成功了,就是已經在啓動容器階段。

26

說明剛纔把 storageclass 它裏面的對可用區的限制從 hangzhou-d 改成 hangzhou-b 以後,集羣中就有兩個 node,它的拓撲關係是和 storageclass 裏要求的拓撲關係是相匹配的,這樣的話它就能保證它的 pod 是有 node 節點可調度的。上圖中最後一點 Pod 已經 Running 了,說明剛纔的拓撲限制改動以後能夠 work 了。

4、處理流程

kubernetes 對 Volume Snapshot/Restore 處理流程

接下來看一下 K8s 中對存儲快照與拓撲調度的具體處理流程。以下圖所示:

27

首先來看一下存儲快照的處理流程,這裏來首先解釋一下 csi 部分。K8s 中對存儲的擴展功能都是推薦經過 csi out-of-tree 的方式來實現的。

csi 實現存儲擴展主要包含兩部分:

  • 第一部分是由 K8s 社區推進實現的 csi controller 部分,也就是這裏的 csi-snapshottor controller 以及 csi-provisioner controller,這些主要是通用的 controller 部分;
  • 另一部分是由特定的雲存儲廠商用自身 OpenAPI 實現的不一樣的 csi-plugin 部分,也叫存儲的 driver 部分。

兩部分部件經過 unix domain socket 通訊鏈接到一塊兒。有這兩部分,才能造成一個真正的存儲擴展功能。

如上圖所示,當用戶提交 VolumeSnapshot 對象以後,會被 csi-snapshottor controller watch 到。以後它會經過 GPPC 調用到 csi-plugin,csi-plugin 經過 OpenAPI 來真正實現存儲快照的動做,等存儲快照已經生成以後,會返回到 csi-snapshottor controller 中,csi-snapshottor controller 會將存儲快照生成的相關信息放到 VolumeSnapshotContent 對象中並將用戶提交的 VolumeSnapshot 作 bound。這個 bound 其實就有點相似 PV 和 PVC 的 bound 同樣。

有了存儲快照,如何去使用存儲快照恢復以前的數據呢?前面也說過,經過聲明一個新的 PVC 對象,而且指定他的 dataSource 爲 Snapshot 對象,當提交 PVC 的時候會被 csi-provisioner watch 到,以後會經過 GRPC 去建立存儲。這裏建立存儲跟以前講解的 csi-provisioner 有一個不太同樣的地方,就是它裏面還指定了 Snapshot 的 ID,當去雲廠商建立存儲時,須要多作一步操做,即將以前的快照數據恢復到新建立的存儲中。以後流程返回到 csi-provisioner,它會將新建立的存儲的相關信息寫到一個新的 PV 對象中,新的 PV 對象被 PV controller watch 到它會將用戶提交的 PVC 與 PV 作一個 bound,以後 pod 就能夠經過 PVC 來使用 Restore 出來的數據了。這是 K8s 中對存儲快照的處理流程。

kubernetes 對 Volume Topology-aware Scheduling 處理流程

接下來看一下存儲拓撲調度的處理流程:

28

第一個步驟其實就是要去聲明延遲綁定,這個經過 StorageClass 來作的,上面已經闡述過,這裏就不作詳細描述了。

接下來看一下調度器,上圖中紅色部分就是調度器新加的存儲拓撲調度邏輯,咱們先來看一下不加紅色部分時調度器的爲一個 pod 選擇 node 時,它的大概流程:

  • 首先用戶提交完 pod 以後,會被調度器 watch 到,它就會去首先作預選,預選就是說它會將集羣中的全部 node 都來與這個 pod 它須要的資源作匹配;
  • 若是匹配上,就至關於這個 node 可使用,固然可能不止一個 node 可使用,最終會選出來一批 node;
  • 而後再通過第二個階段優選,優選就至關於我要對這些 node 作一個打分的過程,經過打分找到最匹配的一個 node;
  • 以後調度器將調度結果寫到 pod 裏面的 spec.nodeName 字段裏面,而後會被相應的 node 上面的 kubelet watch 到,最後就開始建立 pod 的整個流程。

那如今看一下加上卷相關的調度的時候,篩選 node(第二個步驟)又是怎麼作的?

  • 先就要找到 pod 中使用的全部 PVC,找到已經 bound 的 PVC,以及須要延遲綁定的這些 PVC;
  • 對於已經 bound 的 PVC,要 check 一下它對應的 PV 裏面的 nodeAffinity 與當前 node 的拓撲是否匹配 。若是不匹配, 就說明這個 node 不能被調度。若是匹配,繼續往下走,就要去看一下須要延遲綁定的 PVC;
  • 對於須要延遲綁定的 PVC。先去獲取集羣中存量的 PV,知足 PVC 需求的,先把它所有撈出來,而後再將它們一一與當前的 node labels 上的拓撲作匹配,若是它們(存量的 PV)都不匹配,那就說明當前的存量的 PV 不能知足需求,就要進一步去看一下若是要動態建立 PV 當前 node 是否知足拓撲限制,也就是還要進一步去 check StorageClass 中的拓撲限制,若是 StorageClass 中聲明的拓撲限制與當前的 node 上面已經有的 labels 裏面的拓撲是相匹配的,那其實這個 node 就可使用,若是不匹配,說明該 node 就不能被調度。

通過這上面步驟以後,就找到了全部即知足 pod 計算資源需求又知足 pod 存儲資源需求的全部 nodes。<br /> <br />當 node 選出來以後,第三個步驟就是調度器內部作的一個優化。這裏簡單過一下,就是更新通過預選和優選以後,pod 的 node 信息,以及 PV 和 PVC 在 scheduler 中作的一些 cache 信息。

第四個步驟也是重要的一步,已經選擇出來 node 的 Pod,無論其使用的 PVC 是要 binding 已經存在的 PV,仍是要作動態建立 PV,這時就能夠開始作。由調度器來觸發,調度器它就會去更新 PVC 對象和 PV 對象裏面的相關信息,而後去觸發 PV controller 去作 binding 操做,或者是由 csi-provisioner 去作動態建立流程。

總結

  1. 經過對比 PVC&PV 體系講解了存儲快照的相關 K8s 資源對象以及使用方法;
  2. 經過兩個實際場景遇到的問題引出存儲拓撲調度功能必要性,以及 K8s 中如何經過拓撲調度來解決這些問題;
  3. 經過剖析 K8s 中存儲快照和存儲拓撲調度內部運行機制,深刻理解該部分功能的工做原理。

阿里巴巴雲原生微信公衆號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術公衆號。

相關文章
相關標籤/搜索