天天看點

雲原生存儲詳解:容器存儲與 K8s 存儲卷

雲原生存儲詳解系列文章将從雲原生存儲服務的概念、特點、需求、原理、使用及案例等方面,和大家一起探讨雲原生存儲技術新的機遇與挑戰。本文為該系列文章的第二篇,會對容器存儲的相關概念進行講述,歡迎大家在留言區參與讨論。

作者 | 阚俊寶 阿裡雲技術專家

導讀:雲原生存儲詳解系列文章将從雲原生存儲服務的概念、特點、需求、原理、使用及案例等方面,和大家一起探讨雲原生存儲技術新的機遇與挑戰。本文為該系列文章的第二篇,會對容器存儲的相關概念進行講述,歡迎大家在留言區參與讨論。

相關文章推薦:

雲原生存儲詳解:雲原生應用的基石

雲原生存儲詳解:容器存儲與 K8s 存儲卷

雲原生存儲的兩個關鍵領域:Docker 存儲卷、K8s 存儲卷;

  • Docker 存儲卷:容器服務在單節點的存儲組織形式,關注資料存儲、容器運作時的相關技術;
  • K8s 存儲卷:關注容器叢集的存儲編排,從應用使用存儲的角度關注存儲服務。

Docker 存儲

容器服務之是以如此流行,一大優勢即來自于運作容器時容器鏡像的組織形式。容器通過複用容器鏡像的技術,實作在相同節點上多個容器共享一個鏡像資源(更細一點說是共享某一個鏡像層),避免了每次啟動容器時都拷貝、加載鏡像檔案,這種方式既節省了主機的存儲空間,又提高了容器啟動效率。

1. 容器讀寫層

為了提高節點存儲的使用效率,容器不光在不同運作的容器之間共享鏡像資源,而且還實作了在不同鏡像之間共享資料。共享鏡像資料的實作原理:鏡像是分層組合而成的,即一個完整的鏡像會包含多個資料層,每層資料互相疊加、覆寫組成了最終的完整鏡像。

為了實作多個容器間共享鏡像資料,容器鏡像每一層都是隻讀的。而通過實踐我們得知,使用鏡像啟動一個容器的時候,其實是可以在容器裡随意讀寫的,這是如何實作的呢?

容器使用鏡像時,在多個鏡像分層的最上面還添加了一個讀寫層。每一個容器在運作時,都會基于目前鏡像在其最上層挂載一個讀寫層,使用者針對容器的所有操作都在讀寫層中完成。一旦容器銷毀,這個讀寫層也随之銷毀。

雲原生存儲詳解:容器存儲與 K8s 存儲卷

如上圖所示例子,一個節點上共有 3 個容器,分别基于 2 個鏡像運作。

鏡像存儲層說明如下:

該節點上共包含 6 個鏡像層:Layer 1~6。
  • 鏡像 1 由:Layer 1、3、4、5 組成;
  • 鏡像 2 由:Layer 2、3、5、6 組成。
是以兩個鏡像共享了 Layer 3、5 兩個鏡像層;

容器存儲說明:

  • 容器 1:使用鏡像 1 啟動
  • 容器 2:使用鏡像 1 啟動
  • 容器 3:使用鏡像 2 啟動

容器 1 和容器 2 共享鏡像 1,且每個容器有自己的可寫層;

容器 1(2)和容器 3 共享鏡像 2 個層(Layer3、5);

通過上述例子可以看到,通過容器鏡像分層實作資料共享可以大幅減少容器服務對主機存儲的資源需求。

上面給出了容器讀寫層結構,而讀寫的原則:

對于讀:容器由這麼多層的資料組合而成,當不同層次的資料重複時,讀取的原則是上層資料覆寫下層資料;

對于寫:容器修改某個檔案時,都是在最上層的讀寫層進行。主要實作技術有:寫時複制、用時配置。

1)寫時複制

寫時複制(CoW:copy-on-write),表示隻在需要寫時才去複制,是針對已有檔案的修改場景。CoW 技術可以讓所有的容器共享 image 的檔案系統,所有資料都從 image 中讀取,隻有當要對檔案進行寫操作時,才從 image 裡把要寫的檔案複制到最上面的讀寫層進行修改。是以無論有多少個容器共享同一個 image,所做的寫操作都是對從 image 中複制後在複本上進行,并不會修改 image 的源檔案,且多個容器操作同一個檔案,會在每個容器的檔案系統裡生成一個複本,每個容器修改的都是自己的複本,互相隔離,互相不影響。

2)用時配置

用時配置設定:在鏡像中原本沒有某個檔案的場景,隻有在要新寫入一個檔案時才配置設定空間,這樣可以提高存儲資源的使用率。比如啟動一個容器,并不會為這個容器預配置設定一些磁盤空間,而是當有新檔案寫入時,才按需配置設定新空間。

2. 存儲驅動

存儲驅動是指如何對容器的各層資料進行管理,已達到上述需要實作共享、可讀寫的效果。即:容器存儲驅動實作了容器讀寫層資料的存儲和管理。常見的存儲驅動:

  • AUFS
  • OverlayFS
  • Devicemapper
  • Btrfs
  • ZFS

以 AUFS 為例,我們來講述一下存儲驅動的工作原理:

雲原生存儲詳解:容器存儲與 K8s 存儲卷

AUFS 是一種聯合檔案系統(UFS),是檔案級的存儲驅動。

AUFS 是一個能透明疊加一個或多個現有檔案系統的層狀檔案系統,把多層檔案系統合并成單層表示。即:支援将不同目錄挂載到同一個虛拟檔案系統下的檔案系統。

可以一層一層地疊加修改檔案,其底層都是隻讀的,隻有最上層的檔案系統是可寫的。

當需要修改一個檔案時,AUFS 建立該檔案的一個副本,使用 CoW 将檔案從隻讀層複制到可寫層進行修改,結果也儲存在可寫層。

在 Docker 中,底下的隻讀層就是 image,可寫層就是 Container 運作時。

其他各種存儲驅動這裡不再細講,有興趣的同學可以到網上查詢資料。

3. Docker 資料卷介紹

容器中的應用讀寫資料都是發生在容器的讀寫層,鏡像層+讀寫層映射為容器内部檔案系統、負責容器内部存儲的底層架構。當我們需要容器内部應用和外部存儲進行互動時,需要一個類似于計算機 U 盤一樣的外置存儲,容器資料卷即提供了這樣的功能。

另一方面:容器本身的存儲資料都是臨時存儲,在容器銷毀的時候資料會一起删除。而通過資料卷将外部存儲挂載到容器檔案系統,應用可以引用外部資料,也可以将自己産出的資料持久化到資料卷中,是以容器資料卷是容器進行資料持久化的實作方式。

容器存儲組成:隻讀層(容器鏡像) + 讀寫層 + 外置存儲(資料卷)
           

容器資料卷從作用範圍可以分為:單機資料卷 和 叢集資料卷。單機資料卷即為容器服務在一個節點上的資料卷挂載能力,docker volume 是單機資料卷的代表實作;叢集資料卷則關注的是叢集級别的資料卷編排能力,K8s 資料卷則是叢集資料卷的主要應用方式。

Docker Volume 是一個可供多個容器使用的目錄,它繞過 UFS,包含以下特性:

  • 資料卷可以在容器之間共享和重用;
  • 相比通過存儲驅動實作的可寫層,資料卷讀寫是直接對外置存儲進行讀寫,效率更高;
  • 對資料卷的更新是對外置存儲讀寫,不會影響鏡像和容器讀寫層;
  • 資料卷可以一直存在,直到沒有容器使用。

1)Docker 資料卷類型

Bind:将主機目錄/檔案直接挂載到容器内部。

  • 需要使用主機的上的絕對路徑,且可以自動建立主機目錄;
  • 容器可以修改挂載目錄下的任何檔案,是應用更具有便捷性,但也帶來了安全隐患。

Volume:使用第三方資料卷的時候使用這種方式。

  • Volume指令行指令:docker volume (create/rm);
  • 是Docker提供的功能,是以在非 docker 環境下無法使用;
  • 分為命名資料卷和匿名資料卷,其實作是一緻的,差別是匿名資料卷的名字為随機碼;
  • 支援資料卷驅動擴充,實作更多外部存儲類型的接入。

Tmpfs:非持久化的卷類型,存儲在記憶體中。

資料易丢失。

2)Bind 挂載方式文法

-v: src:dst:opts 隻支援單機版。

  • Src:表示卷映射源,主機目錄或檔案,需要是絕對位址;
  • Dst:容器内目标挂載位址;
  • Opts:可選,挂載屬性:ro, consistent, delegated, cached, z, Z;
  • Consistent, delegated, cached:為mac系統配置共享傳播屬性;
  • Z、z:配置主機目錄的selinux label。

示例:

$ docker run -d --name devtest -v /home:/data:ro,rslave nginx
$ docker run -d --name devtest --mount type=bind,source=/home,target=/data,readonly,bind-propagation=rslave nginx
$ docker run -d --name devtest -v /home:/data:z nginx
           

3)Volume 挂載方式文法

  • Src:表示卷映射源,資料卷名、空;
  • Dst:容器内目标目錄;
  • Opts:可選,挂載屬性:ro(隻讀)。
$ docker run -d --name devtest -v myvol:/app:ro nginx
$ docker run -d --name devtest --mount source=myvol2,target=/app,readonly nginx
           

4. Docker 資料卷使用

Docker 資料卷使用方式:

1)Volume 類型

  • 匿名資料卷:docker run –d -v /data3 nginx;
  • 會主機上預設建立目錄:/var/lib/docker/volumes/{volume-id}/_data進行映射;
  • 命名資料卷:docker run –d -v nas1:/data3 nginx;
  • 如果目前找不到nas1卷,會建立一個預設類型(local)的卷。

2)Bind 方式

docker run -d -v /test:/data nginx

如果主機上沒有/test目錄,則預設建立此目錄。

3)資料卷容器

資料卷容器是一個運作中的容器,其他容器可以繼承此容器中的挂載資料卷,則此容器的所有挂載都會在引用容器中展現。

docker run -d --volumes-from nginx1 -v /test1:/data1 nginx

繼承所有來自配置容器的資料卷,并包含自己定義的卷。

4)資料卷的挂載傳播

Docker volume 支援挂載傳播的配置:Propagation。

  • Private:挂載不傳播,源目錄和目标目錄中的挂載都不會在另一方展現;
  • Shared:挂載會在源和目的之間傳播;
  • Slave:源對象的挂載可以傳播到目的對象,反之不行;
  • Rprivate:遞歸 Private,預設方式;
  • Rshared:遞歸 Shared;
  • Rslave:遞歸 Slave。
$ docker run –d -v /home:/data:shared nginx
表示:主機/home下面挂載的目錄,在容器/data下面可用,反之可行;
$ docker run –d -v /home:/data:slave nginx
表示:主機/home下面挂載的目錄,在容器/data下面可用,反之不行;
           

5)資料卷挂載的可見性

Volume 挂載可見性:

  • 本地空目錄、鏡像空目錄:無特殊處理;
  • 本地空目錄、鏡像非空目錄:鏡像目錄的内容拷貝到主機;(是拷貝,不是映射;即使容器删除内容也會儲存);
  • 本地非空目錄、鏡像空目錄:本地目錄内容映射到容器;
  • 本地非空目錄、鏡像非空目錄:本地目錄内容映射到容器,容器目錄的内容被隐藏。

Bind 挂載可見性:以主機目錄為準。

  • 本地空目錄、鏡像非空目錄:容器目錄變成空;
  • 本地非空目錄、鏡像非空目錄:本地目錄内容映射到容器,容器目錄的内容被隐藏。

5. Docker資料卷插件

Docker 資料卷實作了将容器外部存儲挂載到容器檔案系統的方式。為了擴充容器對外部存儲類型的需求,docker 提出了通過存儲插件的方式挂載不同類型的存儲服務。擴充插件統稱為 Volume Driver,可以為每種存儲類型開發一種存儲插件。

  • 單個節點上可以部署多個存儲插件;
  • 一個存儲插件負責一種存儲類型的挂載服務。
雲原生存儲詳解:容器存儲與 K8s 存儲卷

Docker Daemon 與 Volume driver 通信方式有:

  • Sock檔案:linux 下放在/run/docker/plugins 目錄下
  • Spec檔案:/etc/docker/plugins/convoy.spec 定義
  • Json檔案:/usr/lib/docker/plugins/infinit.json 定義

實作接口:

Create, Remove, Mount, Path, Umount, Get, List, Capabilities;

使用示例:

$ docker volume create --driver nas -o diskid="" -o host="10.46.225.247" -o path=”/nas1" -o mode="" --name nas1
           

Docker Volume Driver 适用在單機容器環境或者 swarm 平台進行資料卷管理,随着 K8s 的流行其使用場景已經越來越少,關于 VolumeDriver 的詳細介紹這裡不在細講,有興趣可以參考:https://docs.docker.com/engine/extend/plugins_volume/

K8s 存儲卷

1. 基礎概念

根據之前的描述,為了實作容器資料的持久化我們需要使用資料卷的功能,在 K8s 編排系統中如何為運作的負載(Pod)定義存儲呢?K8s 是一個容器編排系統,其關注的是容器應用在整個叢集的管理和部署形式,是以在考慮 K8s 應用存儲的時候就需要從叢集角度考慮。K8s 存儲卷定義了在 K8s 系統中應用與存儲的關聯關系。其包含以下概念:

1)Volume 資料卷

資料卷定義了外置存儲的細節,并内嵌到 Pod 中作為 Pod 的一部分。其實質是外置存儲在 K8s 系統的一個記錄對象,當負載需要使用外置存儲的時候,從資料卷中查到相關資訊并進行存儲挂載操作。

  • 生命周期和 Pod 一緻,即 pod 被删除的時候資料卷也一起消失(注意不是資料删除);
  • 存儲細節定義在編排模闆中,應用編排感覺存儲細節;
  • 一個負載(Pod)中可以同時定義多個 volume,可以是相同類型或不同類型的存儲;
  • Pod 的每個 container 可以引用一個或多個 volume,不同 container 可以同時使用相同 volume。

K8S Volume 常用類型:

  • 本地存儲:如 HostPath、emptyDir,這些存儲卷的特點是,資料儲存在叢集的特定節點上,并且不能随着應用飄逸,節點當機時資料即不再可用;
  • 網絡存儲:Ceph、Glusterfs、NFS、Iscsi 等類型,這些存儲卷的特點是資料不在叢集的某個節點上,而是在遠端的存儲服務上,使用存儲卷時需要将存儲服務挂載到本地使用;
  • Secret/ConfigMap:這些存儲卷類型,其資料是叢集的一些對象資訊,并不屬于某個節點,使用時将對象資料以卷的形式挂載到節點上供應用使用;
  • CSI/Flexvolume:這是兩種資料卷擴容方式,可以了解為抽象的資料卷類型。每種擴充方式都可再細化成不同的存儲類型;
  • PVC:一種資料卷定義方式,将資料卷抽象成一個獨立于 pod 的對象,這個對象定義(關聯)的存儲資訊即存儲卷對應的真正存儲資訊,供 K8s 負載挂載使用。

一些 volume 模闆示例如下:

volumes:
  - name: hostpath
    hostPath:
      path: /data
      type: Directory
---
  volumes:
  - name: disk-ssd
    persistentVolumeClaim:
      claimName: disk-ssd-web-0
  - name: default-token-krggw
    secret:
      defaultMode: 420
      secretName: default-token-krggw
---
  volumes:
    - name: "oss1"
      flexVolume:
        driver: "alicloud/oss"
        options:
          bucket: "docker"
          url: "oss-cn-hangzhou.aliyuncs.com"
           

2)PVC和PV

  • K8s 存儲卷是一個叢集級别的概念,其對象作用範圍是整個 K8s 叢集,而不是而一個節點;
  • K8s 存儲卷包含一些對象(PVC、PV、SC),這些對象和應用負載(Pod)是獨立,通過編排模闆進行關聯;
  • K8s 存儲卷可以有自己的獨立生命周期,不依附于 Pod。

PVC 是 PersistentVolumeClaim 的縮寫,譯為存儲聲明;PVC 是在 K8s 中一種抽象的存儲卷類型,代表了某個具體類型存儲的資料卷表達。其設計意圖是:存儲與應用編排分離,将存儲細節抽象出來并實作存儲的編排(存儲卷)。這樣 K8s 中存儲卷對象獨立于應用編排而單獨存在,在編排層面使應用和存儲解耦。

PV 是 PersistentVolume 的縮寫,譯為持久化存儲卷;PV 在 K8s 中代表一個具體存儲類型的卷,其對象中定義了具體存儲類型和卷參數。即目标存儲服務所有相關的資訊都儲存在 PV 中,K8s 引用 PV 中的存儲資訊執行挂載操作。

應用負載、PVC、PV 的關聯關系為:

雲原生存儲詳解:容器存儲與 K8s 存儲卷

從實作上看,隻要有了 PV 既可以實作存儲和應用的編排分離,也能實作資料卷的挂載,為何要用 PVC + PV 兩個對象呢?K8s 這樣設計是從應用角度對存儲卷進行二次抽象;由于 PV 描述的是對具體存儲類型,需要定義詳細的存儲資訊,而應用層使用者在消費存儲服務的時候往往不希望對底層細節知道的太多,讓應用編排層面來定義具體的存儲服務不夠友好。這時對存儲服務再次進行抽象,隻把使用者關系的參數提煉出來,用 PVC 來抽象更底層的 PV。是以 PVC、PV 關注的對象不一樣,PVC 關注使用者對存儲需求,給使用者提供統一的存儲定義方式;而 PV 關注的是存儲細節,可以定義具體存儲類型、存儲挂載使用的詳細參數等。

使用時應用層會聲明一個對存儲的需求(PVC),而 K8s 會通過最佳比對的方式選擇一個滿足 PVC 需求的 PV,并與之綁定。是以從職責上 PVC 是應用所需要的存儲對象,屬于應用作用域(和應用處于一個名詞空間);PV 是存儲平面的存儲對象,屬于整個存儲域(不屬于某個名詞空間);

下面給出 PVC、PV 的一些屬性:

  • PVC 和 PV 總是成對出現的,PVC 必須與 PV 綁定後才能被應用(Pod)消費;
  • PVC 和 PV 是一一綁定關系,不存在一個 PV 被多個 PVC 綁定,或者一個 PVC 綁定多個 PV 的情況;
  • PVC 是應用層面的存儲概念,是屬于具體的名詞空間的;
  • PV 是存儲層面的存儲概念,是叢集級别的,不屬于某個名詞空間;PV 常由專門的存儲運維人員負責管理;
  • 消費關系上:Pod 消費 PVC,PVC 消費 PV,而 PV 定義了具體的存儲媒體。

3)PVC 詳細定義

PVC 定義的模闆如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: disk-ssd-web-0
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: alicloud-disk-available
  volumeMode: Filesystem
           

PVC 定義的存儲接口包括:存儲的讀寫模式、資源容量、卷模式等;主要參數說明如下:

accessModes:存儲卷的通路模式,支援:ReadWriteOnce、ReadWriteMany、ReadOnlyMany 三種模式。

  • ReadWriteOnce 表示 pvc 隻能同時被一個 pod 以讀寫方式消費;
  • ReadWriteMany 可以同時被多個 pod 以讀寫方式消費;
  • ReadOnlyMany 表示可以同時被多個 pod 以隻讀方式消費;
注意:這裡定義的通路模式隻是編排層面的聲明,具體應用在讀寫存儲檔案的時候是否可讀可寫,需要具體的存儲插件實作确定。

storage:定義此 PVC 對象期望提供的存儲容量,同樣此處的資料大小也隻是編排聲明的值,具體存儲容量要看底層存儲服務類型。

volumeMode:表示存儲卷挂載模式,支援 FileSystem、Block 兩種模式;

FileSystem:将資料卷挂載成檔案系統的方式供應用使用;

Block:将資料卷挂載成塊裝置的形式供應用使用。

4)PV 詳細定義

下面為雲盤資料卷 PV 對象的編排示例:

apiVersion: v1
kind: PersistentVolume
metadata:
  labels:
    failure-domain.beta.kubernetes.io/region: cn-shenzhen
    failure-domain.beta.kubernetes.io/zone: cn-shenzhen-e
  name: d-wz9g2j5qbo37r2lamkg4
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 30Gi
  flexVolume:
    driver: alicloud/disk
    fsType: ext4
    options:
      VolumeId: d-wz9g2j5qbo37r2lamkg4
  persistentVolumeReclaimPolicy: Delete
  storageClassName: alicloud-disk-available
  volumeMode: Filesystem
           
  • accessModes:存儲卷的通路模式,支援:ReadWriteOnce、ReadWriteMany、ReadOnlyMany 三種模式;具體含義同 PVC 字段;
  • capacity:定義存儲卷容量;
  • persistentVolumeReclaimPolicy:定義回收政策,即删除 pvc 的時候如何處理 PV;支援 Delete、Retain 兩種類型,動态資料卷部分會詳細說明此參數;
  • storageClassName:表示存儲卷的使用的存儲類名字,動态資料卷部分會詳細說明此參數;
  • volumeMode:同 PVC 中的 volumeMode 定義;
  • Flexvolume:此字段表示具體的存儲類型,這裡 Flexvolume 為一種抽象的存儲類型,并在 flexvolume 的子配置項中定義了具體的存儲類型、存儲參數。

5)PVC/PV 綁定

PVC 隻有綁定了 PV 之後才能被 Pod 使用,而 PVC 綁定 PV 的過程即是消費 PV 的過程,這個過程是有一定規則的,下面規則都滿足的 PV 才能被 PVC 綁定:

  • VolumeMode:被消費 PV 的 VolumeMode 需要和 PVC 一緻;
  • AccessMode:被消費 PV 的 AccessMode 需要和 PVC 一緻;
  • StorageClassName:如果 PVC 定義了此參數,PV 必須有相關的參數定義才能進行綁定;
  • LabelSelector:通過 label 比對的方式從 PV 清單中選擇合适的 PV 綁定;
  • storage:被消費 PV 的 capacity 必須大于或者等于 PVC 的存儲容量需求才能被綁定。

滿足上述所有需要的 PV 才可以被 PVC 綁定。

如果同時有多個 PV 滿足需求,則需要從 PV 中選擇一個更合适的進行綁定;通常選擇容量最小的,如果容量最小的也有多個,則随機選擇。

如果沒有滿足上述需求的 PV 存儲,則 PVC 會處于 Pending 狀态,等待有合适的 PV 出現了再進行綁定。

2. 靜态、動态存儲卷

從上面的讨論我們了解到,PVC 是針對應用服務對存儲的二次抽象,具有簡潔的存儲定義接口。而 PV 是具有繁瑣存儲細節的存儲抽象,一般有專門的叢集管理人員定義、維護。

根據 PV 的建立方式可以将存儲卷分為動态存儲和靜态存儲卷:

  • 靜态存儲卷:由管理者建立的 PV
  • 動态存儲卷:由 Provisioner 插件建立的 PV

1)靜态存儲卷

一般先由叢集管理者分析叢集中存儲需求,并預先配置設定一些存儲媒體,同時建立對應的 PV 對象,建立好的 PV 對象等待 PVC 來消費。如果負載中定義了 PVC 需求,K8s 會通過相關規則實作 PVC 和比對的 PV 進行綁定,這樣就實作了應用對存儲服務的通路能力。

2)動态存儲卷

由叢集管理者配置好後端的存儲池,并建立相應的模闆(storageclass),等到有 PVC 需要消費 PV 的時候,根據 PVC 定義的需求,并參考 storageclass 的存儲細節,由 Provisioner 插件動态建立一個 PV。

兩種卷的比較:

  • 動态存儲卷和靜态存儲卷最終的效果都是:Pod -> PVC -> PV 的使用鍊路,且對象的具體模闆定義都是一緻的;
  • 動态存儲卷和靜态存儲卷差別是:動态卷是插件自動建立 PV,而靜态卷是叢集管理者手動建立 PV。

提供動态存儲卷的優勢:

  • 動态卷讓 K8s 實作了 PV 的自動化生命周期管理,PV 的建立、删除都通過 Provisioner 完成;
  • 自動化建立 PV 對象,減少了配置複雜度和系統管理者的工作量;
  • 動态卷可以實作 PVC 對存儲的需求容量和 Provision 出來的 PV 容量一緻,實作存儲容量規劃最優。

3)動态卷的實作流程

當使用者聲明一個 PVC 時,如果在 PVC 中添加了 StorageClassName 字段,其意圖為:當 PVC 在叢集中找不到比對的 PV 時,會根據 StorageClassName 的定義觸發相應的 Provisioner 插件建立合适的 PV 供綁定,即建立動态資料卷;動态資料卷時由 Provisioner 插件建立的,并通過 StorageClassName 與 PVC 進行關聯。

StorageClass 可譯為存儲類,表示為一個建立 PV 存儲卷的模闆;在 PVC 觸發自動建立PV的過程中,即使用 StorageClass 對象中的内容進行建立。其内容包括:目标 Provisioner 名字,建立 PV 的詳細參數,回收模式等配置。

StorageClasss 模闆定義如下:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: alicloud-disk-topology
parameters:
  type: cloud_ssd
provisioner: diskplugin.csi.alibabacloud.com
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
           
  • provisioner:為一個注冊插件的名字,此插件實作了建立 PV 的功能;一個 StorageClass 隻能定義一個Provisioner;
  • parameters:表示建立資料卷的具體參數;例如這裡表示建立一個 SSD 類型的雲盤;
  • reclaimPolicy:用來指定建立 PV 的 persistentVolumeReclaimPolicy 字段值,支援 Delete/Retain;Delete 表示動态建立的 PV,在銷毀的時候也會自動銷毀;Retain 表示動态建立的 PV,不會自動銷毀,而是由管理者來處理;
  • allowVolumeExpansion:定義由此存儲類建立的 PV 是否運作動态擴容,預設為 false;是否能動态擴容是有底層存儲插件來實作的,這裡隻是一個開關;
  • volumeBindingMode:表示動态建立 PV 的時間,支援 Immediate/WaitForFirstConsumer;分别表示立即建立和延遲建立。
雲原生存儲詳解:容器存儲與 K8s 存儲卷

使用者建立一個 PVC 聲明時,會在叢集尋找合适的 PV 進行綁定,如果沒有合适的 PV 與之綁定,則觸發下面流程:

  • Volume Provisioner 會 watch 到這個 PVC 的存在,若這個 PVC 定義了 StorageClassName,且 StorageClass 對象中定義的 Provisioner 插件是自己,Provisioner 會觸發建立 PV 的流程;
  • Provisioner 根據 PVC 定義的參數(Size、VolumeMode、AccessModes)以及 StorageClass 定義的參數(ReclaimPolicy、Parameters)執行 PV 建立;
  • Provisioner 會在存儲媒體端建立資料卷(通過 API 調用,或者其他方式),完成後會建立 PV 對象;
  • PV 建立完成後,實作與 PVC 的綁定;以滿足後續的 Pod 啟動流程。

4)延遲綁定動态資料卷

某種存儲(阿裡雲雲盤)在挂載屬性上有所限制,隻能将相同可用區的資料卷和 Node 節點進行挂載,不在同一個可用區不可以挂載。這種類型的存儲卷通常遇到如下問題:

  • 建立了 A 可用區的資料卷,但是 A 可用區的節點資源已經耗光,導緻 Pod 啟動無法完成挂載;
  • 叢集管理者在規劃 PVC、PV 的時候不能确定在哪些可用區建立多個 PV 來備用。

StorageClass 中的 volumeBindingMode 字段正是用來解決此問題,如果将 volumeBindingMode 配置為 WaitForFirstConsumer 值,則表示 Provisioner 在收到 PVC Pending 的時候不會立即進行資料卷建立,而是等待這個 PVC 被 Pod 消費的時候才執行建立流程。

其實作原理是:

  • Provisioner 在收到 PVC Pending 狀态的時候不會立即進行資料卷建立,而是等待這個 PVC 被 Pod 消費;
  • 如果有 Pod 消費此 PVC,排程器發現 PVC 是延遲綁定,則 pv 繼續完成排程功能(後續會詳細講解存儲排程);且排程器會将排程結果 patch 到 PVC 的 metadata 中;
  • 當 Provisioner 發現 PVC 中寫入了排程資訊時,會根據排程資訊擷取建立目标資料卷的位置資訊(zone、Node),并觸發 PV 的建立流程。

通過上述流程可見:延遲綁定會先讓應用負載進行排程(确定有充足的資源供 pod 使用),然後再觸發動态卷的建立流程,這樣就避免了資料卷所在可用區沒有資源的問題,也避免了存儲預規劃的不準确性問題。

在多可用區叢集環境中,更推薦使用延遲綁定的動态卷方案,目前阿裡雲 ACK 叢集已經支援上述配置方案。

3. 使用示例

下面給出一個 pod 消費 PVC、PV 的例子:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nas-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  selector:
    matchLabels:
      alicloud-pvname: nas-csi-pv
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nas-csi-pv
  labels:
    alicloud-pvname: nas-csi-pv
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  flexVolume:
    driver: "alicloud/nas"
    options:
      server: "***-42ad.cn-shenzhen.extreme.nas.aliyuncs.com"
      path: "/share/nas"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-nas
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx1
        image: nginx:1.8
      - name: nginx2
        image: nginx:1.7.9
        volumeMounts:
          - name: nas-pvc
            mountPath: "/data"
      volumes:
        - name: nas-pvc
          persistentVolumeClaim:
            claimName: nas-pvc
           

模闆解析:

  • 此應用為 Deployment 方式編排的一個 Nginx 服務,每個 pod 包含 2 個容器:nginx1、nginx2;
  • 模闆中定義了 Volumes 字段,說明期望挂載資料卷給應用使用,此例中使用了 PVC 這種資料卷定義方式;
  • 應用内部:将資料卷 nas-pvc 挂載到 nginx2容器的 /data 目錄上;nginx1 容器并沒有挂載;
  • PVC(nas-pvc)定義為一個不小于 50G 容量、讀寫方式為 ReadWriteOnce 的存儲卷需求,且對 PV 有 Label 設定的需求;
  • PV(nas-csi-pv)定義為一個容量為 50G、讀寫方式為 ReadWriteOnce、回收模式為 Retain、類型為 Flexvolume 抽象類型的存儲卷,且具有 Label 配置;

根據 PVC、PV 綁定的邏輯,此 PV 符合 PVC 消費要求,則 PVC 會和此 PV 進行綁定,并供 pod 挂載使用。

總結

此篇文章較為詳細的講述了容器存儲的整體面貌,包括單機範圍的 Docker 資料卷、和叢集式的 K8s 資料卷;K8s 資料卷更多關注的時候叢集級别的存儲編排能力,同時也在節點上實作了具體的資料卷挂載流程。K8s 為了實作上述複雜的存儲卷編排能力,其實作架構也較為複雜,下節内容我們将為您介紹 K8s 的存儲架構和實作流程。

課程推薦

為了更多開發者能夠享受到 Serverless 帶來的紅利,這一次,我們集結了 10+ 位阿裡巴巴 Serverless 領域技術專家,打造出最适合開發者入門的 Serverless 公開課,讓你即學即用,輕松擁抱雲計算的新範式——Serverless。

點選即可免費觀看課程:https://developer.aliyun.com/learning/roadmap/serverless

“阿裡巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的公衆号。”

繼續閱讀