天天看點

應用存儲和持久化資料卷:存儲快照與拓撲排程

一、基本知識

存儲快照産生背景

在使用存儲時,為了提高資料操作的容錯性,我們通常有需要對線上資料進行snapshot,以及能快速restore的能力。另外,當需要對線上資料進行快速的複制以及遷移等動作,如進行環境的複制、資料開發等功能時,都可以通過存儲快照來滿足需求,而 K8s 中通過 CSI Snapshotter controller 來實作存儲快照的功能。

 存儲快照使用者接口-Snapshot

我們知道,K8s 中通過 pvc 以及 pv 的設計體系來簡化使用者對存儲的使用,而存儲快照的設計其實是仿照  pvc & pv 體系的設計思想。當使用者需要存儲快照的功能時,可以通過 VolumeSnapshot 對象來聲明,并指定相應的 VolumeSnapshotClass 對象,之後由叢集中的相關元件動态生成存儲快照以及存儲快照對應的對象 VolumeSnapshotContent。如下對比圖所示,動态生成 VolumeSnapshotContent 和動态生成 pv 的流程是非常相似的。

應用存儲和持久化資料卷:存儲快照與拓撲排程

存儲快照使用者接口-Restore

有了存儲快照之後,如何将快照資料快速恢複過來呢?如下圖所示:

應用存儲和持久化資料卷:存儲快照與拓撲排程

如上所示的流程,可以借助 PVC 對象将其的 dataSource 字段指定為 VolumeSnapshot 對象。這樣當 PVC 送出之後,會由叢集中的相關元件找到 dataSource 所指向的存儲快照資料,然後新建立對應的存儲以及 pv 對象,将存儲快照資料恢複到新的 pv 中,這樣資料就恢複回來了,這就是存儲快照的restore用法。

Topolopy-含義

首先了解一下拓撲是什麼意思:這裡所說的拓撲是 K8s 叢集中為管理的 nodes 劃分的一種“位置”關系,意思為:可以通過在 node 的 labels 資訊裡面填寫某一個 node 屬于某一個拓撲。

常見的有三種,這三種在使用時經常會遇到的:

  • 第一種,在使用雲存儲服務的時候,經常會遇到region,也就是地區的概念,在 K8s 中常通過 label failure-domain.beta.kubernetes.io/region 來辨別。這個是為了辨別單個 K8s 叢集管理的跨 region 的 nodes 到底屬于哪個地區;
  • 第二種,比較常用的是可用區,也就是 availablezone,在 K8s 中常通過 label failure-domain.beta.kubernetes.io/zone 來辨別。這個是為了辨別單個 K8s 叢集管理的跨 zone 的 nodes 到底屬于哪個可用區;
  • 第三種,是hostname,就是單機次元,是拓撲域為 node 範圍,在 K8s 中常通過 label kubernetes.io/hostname 來辨別,這個在文章的最後講 local pv 的時候,會再較長的描述。

上面講到的三個拓撲是比較常用的,而拓撲其實是可以自己定義的。可以定義一個字元串來表示一個拓撲域,這個 key 所對應的值其實就是拓撲域下不同的拓撲位置。

接下來就一起來看看拓撲在 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做“通路位置”的限制會遇到什麼問題?

應用存儲和持久化資料卷:存儲快照與拓撲排程

當使用者在送出完 PVC 的時候,K8s PV controller可能綁定的是 node2 上面的 PV。但是,真正使用這個 PV 的 pod,在被排程的時候,有可能排程在 node1 上,最終導緻這個 pod 在起來的時候沒辦法去使用這塊存儲,因為 pod 真實情況是要使用 node2 上面的存儲。

第二個(如果不對 PV 做“通路位置”的限制會出問題的)場景:

存儲拓撲排程

首先總結一下之前的兩個問題,它們都是 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 就能滿足存儲本身對拓撲的限制。

三、操作示範

本節将線上上環境來示範一下前面講解的内容。

首先來看一下我的阿裡雲伺服器上搭建的 K8s 服務。總共有 3 個 node 節點。一個 master 節點,兩個 node。其中 master 節點是不能排程 pod 的。

應用存儲和持久化資料卷:存儲快照與拓撲排程

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

應用存儲和持久化資料卷:存儲快照與拓撲排程

現在開始 snapshot 的示範。首先去動态建立雲盤,然後才能做 snapshot。動态建立雲盤需要先建立  storageclass,然後去根據 PVC 動态建立 PV,然後再建立一個使用它的 pod 了。

應用存儲和持久化資料卷:存儲快照與拓撲排程

有個以上對象,現在就可以做 snapshot 了,首先看一下做 snapshot 需要的第一個配置檔案:snapshotclass.yaml。

應用存儲和持久化資料卷:存儲快照與拓撲排程

其實裡面就是指定了在做存儲快照的時候需要使用的插件,這個插件剛才示範了已經部署好了,就是 csi-external-snapshot-0 這個插件。

應用存儲和持久化資料卷:存儲快照與拓撲排程

 接下來建立 volume-snapshotclass 檔案,建立完之後就開始了 snapshot。

應用存儲和持久化資料卷:存儲快照與拓撲排程

然後看 snapshot.yaml,Volumesnapshot 聲明建立存儲快照了,這個地方就指定剛才建立的那個 PVC 來做的資料源來做 snapshot,那我們開始建立。

應用存儲和持久化資料卷:存儲快照與拓撲排程

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

應用存儲和持久化資料卷:存儲快照與拓撲排程

可以看一下它裡面的内容,主要看 volumesnapshotcontent 記錄的一些資訊,這個是我 snapshot 出來之後,它記錄的就是雲存儲廠商那邊傳回給我的 snapshot 的 ID。然後是這個 snapshot 資料源,也就是剛才指定的 PVC,可以通過它會找到對應的 PV。

應用存儲和持久化資料卷:存儲快照與拓撲排程

snapshot 的示範大概就是這樣,把剛才建立的 snapshot 删掉,還是通過 volumesnapshot 來删掉。然後看一下,動态建立的這個 volumesnapshotcontent 也被删掉。

應用存儲和持久化資料卷:存儲快照與拓撲排程

接下來看一下動态 PV 建立的過程加上一些拓撲限制,首先将的 storageclass 建立出來,然後再看一下 storageclass 裡面做的限制,storageclass 首先還是指定它的 BindingMode 為 WaitForFirstConsumer,也就是做延遲綁定,然後是對它的拓撲限制,我這裡面在 allowedTopologies 字段中配置了一個可用區級别的限制。

應用存儲和持久化資料卷:存儲快照與拓撲排程

來嘗試建立一下的 PVC,PVC 建立出來之後,理論上它應該處在 pending 狀态。看一下,它現在因為它要做延遲綁定,由于現在沒有使用它的 pod,暫時沒辦法去做綁定,也沒辦法去動态建立新的 PV。

應用存儲和持久化資料卷:存儲快照與拓撲排程

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

應用存儲和持久化資料卷:存儲快照與拓撲排程

那來看一下 pod 為啥處在 pending 狀态,可以看一下是排程失敗了,排程失敗原因:一個 node 由于 taint 不能排程,這個其實是 master,另外兩個 node 也是沒有說是可綁定的 PV。

應用存儲和持久化資料卷:存儲快照與拓撲排程

為什麼會有兩個 node 出現沒有可綁定的 pv 的錯誤?不是動态建立麼?

我們來仔細看看 storageclass 中的拓撲限制,通過上面的講解我們知道,這裡限制使用該 storageclass 建立的 PV 存儲必須在可用區 cn-hangzhou-d 可通路的,而使用該存儲的 pod 也必須排程到 cn-hangzhou-d 的 node 上。

應用存儲和持久化資料卷:存儲快照與拓撲排程

那就來看一下 node 節點上有沒有這個拓撲資訊,如果它沒有當然是不行了。

看一下第一個 node 的全量資訊吧,主要找它的 labels 裡面的資訊,看 lables 裡面的确有一個這樣的 key。也就是說有一個這樣的拓撲,但是這指定是 cn-hangzhou-b,剛才 storageclass 裡面指定的是 cn-hangzhou-d。

應用存儲和持久化資料卷:存儲快照與拓撲排程

那看一下另外的一個 node 上的這個拓撲資訊寫的也是 hangzhou-b,但是我們那個 storageclass 裡面限制是 d。

應用存儲和持久化資料卷:存儲快照與拓撲排程

這就導緻最終沒辦法将 pod 排程在這兩個 node 上。現在我們修改一下 storageclass 中的拓撲限制,将 cn-hangzhou-d 改為 cn-hangzhou-b。

應用存儲和持久化資料卷:存儲快照與拓撲排程

改完之後再看一下,其實就是說我動态建立出來的 PV 要能被 hangzhou-b 這個可用區通路的,使用該存儲的 pod 要排程到該可用區的 node 上。把之前的 pod 删掉,讓它重新被排程看一下有什麼結果,好,現在這個已經排程成功了,就是已經在啟動容器階段。

應用存儲和持久化資料卷:存儲快照與拓撲排程

說明剛才把 storageclass 它裡面的對可用區的限制從 hangzhou-d 改為 hangzhou-b 之後,叢集中就有兩個 node,它的拓撲關系是和 storageclass 裡要求的拓撲關系是相比對的,這樣的話它就能保證它的 pod 是有 node 節點可排程的。上圖中最後一點 Pod 已經 Running 了,說明剛才的拓撲限制改動之後可以 work 了。

四、處理流程

kubernetes 對 Volume Snapshot/Restore 處理流程

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

應用存儲和持久化資料卷:存儲快照與拓撲排程

首先來看一下存儲快照的處理流程,這裡來首先解釋一下 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 處理流程

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

應用存儲和持久化資料卷:存儲快照與拓撲排程

第一個步驟其實就是要去聲明延遲綁定,這個通過 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。