Kubernetes 學習(十)Kubernetes 容器持久化存儲


0. 前言

  • 最近在學習張磊老師的 深入剖析Kubernetes 系列課程,最近學到了 Kubernetes 容器持久化存儲部分
  • 現對這一部分的相關學習和體會做一下整理,內容參考 深入剖析Kubernetes 原文,僅作為自己后續回顧方便
  • 希望詳細了解的同學可以移步至原文支持一下原作者
  • 參考原文:深入剖析Kubernetes

1. PV、PVC、StorageClass 關系梳理

1.1 相關概念

  • Volume:其實就是將一個宿主機上的目錄,跟一個容器里的目錄綁定掛載在了一起
  • 持久化 Volume:指的就是這個宿主機上的目錄,具備“持久性”
    • 即:這個目錄里面的內容,既不會因為容器的刪除而被清理掉,也不會跟當前的宿主機綁定
    • 這樣,當容器被重啟或者在其他節點上重建出來之后,它仍然能夠通過掛載這個 Volume,訪問到這些內容
    • 大多數情況下,持久化 Volume 的實現,往往依賴於一個遠程存儲服務,比如:遠程文件存儲(比如,NFS、GlusterFS)、遠程塊存儲(比如,公有雲提供的遠程磁盤)等等
    • 而 Kubernetes 需要做的工作,就是使用這些存儲服務,來為容器准備一個持久化的宿主機目錄,以供將來進行綁定掛載時使用
    • 而所謂“持久化”,指的是容器在這個目錄里寫入的文件,都會保存在遠程存儲中,從而使得這個目錄具備了“持久性”
  • PV:表示是持久化存儲數據卷對象。這個 API 對象定義了一個持久化存儲在宿主機上的目錄(如 NFS 的掛載目錄)
    • 通常情況下,PV 對象由運維人員事先創建在 Kubernetes 集群里,比如:
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 10.244.1.4
    path: "/"
View Code
  • PVC:表示 Pod 所希望使用的持久化存儲的屬性(如:Volume 存儲的大小、可讀寫權限等等) 
    • PVC 對象通常由開發人員創建,或者以 PVC 模板的方式成為 StatefulSet 的一部分,然后由 StatefulSet 控制器負責創建帶編號的 PVC。比如:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: manual
  resources:
    requests:
      storage: 1Gi
View Code
  • StorageClass:其實就是創建 PV 的模板。具體地說,StorageClass 對象會定義如下兩個部分內容:
    • 第一,PV 的屬性。比如,存儲類型、Volume 的大小等等
    • 第二,創建這種 PV 需要用到的存儲插件。比如,Ceph 等等
    • Kubernetes 只會將 StorageClass 相同的 PVC 和 PV 綁定起來
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
View Code

1.2 綁定條件

  • PVC 要真正被容器使用起來,就必須先和某個符合條件的 PV 通過兩個條件進行綁定:
    • 首先是 PV 和 PVC 的 spec 字段,比如 PV 的存儲(storage)大小,必須滿足 PVC 的要求
    • 其次是 PV 和 PVC 的 storageClassName 字段必須一樣
  • 在成功地將 PVC 和 PV 進行綁定之后,Pod 就能夠像使用 hostPath 等常規類型的 Volume 一樣,在自己的 YAML 文件里聲明使用這個 PVC 了,如:
    • Pod 可以在 volumes 字段里聲明自己要使用的 PVC 名字
    • 接下來,等這個 Pod 創建之后,kubelet 就會把這個 PVC 所對應的 PV,掛載在這個 Pod 容器內的目錄上
apiVersion: v1
kind: Pod
metadata:
  labels:
    role: web-frontend
spec:
  containers:
  - name: web
    image: nginx
    ports:
      - name: web
        containerPort: 80
    volumeMounts:
        - name: nfs
          mountPath: "/usr/share/nginx/html"
  volumes:
  - name: nfs
    persistentVolumeClaim:
      claimName: nfs
View Code

1.3 綁定關系

  • 從面相對象的角度思考,PVC 可以理解為持久化存儲的“接口”
  • 它提供了對某種持久化存儲的描述,但不提供具體的實現
  • 而這個持久化存儲的實現部分則由 PV 負責完成
  • 如果創建 Pod 的時候,系統里並沒有合適的 PV 跟它定義的 PVC 綁定,Pod 的啟動就會報錯
  • 在 Kubernetes 中,實際上存在着一個專門處理持久化存儲的控制器,叫作 Volume Controller
  • 這個 Volume Controller 維護着多個控制循環,其中有一個循環,扮演的就是撮合 PV 和 PVC 的“紅娘”的角色:PersistentVolumeController
    • PersistentVolumeController 會不斷地查看當前每一個 PVC,是不是已經處於 Bound(已綁定)狀態
    • 如果不是,那它就會遍歷所有可用的 PV,並嘗試將其與這個未綁定的 PVC 進行綁定
    • 這樣,Kubernetes 就可以保證用戶提交的每一個 PVC,只要有合適的 PV 出現,它就能夠很快進入綁定狀態
    • 而所謂將一個 PV 與 PVC 進行綁定,其實就是將這個 PV 對象的名字,填在了 PVC 對象的 spec.volumeName 字段上
    • 接下來 Kubernetes 只要獲取到這個 PVC 對象,就一定能夠找到它所綁定的 PV

1.4 持久化

  • 所謂容器的 Volume,其實就是將一個宿主機上的目錄,跟一個容器里的目錄綁定掛載在了一起
  • 而所謂的“持久化 Volume”,指的就是這個宿主機上的目錄,具備“持久性”:
    • 這個目錄里面的內容,既不會因為容器的刪除而被清理掉,也不會跟當前的宿主機綁定
    • 這樣,當容器被重啟或者在其他節點上重建出來之后,它仍然能夠通過掛載這個 Volume,訪問到這些內容
  • 前面使用的 hostPath 和 emptyDir 類型的 Volume 並不具備這個特征:
    • 它們既有可能被 kubelet 清理掉,也不能被“遷移”到其他節點上
  • 所以,大多數情況下,持久化 Volume 的實現,往往依賴於一個遠程存儲服務,比如:遠程文件存儲(比如 NFS、GlusterFS)、遠程塊存儲(比如公有雲提供的遠程磁盤)等等

1.4.1 兩階段處理

  • 而 Kubernetes 需要做的工作,就是使用這些存儲服務,來為容器准備一個持久化的宿主機目錄,以供將來進行綁定掛載時使用
  • 而所謂“持久化”,指的是容器在這個目錄里寫入的文件,都會保存在遠程存儲中,從而使得這個目錄具備了“持久性”
  • 這個准備“持久化”宿主機目錄的過程,稱為“兩階段處理”:
    • 當一個 Pod 調度到一個節點上之后,kubelet 就要負責為這個 Pod 創建它的 Volume 目錄
    • 默認情況下,kubelet 為 Volume 創建的目錄是如下所示的一個宿主機上的路徑:/var/lib/kubelet/pods/<Pod 的 ID>/volumes/kubernetes.io~<Volume 類型 >/<Volume 名字 >

1.4.1.1 Attach

  • 如果 Volume 類型是遠程塊存儲,那么 kubelet 就需要先調用相應的 API,將它所提供的 Persistent Disk 注冊到 Pod 所在的宿主機上
  • 這一步為虛擬機注冊遠程磁盤的操作,對應的正是“兩階段處理”的第一階段
  • 在 Kubernetes 中,我們把這個階段稱為 Attach
  • Kubernetes 提供的可用參數是 nodeName,即宿主機的名字

1.4.1.2 Mount

  • Attach 階段完成后,為了能夠使用這個遠程磁盤,kubelet 還要進行第二個操作,即:格式化這個磁盤設備,然后將它掛載到宿主機指定的掛載點上
  • 這個掛載點,正是在前面反復提到的 Volume 的宿主機目錄
  • 所以,這一步相當於執行:將磁盤設備格式化並掛載到 Volume 宿主機目錄的操作,對應的正是“兩階段處理”的第二個階段:Mount
  • Kubernetes 提供的可用參數是 dir,即 Volume 的宿主機目錄
  • Mount 階段完成后,這個 Volume 的宿主機目錄就是一個“持久化”的目錄了,容器在它里面寫入的內容,會保存在遠程磁盤中
  • 而如果你的 Volume 類型是遠程文件存儲(比如 NFS)的話,kubelet 的處理過程就會更簡單一些
  • 因為在這種情況下,kubelet 可以跳過 Attach 階段,因為一般來說,遠程文件存儲並沒有一個“存儲設備”需要注冊在宿主機上
  • 所以,kubelet 會直接從 Mount 階段開始准備宿主機上的 Volume 目錄
  • 在這一步,kubelet 需要作為 client,將遠端 NFS 服務器的目錄(比如:“/”目錄),掛載到 Volume 的宿主機目錄上
  • 即相當於執行如下所示的命令:mount -t nfs <NFS 服務器地址 >:/ /var/lib/kubelet/pods/<Pod 的 ID>/volumes/kubernetes.io~<Volume 類型 >/<Volume 名字 >
  • 通過這個掛載操作,Volume 的宿主機目錄就成為了一個遠程 NFS 目錄的掛載點
  • 后面你在這個目錄里寫入的所有文件,都會被保存在遠程 NFS 服務器上。所以,我們也就完成了對這個 Volume 宿主機目錄的“持久化”

1.4.2 后續工作

  • 經過兩階段處理,就得到了一個“持久化”的 Volume 宿主機目錄
  • 接下來,kubelet 只要把這個 Volume 目錄通過 CRI 里的 Mounts 參數,傳遞給 Docker,然后就可以為 Pod 里的容器掛載這個“持久化”的 Volume 了
  • 其實,這一步相當於執行了如下所示的命令:docker run -v /var/lib/kubelet/pods/<Pod 的 ID>/volumes/kubernetes.io~<Volume 類型 >/<Volume 名字 >:/< 容器內的目標目錄 > 我的鏡像 ...
  • 在 Kubernetes 中,上述關於 PV 的“兩階段處理”流程,是靠獨立於 kubelet 主控制循環(Kubelet Sync Loop)之外的兩個控制循環來實現的:
    • Attach(以及 Dettach)操作,是由 Volume Controller 負責維護的:AttachDetachController(不斷地檢查每一個 Pod 對應的 PV,和這個 Pod 所在宿主機之間掛載情況。從而決定,是否需要對這個 PV 進行操作)
    • 作為一個 Kubernetes 內置的控制器,Volume Controller 是 kube-controller-manager 的一部分
    • 所以,AttachDetachController 也一定是運行在 Master 節點上的
    • Mount(以及 Unmount)操作,必須發生在 Pod 對應的宿主機上,是 kubelet 組件的一部分,叫作 VolumeManagerReconciler,是一個獨立於 kubelet 主循環的 Goroutine
  • 通過這樣將 Volume 的處理同 kubelet 的主循環解耦,Kubernetes 就避免了這些耗時的遠程掛載操作拖慢 kubelet 的主控制循環,進而導致 Pod 的創建效率大幅下降的問題

1.5 StorageClass

  • 一個大規模的 Kubernetes 集群里很可能有成千上萬個 PVC,這就意味着運維人員必須得事先創建出成千上萬個 PV
  • 更麻煩的是,隨着新的 PVC 不斷被提交,運維人員就不得不繼續添加新的、能滿足條件的 PV,否則新的 Pod 就會因為 PVC 綁定不到 PV 而失敗
  • 在實際操作中,這幾乎沒辦法靠人工做到
  • 所以,Kubernetes 提供了一套可以自動創建 PV 的機制,即:Dynamic Provisioning
  • 相比之下,前面人工管理 PV 的方式就叫作 Static Provisioning
  • Dynamic Provisioning 機制工作的核心,在於一個名叫 StorageClass 的 API 對象
  • 而 StorageClass 對象的作用,其實就是創建 PV 的模板
  • 具體地說,StorageClass 對象會定義如下兩個部分內容:
    • PV 的屬性。比如存儲類型、Volume 的大小等等
    • 創建這種 PV 需要用到的存儲插件。比如 Ceph 等等
  • 有了這樣兩個信息之后,Kubernetes 就能夠根據用戶提交的 PVC,找到一個對應的 StorageClass
  • 然后,Kubernetes 就會調用該 StorageClass 聲明的存儲插件,創建出需要的 PV。比如:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
View Code
  • 在這個 YAML 文件里,我們定義了一個名叫 block-service 的 StorageClass
  • provisioner 字段的值是:kubernetes.io/gce-pd,這正是 Kubernetes 內置的 GCE PD 存儲插件的名字
  • parameters 字段,就是 PV 的參數。比如:上面例子里的 type=pd-ssd,指的是這個 PV 的類型是“SSD 格式的 GCE 遠程磁盤”
  • 作為應用開發者,我們只需要在 PVC 里指定要使用的 StorageClass 名字即可,如:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: block-service
  resources:
    requests:
      storage: 30Gi
View Code
  • 在 PVC 里添加了一個叫作 storageClassName 的字段,用於指定該 PVC 所要使用的 StorageClass 的名字是:block-service
  • 通過 kubectl create 創建上述 PVC 對象之后,Kubernetes 就會調用 Google Cloud 的 API,創建出一塊 SSD 格式的 Persistent Disk。然后,再使用這個 Persistent Disk 的信息,自動創建出一個對應的 PV 對象
  • 這個自動創建出來的 PV 的 StorageClass 字段的值,也是 block-service
  • 這是因為,Kubernetes 只會將 StorageClass 相同的 PVC 和 PV 綁定起來
  • 有了 Dynamic Provisioning 機制,運維人員只需要在 Kubernetes 集群里創建出數量有限的 StorageClass 對象就可以了
  • 當開發人員提交了包含 StorageClass 字段的 PVC 之后,Kubernetes 就會根據這個 StorageClass 創建出對應的 PV

1.6 小結

  • PVC 描述的是 Pod 想要使用的持久化存儲的屬性,比如存儲的大小、讀寫權限等
  • PV 描述的,則是一個具體的 Volume 的屬性,比如 Volume 的類型、掛載目錄、遠程存儲服務器地址等
  • 而 StorageClass 的作用,則是充當 PV 的模板。並且,只有同屬於一個 StorageClass 的 PV 和 PVC,才可以綁定在一起
  • 當然,StorageClass 的另一個重要作用,是指定 PV 的 Provisioner(存儲插件)
  • 如果你的存儲插件支持 Dynamic Provisioning 的話,Kubernetes 就可以自動為你創建 PV 了

 2. CSI 插件體系的設計原理

  • 存儲插件實際擔任的角色,僅僅是 Volume 管理中的 Attach 階段和 Mount 階段的具體執行者
  • 而像 Dynamic Provisioning 這樣的功能,不是存儲插件的責任,而是 Kubernetes 本身存儲管理功能的一部分,如圖:

  • CSI 插件體系的設計思想,就是把這個 Provision 階段,以及 Kubernetes 里的一部分存儲管理功能,從主干代碼里剝離出來,做成了幾個單獨的組件
  • 這些組件會通過 Watch API 監聽 Kubernetes 里與存儲相關的事件變化,比如 PVC 的創建,來執行具體的存儲管理動作
  • 而這些管理動作,比如 Attach 階段和 Mount 階段的具體操作,實際上就是通過調用 CSI 插件來完成的。設計思路如圖:

  • 這套存儲插件體系多了三個獨立的外部組件(External Components),即:Driver Registrar、External Provisioner 和 External Attacher
  • 對應的正是從 Kubernetes 項目里面剝離出來的那部分存儲管理功能
  • 需要注意的是,External Components 雖然是外部組件,但依然由 Kubernetes 社區來開發和維護
  • 而右側的部分,就是需要編寫代碼來實現的 CSI 插件
    • 一個 CSI 插件只有一個二進制文件,但它會以 gRPC 的方式對外提供三個服務(gRPC Service),分別叫作:CSI Identity、CSI Controller 和 CSI Node

2.1 External Components

2.1.1 Driver Registrar

  • Driver Registrar 組件,負責將插件注冊到 kubelet 里面(這可以類比為將可執行文件放在插件目錄下)
  • 而在具體實現上,Driver Registrar 需要請求 CSI 插件的 Identity 服務來獲取插件信息

2.1.2 External Provisioner 

  • External Provisioner 組件,負責的正是 Provision 階段
  • 在具體實現上,External Provisioner 監聽了 APIServer 里的 PVC 對象
  • 當一個 PVC 被創建時,它就會調用 CSI Controller 的 CreateVolume 方法,為你創建對應 PV
  • 此外,如果你使用的存儲是公有雲提供的磁盤(或者塊設備)的話,這一步就需要調用公有雲(或者塊設備服務)的 API 來創建這個 PV 所描述的磁盤(或者塊設備)
  • 不過,由於 CSI 插件是獨立於 Kubernetes 之外的,所以在 CSI 的 API 里不會直接使用 Kubernetes 定義的 PV 類型,而是會自己定義一個單獨的 Volume 類型

2.1.3 External Attacher

  • External Attacher 組件,負責的正是 Attach 階段
  • 在具體實現上,它監聽了 APIServer 里 VolumeAttachment 對象的變化
  • VolumeAttachment 對象是 Kubernetes 確認一個 Volume 可以進入 Attach 階段的重要標志
  • 一旦出現了 VolumeAttachment 對象,External Attacher 就會調用 CSI Controller 服務的 ControllerPublish 方法,完成它所對應的 Volume 的 Attach 階段
  • 而 Volume 的 Mount 階段,並不屬於 External Components 的職責
  • 當 kubelet 的 VolumeManagerReconciler 控制循環檢查到它需要執行 Mount 操作的時候,會通過 pkg/volume/csi 包,直接調用 CSI Node 服務完成 Volume 的 Mount 階段
  • 在實際使用 CSI 插件的時候,我們會將這三個 External Components 作為 sidecar 容器和 CSI 插件放置在同一個 Pod 中。由於 External Components 對 CSI 插件的調用非常頻繁,所以這種 sidecar 的部署方式非常高效

2.2 CSI 插件服務

2.2.1 CSI IdentityCSI 插件的 CSI Identity 服務,負責對外暴露這個插件本身的信息

2.2.2 CSI Controller

  • CSI Controller 服務,定義的則是對 CSI Volume 的管理接口,比如:創建和刪除 CSI Volume、對 CSI Volume 進行 Attach/Dettach,以及對 CSI Volume 進行 Snapshot 等
  • CSI Controller 服務里定義的這些操作有個共同特點,那就是它們都無需在宿主機上進行,而是屬於 Kubernetes 里 Volume Controller 的邏輯,也就是屬於 Master 節點的一部分
  • CSI Controller 服務的實際調用者,並不是 Kubernetes(即:通過 pkg/volume/csi 發起 CSI 請求),而是 External Provisioner 和 External Attacher
  • 這兩個 External Components,分別通過監聽 PVC 和 VolumeAttachement 對象,來跟 Kubernetes 進行協作

2.2.3 CSI Node

  • 而 CSI Volume 需要在宿主機上執行的操作,都定義在了 CSI Node 服務里面

2.3 小節

  • CSI 的設計思想,把插件的職責從兩階段處理,擴展成了 Provision、Attach 和 Mount 三個階段
  • 其中,Privision 等價於“創建遠程磁盤塊”,Attach 等價於“注冊磁盤到虛擬機”,Mount 等價於“將該磁盤格式化后,掛載在 Volume 的宿主機目錄上”
  • 當 AttachDetachController 需要進行 Attach 操作時,它實際上會執行到 pkg/volume/csi 目錄中,創建一個 VolumeAttachment 對象,從而觸發 External Attacher 調用 CSI Controller 服務的 ControllerPublishVolume 方法
  • 當 VolumeManagerReconciler 需要進行 Mount 操作時,它實際上也會執行到 pkg/volume/csi 目錄中,直接向 CSI Node 服務發起調用 NodePublishVolume 方法的請求。
  • 以上,就是 CSI 插件最基本的工作原理了

3. CSI 插件部署

3.1 常用原則

  • 第一,通過 DaemonSet 在每個節點上都啟動一個 CSI 插件,來為 kubelet 提供 CSI Node 服務
  • 這是因為,CSI Node 服務需要被 kubelet 直接調用,所以它要和 kubelet“一對一”地部署起來
  • 此外,在上述 DaemonSet 的定義里面,除了 CSI 插件,我們還以 sidecar 的方式運行着 driver-registrar 這個外部組件
  • 它的作用,是向 kubelet 注冊這個 CSI 插件
  • 這個注冊過程使用的插件信息,則通過訪問同一個 Pod 里的 CSI 插件容器的 Identity 服務獲取到
  • 需要注意的是,由於 CSI 插件運行在一個容器里,那么 CSI Node 服務在 Mount 階段執行的掛載操作,實際上是發生在這個容器的 Mount Namespace 里的
  • 可是,我們真正希望執行掛載操作的對象,都是宿主機 /var/lib/kubelet 目錄下的文件和目錄
  • 所以,在定義 DaemonSet Pod 的時候,我們需要把宿主機的 /var/lib/kubelet 以 Volume 的方式掛載進 CSI 插件容器的同名目錄下
  • 然后設置這個 Volume 的 mountPropagation=Bidirectional,即開啟雙向掛載傳播,從而將容器在這個目錄下進行的掛載操作“傳播”給宿主機,反之亦然
  • 第二,通過 StatefulSet 在任意一個節點上再啟動一個 CSI 插件,為 External Components 提供 CSI Controller 服務
  • 所以,作為 CSI Controller 服務的調用者,External Provisioner 和 External Attacher 這兩個外部組件,就需要以 sidecar 的方式和這次部署的 CSI 插件定義在同一個 Pod 里
  • 而像我們上面這樣將 StatefulSet 的 replicas 設置為 1 的話,StatefulSet 就會確保 Pod 被刪除重建的時候,永遠有且只有一個 CSI 插件的 Pod 運行在集群中
  • 這對 CSI 插件的正確性來說,至關重要

3.2 小結

  • 當用戶創建了一個 PVC 之后,部署的 StatefulSet 里的 External Provisioner 容器,就會監聽到這個 PVC 的誕生
  • 然后調用同一個 Pod 里的 CSI 插件的 CSI Controller 服務的 CreateVolume 方法,為你創建出對應的 PV
  • 這時候,運行在 Kubernetes Master 節點上的 Volume Controller,就會通過 PersistentVolumeController 控制循環,發現這對新創建出來的 PV 和 PVC,並且看到它們聲明的是同一個 StorageClass
  • 所以,它會把這一對 PV 和 PVC 綁定起來,使 PVC 進入 Bound 狀態
  • 然后,用戶創建了一個聲明使用上述 PVC 的 Pod,並且這個 Pod 被調度器調度到了宿主機 A 上
  • 這時候,Volume Controller 的 AttachDetachController 控制循環就會發現,上述 PVC 對應的 Volume,需要被 Attach 到宿主機 A 上
  • 所以,AttachDetachController 會創建一個 VolumeAttachment 對象,這個對象攜帶了宿主機 A 和待處理的 Volume 的名字
  • 這樣,StatefulSet 里的 External Attacher 容器,就會監聽到這個 VolumeAttachment 對象的誕生
  • 於是,它就會使用這個對象里的宿主機和 Volume 名字,調用同一個 Pod 里的 CSI 插件的 CSI Controller 服務的 ControllerPublishVolume 方法,完成 Attach 階段
  • 上述過程完成后,運行在宿主機 A 上的 kubelet,就會通過 VolumeManagerReconciler 控制循環,發現當前宿主機上有一個 Volume 對應的存儲設備(比如磁盤)已經被 Attach 到了某個設備目錄下
  • 於是 kubelet 就會調用同一台宿主機上的 CSI 插件的 CSI Node 服務的 NodeStageVolume 和 NodePublishVolume 方法,完成這個 Volume 的 Mount 階段
  • 至此,一個完整的持久化 Volume 的創建和掛載流程就結束了

4. 總結

  • 通過學習,基本了解了 Kubernetes 持久化存儲的基本原理和流程
  • 當前內容還是以張磊老師的原文為主,后續還需要繼續思考和提煉
  • 本文所有涉及的知識點匯總至圖 Kubernetes 容器持久化存儲 中,剛興趣的同學可以點擊查看

5. 參考文獻


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM