k8s-pv-pvc

張鐵牛發表於2022-01-18

1. 簡介

持久卷(PersistentVolume,PV)是叢集中的一塊儲存,可以由管理員事先供應,或者 使用儲存類(Storage Class)來動態供應。 持久卷是叢集資源,就像節點也是叢集資源一樣。PV 持久卷和普通的 Volume 一樣,也是使用 卷外掛來實現的,只是它們擁有獨立於任何使用 PV 的 Pod 的生命週期。 此 API 物件中記述了儲存的實現細節,無論其背後是 NFS、iSCSI 還是特定於雲平臺的儲存系統。

持久卷申領(PersistentVolumeClaim,PVC)表達的是使用者對儲存的請求。概念上與 Pod 類似。 Pod 會耗用節點資源,而 PVC 申領會耗用 PV 資源。Pod 可以請求特定數量的資源(CPU 和記憶體);同樣 PVC 申領也可以請求特定的大小和訪問模式 (例如,可以要求 PV 卷能夠以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一來掛載)。

儘管 PersistentVolumeClaim 允許使用者消耗抽象的儲存資源,常見的情況是針對不同的 問題使用者需要的是具有不同屬性(如,效能)的 PersistentVolume 卷。 叢集管理員需要能夠提供不同性質的 PersistentVolume,並且這些 PV 卷之間的差別不 僅限於卷大小和訪問模式,同時又不能將卷是如何實現的這些細節暴露給使用者。 為了滿足這類需求,就有了 儲存類(StorageClass) 資源。

2. 生命週期

PV 卷是叢集中的資源。PVC 申領是對這些資源的請求,也被用來執行對資源的申領檢查。 PV 卷和 PVC 申領之間的互動遵循如下生命週期:

2.1 供應

PV 卷的供應有兩種方式:靜態供應或動態供應。

2.1.1 靜態供應

叢集管理員建立若干 PV 卷。這些卷物件帶有真實儲存的細節資訊,並且對叢集 使用者可用(可見)。PV 卷物件存在於 Kubernetes API 中,可供使用者消費(使用)。

2.1.2 動態供應

如果管理員所建立的所有靜態 PV 卷都無法與使用者的 PersistentVolumeClaim 匹配, 叢集可以嘗試為該 PVC 申領動態供應一個儲存卷。 這一供應操作是基於 StorageClass 來實現的:PVC 申領必須請求某個 儲存類,同時叢集管理員必須 已經建立並配置了該類,這樣動態供應卷的動作才會發生。 如果 PVC 申領指定儲存類為 "",則相當於為自身禁止使用動態供應的卷。

2.2 繫結

使用者建立一個帶有特定儲存容量和特定訪問模式需求的 PersistentVolumeClaim 物件; 在動態供應場景下,這個 PVC 物件可能已經建立完畢。 主控節點中的控制迴路監測新的 PVC 物件,尋找與之匹配的 PV 卷(如果可能的話), 並將二者繫結到一起。 如果為了新的 PVC 申領動態供應了 PV 卷,則控制迴路總是將該 PV 卷繫結到這一 PVC 申領。 否則,使用者總是能夠獲得他們所請求的資源,只是所獲得的 PV 卷可能會超出所請求的配置。 一旦繫結關係建立,則 PersistentVolumeClaim 繫結就是排他性的,無論該 PVC 申領是 如何與 PV 卷建立的繫結關係。 PVC 申領與 PV 卷之間的繫結是一種一對一的對映,實現上使用 ClaimRef 來記述 PV 卷 與 PVC 申領間的雙向繫結關係。

如果找不到匹配的 PV 卷,PVC 申領會無限期地處於未繫結狀態。 當與之匹配的 PV 卷可用時,PVC 申領會被繫結。 例如,即使某叢集上供應了很多 50 Gi 大小的 PV 卷,也無法與請求 100 Gi 大小的儲存的 PVC 匹配。當新的 100 Gi PV 卷被加入到叢集時,該 PVC 才有可能被繫結。

2.3 使用

Pod 將 PVC 申領當做儲存捲來使用。叢集會檢視 PVC 申領,找到所繫結的卷,並 為 Pod 掛載該卷。對於支援多種訪問模式的卷,使用者要在 Pod 中以卷的形式使用申領 時指定期望的訪問模式。

一旦使用者有了申領物件並且該申領已經被繫結,則所繫結的 PV 卷在使用者仍然需要它期間 一直屬於該使用者。使用者通過在 Pod 的 volumes 塊中包含 persistentVolumeClaim 節區來排程 Pod,訪問所申領的 PV 卷。

2.4 保護使用中的儲存物件

保護使用中的儲存物件(Storage Object in Use Protection)這一功能特性的目的 是確保仍被 Pod 使用的 PersistentVolumeClaim(PVC)物件及其所繫結的 PersistentVolume(PV)物件在系統中不會被刪除,因為這樣做可能會引起資料丟失。

說明: 當使用某 PVC 的 Pod 物件仍然存在時,認為該 PVC 仍被此 Pod 使用。

如果使用者刪除被某 Pod 使用的 PVC 物件,該 PVC 申領不會被立即移除。 PVC 物件的移除會被推遲,直至其不再被任何 Pod 使用。 此外,如果管理員刪除已繫結到某 PVC 申領的 PV 卷,該 PV 卷也不會被立即移除。 PV 物件的移除也要推遲到該 PV 不再繫結到 PVC。

你可以看到當 PVC 的狀態為 Terminating 且其 Finalizers 列表中包含 kubernetes.io/pvc-protection 時,PVC 物件是處於被保護狀態的。

kubectl describe pvc hostpath
Name:          hostpath
Namespace:     default
StorageClass:  example-hostpath
Status:        Terminating
Volume:
Labels:        <none>
Annotations:   volume.beta.kubernetes.io/storage-class=example-hostpath
               volume.beta.kubernetes.io/storage-provisioner=example.com/hostpath
Finalizers:    [kubernetes.io/pvc-protection]
...

你也可以看到當 PV 物件的狀態為 Terminating 且其 Finalizers 列表中包含 kubernetes.io/pv-protection 時,PV 物件是處於被保護狀態的。

kubectl describe pv task-pv-volume
Name:            task-pv-volume
Labels:          type=local
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    standard
Status:          Terminating
Claim:
Reclaim Policy:  Delete
Access Modes:    RWO
Capacity:        1Gi
Message:
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /tmp/data
    HostPathType:
Events:            <none>

2.5 回收

當使用者不再使用其儲存卷時,他們可以從 API 中將 PVC 物件刪除,從而允許 該資源被回收再利用。PersistentVolume 物件的回收策略告訴叢集,當其被 從申領中釋放時如何處理該資料卷。 目前,資料卷可以被 Retained(保留)、Recycled(回收)或 Deleted(刪除)。

保留(Retain)

回收策略 Retain 使得使用者可以手動回收資源。當 PersistentVolumeClaim 物件 被刪除時,PersistentVolume 卷仍然存在,對應的資料卷被視為"已釋放(released)"。 由於捲上仍然存在這前一申領人的資料,該卷還不能用於其他申領。 管理員可以通過下面的步驟來手動回收該卷:

  1. 刪除 PersistentVolume 物件。與之相關的、位於外部基礎設施中的儲存資產 (例如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)在 PV 刪除之後仍然存在。
  2. 根據情況,手動清除所關聯的儲存資產上的資料。
  3. 手動刪除所關聯的儲存資產。

如果你希望重用該儲存資產,可以基於儲存資產的定義建立新的 PersistentVolume 卷物件。

刪除(Delete)

對於支援 Delete 回收策略的卷外掛,刪除動作會將 PersistentVolume 物件從 Kubernetes 中移除,同時也會從外部基礎設施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所關聯的儲存資產。 動態供應的卷會繼承其 StorageClass 中設定的回收策略,該策略預設 為 Delete。 管理員需要根據使用者的期望來配置 StorageClass;否則 PV 卷被建立之後必須要被 編輯或者修補。

回收(Recycle)

警告: 回收策略 Recycle 已被廢棄。取而代之的建議方案是使用動態供應。

2.6 預留 PersistentVolume

通過在 PersistentVolumeClaim 中指定 PersistentVolume,你可以宣告該特定 PV 與 PVC 之間的繫結關係。如果該 PersistentVolume 存在且未被通過其 claimRef 欄位預留給 PersistentVolumeClaim,則該 PersistentVolume 會和該 PersistentVolumeClaim 繫結到一起。

繫結操作不會考慮某些卷匹配條件是否滿足,包括節點親和性等等。 控制面仍然會檢查 儲存類、訪問模式和所請求的 儲存尺寸都是合法的。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: foo-pvc
  namespace: foo
spec:
  storageClassName: "" # 此處須顯式設定空字串,否則會被設定為預設的 StorageClass
  volumeName: foo-pv
  ...

此方法無法對 PersistentVolume 的繫結特權做出任何形式的保證。 如果有其他 PersistentVolumeClaim 可以使用你所指定的 PV,則你應該首先預留 該儲存卷。你可以將 PV 的 claimRef 欄位設定為相關的 PersistentVolumeClaim 以確保其他 PVC 不會繫結到該 PV 卷。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: foo-pv
spec:
  storageClassName: ""
  claimRef:
    name: foo-pvc
    namespace: foo
  ...

如果你想要使用 claimPolicy 屬性設定為 Retain 的 PersistentVolume 卷 時,包括你希望複用現有的 PV 卷時,這點是很有用的

3. 安裝nfs

網路檔案系統,英文Network File System(NFS),能使使用者訪問網路上別處的檔案就像在使用自己的計算機一樣。

3.1 所有節點

#所有機器安裝
$ yum install -y nfs-utils

3.2 nfs server 節點

#nfs主節點
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports

mkdir -p /nfs/data
systemctl enable rpcbind --now
systemctl enable nfs-server --now
#配置生效
exportfs -r

3.3 從節點

# 檢視nfs伺服器共享資訊
# 引數:-e 指定ip 如果不指定檢視當前服務的資訊
# nfs server ip 為3.1.2 伺服器的ip
showmount -e <nfs server ip>

#執行以下命令掛載 nfs 伺服器上的共享目錄到本機路徑 /nfs/data
mkdir -p /nfs/data
mount -t nfs <nfs server ip>:/nfs/data /nfs/data
# 寫入一個測試檔案
echo "hello nfs server" > /nfs/data/test.txt

**一定要注意,在NFS客戶端掛載/解除安裝NFS服務端目錄的時候 一定要事先退出掛載/解除安裝目錄之後,再進入掛載目錄檢視結果 ! **

4. 原生方式資料掛載

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-pv-demo
  name: nginx-pv-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-pv-demo
  template:
    metadata:
      labels:
        app: nginx-pv-demo
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
      volumes:
        - name: html
          nfs:
            # 掛載點資訊 需要提前建立好 掛載點的目錄01
            path: /nfs/data/01
            # nfs server ip
            server: 192.168.0.201

5. 建立pv

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv01
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: nfs-test
  nfs:
    # 掛載點資訊 需要提前建立好 掛載點的目錄01
    path: /nfs/data/01
    # nfs server ip
    server: 192.168.0.201

5.1 容量

一般而言,每個 PV 卷都有確定的儲存容量。 容量屬性是使用 PV 物件的 capacity 屬性來設定的。

目前,儲存大小是可以設定和請求的唯一資源。 未來可能會包含 IOPS、吞吐量等屬性。

5.2 卷模式

針對 PV 持久卷,Kubernetes 支援兩種卷模式(volumeModes):Filesystem(檔案系統)Block(塊)volumeMode 是一個可選的 API 引數。 如果該引數被省略,預設的卷模式是 Filesystem

volumeMode 屬性設定為 Filesystem 的卷會被 Pod 掛載(Mount) 到某個目錄。 如果卷的儲存來自某塊裝置而該裝置目前為空,Kuberneretes 會在第一次掛載卷之前 在裝置上建立檔案系統。

你可以將 volumeMode 設定為 Block,以便將卷作為原始塊裝置來使用。 這類卷以塊裝置的方式交給 Pod 使用,其上沒有任何檔案系統。 這種模式對於為 Pod 提供一種使用最快可能方式來訪問卷而言很有幫助,Pod 和 卷之間不存在檔案系統層。另外,Pod 中執行的應用必須知道如何處理原始塊裝置。 關於如何在 Pod 中使用 volumeMode: Block 的卷,可參閱 原始塊卷支援

5.3 訪問模式

PersistentVolume 卷可以用資源提供者所支援的任何方式掛載到宿主系統上。 如下表所示,提供者(驅動)的能力不同,每個 PV 卷的訪問模式都會設定為 對應卷所支援的模式值。 例如,NFS 可以支援多個讀寫客戶,但是某個特定的 NFS PV 卷可能在伺服器 上以只讀的方式匯出。每個 PV 卷都會獲得自身的訪問模式集合,描述的是 特定 PV 卷的能力。

訪問模式有:

  • ReadWriteOnce

    卷可以被一個節點以讀寫方式掛載。 ReadWriteOnce 訪問模式也允許執行在同一節點上的多個 Pod 訪問卷。

  • ReadOnlyMany

    卷可以被多個節點以只讀方式掛載。

  • ReadWriteMany

    卷可以被多個節點以讀寫方式掛載。

  • ReadWriteOncePod

    卷可以被單個 Pod 以讀寫方式掛載。 如果你想確保整個叢集中只有一個 Pod 可以讀取或寫入該 PVC, 請使用ReadWriteOncePod 訪問模式。這隻支援 CSI 卷以及需要 Kubernetes 1.22 以上版本。

重要提醒! 每個卷同一時刻只能以一種訪問模式掛載,即使該卷能夠支援 多種訪問模式。

5.4 類

每個 PV 可以屬於某個類(Class),通過將其 storageClassName 屬性設定為某個 StorageClass 的名稱來指定。 特定類的 PV 卷只能繫結到請求該類儲存卷的 PVC 申領。 未設定 storageClassName 的 PV 卷沒有類設定,只能繫結到那些沒有指定特定 儲存類的 PVC 申領。

5.5 回收策略

目前的回收策略有:

  • Retain -- 手動回收
  • Delete -- 自動刪除

5.6 節點親和性

每個 PV 卷可以通過設定節點親和性來定義一些約束,進而限制從哪些節點上可以訪問此卷。 使用這些卷的 Pod 只會被排程到節點親和性規則所選擇的節點上執行。 要設定節點親和性,配置 PV 卷 .spec 中的 nodeAffinity

說明: 對大多數型別的卷而言,你不需要設定節點親和性欄位。AWSEBSGCE PDAzure Disk卷型別都能 自動設定相關欄位。 你需要為 local 卷顯式地設定 此屬性。

5.7 階段

每個卷會處於以下階段(Phase)之一:

  • Available(可用)-- 卷是一個空閒資源,尚未繫結到任何申領;
  • Bound(已繫結)-- 該卷已經繫結到某申領;
  • Released(已釋放)-- 所繫結的申領已被刪除,但是資源尚未被叢集回收;
  • Failed(失敗)-- 卷的自動回收操作失敗。

命令列介面能夠顯示繫結到某 PV 卷的 PVC 物件。

6. 建立pvc

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc01
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 3Gi
  storageClassName: nfs-test
  #selector:
  #  matchLabels:
  #    release: "stable"
  #  matchExpressions:
  #    - {key: environment, operator: In, values: [dev]}

6.1 訪問模式

申領在請求具有特定訪問模式的儲存時,使用與卷相同的訪問模式約定

6.2 卷模式

申領使用與卷相同的約定來表明是將卷作為檔案系統還是塊裝置來使用。

6.3 資源

申領和 Pod 一樣,也可以請求特定數量的資源。在這個上下文中,請求的資源是儲存。

6.4 選擇算符

申領可以設定標籤選擇算符來進一步過濾卷集合。只有標籤與選擇算符相匹配的卷能夠繫結到申領上。 選擇算符包含兩個欄位:

  • matchLabels - 卷必須包含帶有此值的標籤
  • matchExpressions - 通過設定鍵(key)、值列表和操作符(operator) 來構造的需求。合法的操作符有 In、NotIn、Exists 和 DoesNotExist。

來自 matchLabelsmatchExpressions 的所有需求都按邏輯與的方式組合在一起。 這些需求都必須被滿足才被視為匹配。

6.5 類

申領可以通過為 storageClassName 屬性設定 StorageClass 的名稱來請求特定的儲存類。 只有所請求的類的 PV 卷,即 storageClassName 值與 PVC 設定相同的 PV 卷, 才能繫結到 PVC 申領。

PVC 申領不必一定要請求某個類。如果 PVC 的 storageClassName 屬性值設定為 "", 則被視為要請求的是沒有設定儲存類的 PV 卷,因此這一 PVC 申領只能繫結到未設定 儲存類的 PV 卷(未設定註解或者註解值為 "" 的 PersistentVolume(PV)物件在系統中不會被刪除,因為這樣做可能會引起資料丟失。 未設定 storageClassName 的 PVC 與此大不相同,也會被叢集作不同處理。 具體篩查方式取決於 DefaultStorageClass 准入控制器外掛 是否被啟用。

  • 如果准入控制器外掛被啟用,則管理員可以設定一個預設的 StorageClass。 所有未設定 storageClassName 的 PVC 都只能繫結到隸屬於預設儲存類的 PV 卷。 設定預設 StorageClass 的工作是通過將對應 StorageClass 物件的註解 storageclass.kubernetes.io/is-default-class 賦值為 true 來完成的。 如果管理員未設定預設儲存類,叢集對 PVC 建立的處理方式與未啟用准入控制器外掛 時相同。如果設定的預設儲存類不止一個,准入控制外掛會禁止所有建立 PVC 操作。
  • 如果准入控制器外掛被關閉,則不存在預設 StorageClass 的說法。 所有未設定 storageClassName 的 PVC 都只能繫結到未設定儲存類的 PV 卷。 在這種情況下,未設定 storageClassName 的 PVC 與 storageClassName 設定未 "" 的 PVC 的處理方式相同。

取決於安裝方法,預設的 StorageClass 可能在叢集安裝期間由外掛管理器(Addon Manager)部署到叢集中。

當某 PVC 除了請求 StorageClass 之外還設定了 selector,則這兩種需求會按 邏輯與關係處理:只有隸屬於所請求類且帶有所請求標籤的 PV 才能繫結到 PVC。

說明: 目前,設定了非空 selector 的 PVC 物件無法讓叢集為其動態供應 PV 卷。

7. 使用pvc

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      imagePullPolicy: IfNotPresent
      volumeMounts:
      - mountPath: "/usr/share/nginx/html/"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: pvc01

測試檔案掛載