k8s-pv-pvc

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

測試文件掛載