理解OpenShift(1):網絡之 Router 和 Route
理解OpenShift(5):從 Docker Volume 到 OpenShift Persistent Volume
** 本文基於 OpenShift 3.11,Kubernetes 1.11 進行測試 ***
1. 從 Docker Volume 到 OpenShift/Kubernetes Persistent Volume
1.1 Docker 容器層(Container layer)
Docker 鏡像是不可修改的。使用一Docker 鏡像啟動一個容器實例后,Docker 會在鏡像層之上添加一個可讀寫的容器層(Container layer)。容器中所有新增或修改的數據都保存在該容器層之中。在容器實例被刪除后,該層也會隨之被自動刪除,因此所有寫入的或修改的數據都會丟失。具體可閱讀Docker 相關文檔,比如 https://docs.docker.com/v17.09/engine/userguide/storagedriver/imagesandcontainers/。

1.2 Docker Volume
在容器的可寫層中保存數據是可能的,但是有一些缺點:
- 當容器實例不在運行時,數據不會被保存下來,因此數據是易失性的,不是持久性的。
- 很難將容器中的數據弄到容器外面,如果其它進行需要訪問它的話。
- 容器的可寫層和容器所在的宿主機緊耦合,數據無法被移動到其它宿主機上。
- 向容器的可寫層中寫入數據需要通過存儲驅動(storage driver,比如AUFS,Brtfs,OverlayFS等)來管理文件系統。存儲驅動利用Linux內核提供聯合文件系統(union file system),這會降低IO性能。
為了解決以上問題,Docker 提供了 Volume (卷)功能。本質上,一個數據卷(data volume)是 Docker 容器所在宿主機上的一個目錄或文件,它被掛載(mount)進容器。Docker 卷具有自己獨立的生命周期,可以使用 Docker volume 命令獨立地被創建和管理。在容器實例被刪除后,卷依然存在,因此卷中的數據會被保留,從而實現數據持久化。而且,數據卷直接將數據寫入宿主機文件系統,性能相比容器的可寫層有提高。
Docker 提供三種方式將宿主機文件或文件夾掛載到容器中:
- volume(卷):卷保存在宿主機上由Docker 管理的文件系統中,通常在 /var/lib/docker/volumes/ 目錄下。
- bind mount(綁定掛載):被掛載的文件或文件夾可以在宿主機上文件系統的任何地方。
tmpfsvolume:數據保存在宿主機內存中,而不寫入磁盤。

三種方式各自有合適的場景,通常建議使用 Docker Volume。Docker Volume 還支持通過各種卷插件(volume plugin),接入各種外置存儲。本質上,都是存儲插件將存儲的卷掛載到Docker宿主機上的某個目錄,然后Docker 將目錄在掛載給容器。
更詳細信息,請閱讀 https://docs.docker.com/v17.09/engine/admin/volumes/#good-use-cases-for-tmpfs-mounts 等官方文檔。
1.3 Kubernetes/OpenShift Volume
OpenShift 利用 Kubernetes 的存儲機制來實現其 Volume 功能。和Docker volume 概念類似,本質上,一個 K8S Volume 也是一個能被Pod 中的容器訪問的目錄。至於該目錄是怎么來的,后端介質是什么,內容是什么,則是由所使用的具體卷類型(volume type)決定的。Kubernetes Volume 支持多種存儲類型:

關於 K8S Volume 概念的更多信息,請閱讀相關文檔。
1.3.1 K8S NFS Volume 示例
下面以 Glusterfs Volume 為例介紹 K8S Volume 的使用:
(1)OpenShift 管理員在集群中創建一個 endpoints 對象,指向 Glusterfs 服務器的 IP 地址。在我的測試環境中,由兩台服務器提供Glusterfs服務。
172.20.80.7:glusterfsvol1 on /var/lib/origin/openshift.local.volumes/pods/bd8914b5-00d9-11e9-a6cf-fa163eae8505/volumes/kubernetes.io~glusterfs/glustervol1 type fuse.glusterfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other,max_read=131072)
(5)然后,宿主機上的這個目錄會通過 Docker bind mounted 掛載進容器
1.3.2 K8S/OpenShfit Volume 使用方式總結
從上面過程可以看出,使用卷的過程需要至少有存儲工程師和開發人員。要使用某種卷,開發人員需要了解后端存儲的具體配置信息。

但是實際上,存儲信息對於應用開發人員來說,其實是不需要可見的。他們只關心有沒有滿足要求的存儲可用,而不需要關心后端是什么存儲。
為了解耦存儲供給和存儲使用(pod中的存儲定義),Kubernetes 創建了兩個概念:PV (Persistent Volume)和 PVC (Persistent Volume Claim)這些概念。
1.4 Kubernetes/OpenShift Persistent Volume
1.4.1 概念
- PV:Persistent Volume。由 OpenShfit 管理員創建,后端是各種類型的存儲,比如 AWS EBS,GCE Disk,NFS 等。管理員可以創建多個PV,形成一個存儲池,供開發人員使用。
- StorageClass:在需要動態創建 PV 時由 OpenShfit 管理員創建。 管理員利用 StorageClass 來描述他們所提供的存儲的類型(classes)。Storage class 向管理員提供了一種方式,用於描述他們所提供的存儲的信息。不同的class 可映射到不同的 SLA,備份策略,等等。每個 StorageClass 包括 provisoner、parameters、reclaimPolicy 字段。
- PVC:Persistent Volume Claim。由開發人員創建,一個實例表示對某種存儲資源的一個申請。每當開發人員創建一個新的PVC后,Kubernetes 會在已有的PV 池中進行搜索,找到一個最佳匹配的PV 來使用。PVC 中只包含通用的存儲需求,比如訪問模式(AccessModes)、容量(request)等,而不需要關心后端存儲的具體信息。 Pod 通過 PVC 使用 PV,PV 由實際的存儲系統提供物理存儲。

以 Glusterfs 為例,這是各種概念之間對照圖(來源: http://blog.leifmadsen.com/blog/2017/09/19/persistent-volumes-with-glusterfs/):

根據 PV 的不同創建方式,又可以分為靜態創建PV 和 動態創建PV兩種方式。前面一種PV由OpenShift 管理員手工創建,后者一種的PV由系統自動創建。具體可參考后面的兩個例子。
1.4.2 PV 的生命周期
- 供給:分為靜態供給和動態供給。
- 靜態供給是指管理員會預先創建好一定數目的PV,每個PV 包含供集群使用的真實后端存儲的詳細信息,這些PV形成一個持久化卷的資源池。
- 動態供給是集群管理員預先創建 StorageClass,然后PVC申請StorageClass,然后集群會動態創建PV,供PVC消費。 動態卷供給是 Kubernetes獨有的功能,這一功能允許按需創建存儲建。在此之前,集群管理員需要事先在集群外由存儲提供者或者雲提供商創建存儲卷,成功之后再創建PersistentVolume對象,才能夠在kubernetes中使用。動態卷供給能讓集群管理員不必進行預先創建存儲卷,而是隨着用戶需求進行創建。
- 綁定:用戶在部署容器應用時會定義PVC,其中會聲明所需的存儲資源的特性,如大小和訪問方式。K8S 的一個控制器(controller)會負責在PV 資源池中尋找匹配的PV,並將PVC與目標PV 進行對接。這是PV和PVC的狀態將變成 Bound,即綁定狀態。PV 和 PVC 之間的綁定是1:1的,這意味着PVC對PV的占據是獨占的、排它的。 使用:Pod 通過使用 PVC 來通過卷(volume)來使用后端存儲(storage)資源。Pod 和它要使用的 PVC 必須在同一個 project 中。
- 集群為 pod 在同一個 project 中定位到 PVC。
- 通過 PVC 找到 PV。如果沒找到且存在合適的StorageClass,則自動創建一個PV。
- 存儲卷掛載到宿主機,然后被 pod 使用。
- 釋放:當應用不再使用存儲時,可以刪除PVC,此時PV的狀態為 released,即釋放。Kubernetes 支持使用保護模式(Storage Object in Use Protection)。當該功能啟用后,如果用戶刪除被一個正被pod 使用着的 PVC,該 PVC 不會馬上被刪除,而是會推遲到 pod 不再使用該PVC時。如果用戶刪除PV,它也不會被馬上刪除,而是會等到該PV不再綁定到PVC 的時候。是否啟用了該保護,可以從 PV 和 PVC 的 finalizers: - kubernetes.io/pvc-protection 上看出來。
- 回收:當 PV 的狀態變為 released,K8S 會根據 PV 定義的回收策略回收持久化卷。
- retain:保留數據,人工回收持久化卷。
- recycle:通過執行 rm -rf 刪除卷上所有數據。目前只有 NFS 和 host path 支持。
- delete:動態地刪除后端存儲。需要底層 iaas 支持,目前 AWS EBS,GCE PD 和 OpenStack Cinder 支持。
2. 靜態創建PV示例及Volume 權限(以NFS為例)
2.1 靜態創建PV的流程
(1)存儲管理員准備 NFS 環境
網上有很多關於NFS安裝步驟的文章,這里不再重復。我的測試環境上,NFS 服務器的IP 地址為 172.20.80.4,它暴露了三個文件夾供客戶端使用:
(2)OpenShift 管理員創建 PV, 后端使用上述 NFS 存儲的

(3)開發人員創建一個 PVC,使用上一步驟中創建的PV。該 PVC實例會存在於某個project 之中,而PV則是在集群范圍內共享的。

(4)NFS folder4 文件夾被掛載到Pod 所在的宿主機上。
172.20.80.4:/mnt/folder4 on /var/lib/origin/openshift.local.volumes/pods/863e9b2d-01a0-11e9-a6cf-fa163eae8505/volumes/kubernetes.io~nfs/pv-folder4-2 type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.22.122.8,local_lock=none,addr=172.20.80.4)
(5)宿主機上的目錄被 bind mounted 給容器,成為其 /var/volume 文件夾。

2.2 Pod volume 權限
2.2.1 NFS 權限控制
每種存儲后端都有其自己的權限管理方式。在NFS 中,在 /etc/exports 文件中國年,可以使用以下原語來設置每個將被共享出來的文件夾的權限:
- 讀寫模式
- ro:共享目錄只讀;
- rw:共享目錄可讀可寫;
- 用戶管理
- all_squash:所有訪問用戶都映射為匿名用戶或用戶組;
- no_all_squash(默認):訪問用戶先與本機用戶匹配,匹配失敗后再映射為匿名用戶或用戶組;
- root_squash(默認):將來訪的root用戶映射為匿名用戶或用戶組;
- no_root_squash:來訪的root用戶保持root帳號權限;
- anonuid=<UID>:指定匿名訪問用戶的本地用戶UID,默認為nfsnobody(65534);
- anongid=<GID>:指定匿名訪問用戶的本地用戶組GID,默認為nfsnobody(65534);
- 端口管理
- secure(默認):限制客戶端只能從小於1024的tcp/ip端口連接服務器;
- insecure:允許客戶端從大於1024的tcp/ip端口連接服務器;
- 數據寫入方式
- sync:將數據同步寫入內存緩沖區與磁盤中,效率低,但可以保證數據的一致性;
- async:將數據先保存在內存緩沖區中,必要時才寫入磁盤;
- wdelay(默認):檢查是否有相關的寫操作,如果有則將這些寫操作一起執行,這樣可以提高效率;
- no_wdelay:若有寫操作則立即執行,應與sync配合使用;
- 文件夾權限
- subtree_check(默認) :若輸出目錄是一個子目錄,則nfs服務器將檢查其父目錄的權限;
- no_subtree_check :即使輸出目錄是一個子目錄,nfs服務器也不檢查其父目錄的權限,這樣可以提高效率;
NFS 用戶認證及權限控制基於 RPC。在 NFS 3 和 4 版本中,最常用的認證機制是 AUTH_Unix。客戶端系統上的 uid 和 gid 通過 RPC 調用傳到 NFS 端,然后這些 id 所擁有的權限會被校驗,以確定能否訪問目標資源。因此,客戶端和服務器端上的 uid 和 gid 必須相同。同時,可以使用一些設置來做特定處理:
- all_squash:將所有用戶和組都映射為匿名用戶和組。默認為 nfsnobody 用戶(id 為 65534)和 nfsnodbody 組(id 為 65534)。也可以通過 anonuid 和 anongid 指定。
- no_all_squash:訪問用戶先與本機用戶通過 id 進行匹配,如果有 id 相同的用戶則匹配成功,若匹配失敗后再映射為匿名用戶或用戶組。這是默認選項。
- root_squash:將來訪的 root 用戶(id 為 0)映射為匿名用戶。這是默認選型,可以使用 no_root_squash 不進行這種映射,而保持為 root 用戶。
在我們當前的例子中,folder4 的文件夾權限為 /mnt/folder4 172.22.122.0/24(insecure,rw,sync,no_root_squash,no_all_squash)。這表示它:
- 允許 172.22.122.0/24 網段的客戶端訪問(備注:這個網段實際上是宿主機所在的網段,而不是Pod 網段,因為 NFS 實際上是被掛載給宿主機的,然后再 bind mount 給容器的)。
- insecure:允許通過端口號大於 1024 的 tcp 連接訪問它。
- no_root_squash:保持客戶端 root 用戶,將其映射為服務器端 root 用戶。理論上這是一種危險的配置。
- no_all_squash:先將通過 PRC 傳入的 uid 和 gid 在本地進行匹配。成功則使用 NFS 服務器上的同id 的用戶或組;否則使用匿名用戶或組。
NFS 上的 folder4 的目錄權限為:drwxr--r-x 2 nfsnobody nfsnobody 4096 Dec 17 10:11 folder4。這意味着 nfsnobody 用戶可以對它做讀寫,其它用戶(包括nfsnobody組內的用戶和其它用戶)都只能讀。
Pod 中的用戶 id 為:uid=1001(default) gid=0(root) groups=0(root)。
查詢共享目錄OK。
寫入失敗:Permission denied。這是因為本地用戶 uid 1001 在 NFS 服務器上有匹配的用戶 (cloud-user2:x:1001:1001::/home/cloud-user2:/bin/bash),而該用戶並沒有 folder4 文件夾寫權限。
2.2.2 幾種權限做法
從上面對 NFS 權限控制原理的分析可以看出有幾種方式來保證Pod 中的用戶的寫入成功。
(1)將 NFS 暴露出來的文件夾的所有者修改為 nfsnobody:nfsnobody,然后在文件夾上設置 all_squash,這會將所有客戶端 uid 和 gid 映射為NFS服務器端的 nfsnobody 用戶和 nfsnobdy 組。
在 pod 中的 id: uid=1001(default) gid=0(root) groups=0(root)
在 pod 中寫入文件,然后在 NFS 上查看:

可見是uid 和 gid 都是映射成功了的。
(2)上述方法一將所有客戶端的用戶都映射為 nfsnobody:nfsnobody,這有了統一性,但是也消滅了獨特性。有時候還需要保留客戶端上的已知uid。此時會在 NFS 共享的文件夾上設置 no_all_squash,這樣會先做匹配找到兩地都有的user,匹配不成功則走步驟(1)中的做法。
這種情況下,如果匹配成功,則NFS 會對服務器端的同 uid 和 gid 的用戶的權限進行校驗。通常情況下,NFS 服務器端匹配到的用戶不會是 nfsnobdy,根據文件夾上的權限設置,此時Pod 中是無法寫入文件的。這就是 2.2.1 中說描述的場景的結果。此時有兩種處理方式:
(a)將文件夾上 other user 加上寫權限。這種做法比較簡單粗暴,權限暴露過大,不推薦使用。
chmod o+w folder5 -R
(b)使用 supplemental group id
Linux 系統中, supplemental group ID 是進程所擁有的附加組的一個集合。在 Linux 上,文件系統的用戶(user)、組(group)的 ID,連同輔助組(supplementary group)的ID,一起確定對文件系統的操作權限,包括打開(open)、修改所有者(change ownership)、修改權限(permission)。具體請閱讀相關 Linux 文檔。
首先修改NFS 文件夾的 gid 為某個數值,比如下面的命令修改gid為 2000(這里實際上是 gid,不是 supplemental gid。gid 對文件夾有意義,而 supplemental gid 對文件夾無意義而對進程有意義)。
chown :2000 folder4 -R
然后在 pod 上進行配置,使得 Pod 中的主進程的輔助組id 為這里所設置的gid。
2.2.3 設置Pod 中主進程的 uid 和 supplemental gid
(1)Pod 的 uid,gid 和 supplemental gid
Kubernets 目前還不支持設置 gid,因此所有 pod 中運行主進程的 gid 都是 0。
對一個非 cluster admin role 用戶啟動的 pod,它的默認 service account 為 restricted。它要求 uid 必須在指定的區間內,而它自己並沒有指定用戶id 區間:

此時 pod 的 uid 區間受pod 所在的 project 上的定義的相應 annotation 限制:

此時pod 中的 uid 和 suppenmental gid 如下圖所示:(備注:與前面的例子中的 uid 不同,是因為前面的 pod 是 cluster admin user 啟用的,因此 pod 的 scc 為 anyuid):

在不顯式指定 uid 和 supplemental gid 的情況下,會使用區間的最小值作為默認值。
(2)修改 Pod 的 uid
根據前面對 NFS 權限管理的分析,可以將 Pod 中的 uid 修改為 nfsnobody 對應的 uid,這樣Pod 就會具有 NFS 共享目錄的寫入權限。但是,默認的 nfsnobdy 的 uid 為 65534,這個 uid 並不在service account restricted 允許的 uid 區間 [1000000000, 1000009999] 之內,因此無法將 uid 設置為 65534.
此時,可以基於 restricted scc 創建一個新的 scc,別的配置不變,除了將 RunAsUser 策略修改為 RunAsAny 以外。此時,就可以在 Pod 中指定 uid 為 65534 了。
新的scc:
pod 中指定 uid:
pod 的 uid:
掛載的文件夾可寫。操作成功。
(3)修改 supplementantal gid
因為 uid 會和太多因素關聯,所以直接修改 uid 這種做法比較重。除了 uid 外,Pod 中還可以:
- 設置 fsGroup,它主要面向向塊存儲
- 設置 suppmental gid,它主要面向共享文件系統
因為 Glusterfs 是共享文件存儲,因此需設置輔助組id。具體步驟包括:
- 修改 pod 的 suppemental gid,其做法與修改 uid 的做法類似,不再重復。
- 修改 NFS 文件夾的 group 權限,加上 w 和 x,並設置其 gid 為 pod 所使用的 suppemental gid。
這兩,在NFS客戶端(pod)和服務器端(文件夾)上通過 group id 將把權限打通了。
更詳細說明,請閱讀 OpenShift 官方文檔 https://docs.okd.io/latest/install_config/persistent_storage/pod_security_context.html。
3. 動態創建PV示例(以Clusterfs 為例)
3.1 流程概述
3.1.1 從 OpenShift 角度看
下圖展示了從 OpenShift 角度看的動態創建PV的流程。在步驟 3.2,當開發人員創建好PVC以后,OpenShift 會在當前StorageClass中查找滿足要求的 StorageClass。一旦找到,就會根據PVC中的配置自動創建一個PV,並調用StorageClass中的 storage provisioner 自動創建一個存儲volume。在開發人員創建使用該 PVC 的 Pod 后,存儲卷就會被掛載給Pod 所在的宿主機,然后通過 bind mounted 被掛載給Pod。

這么做的好處是顯而易見的,比如:
- 集群管理員不需要預先准備好PV
- PV的容量和PVC的容量是一樣的,這樣就不會存在存儲浪費。
- 在刪除PVC時,PV 會被自動刪除,存儲卷也會被自動刪除。
另一方面,OpenShift 會為每個PVC 在后端存儲上創建一個卷。這樣,在有大量PVC時,存儲中將出現大量的小容量卷,這對某些存儲會產生相當大的壓力,特別是對於一些傳統存儲。這些存儲可能就不能滿足現代容器雲平台對存儲的要求了。
3.1.2 從存儲角度看
因為 Glusterfs 本身不提供 REST API,因此需要在它前面部署一個Proxy。Heketi 就是一種開源的這種Proxy,它的項目地址是 https://github.com/heketi/heketi。它暴露Gluster Volume 操作的REST API,並通過 SSH 來運行 Glusterfs 命令,完成各種卷相關的操作,比如創建,映射等。OpenShift 通過調用 Heketi API 來實現 Gluesterfs 卷的動態創建和管理。

3.2 示例
(1)OpenShift 管理員創建 StorageClass

每個 StorageClass 會包含幾個屬性:
- provisioner:指定創建PV所使用的存儲插件(volume plugin)。Kubernets/OpenShift 帶有它們所支持的存儲插件。
- parameters:后端存儲的各種參數。
- reclaimPolicy:存儲空間回收策略。
關於StorageClass的詳細說明,請閱讀 https://kubernetes.io/docs/concepts/storage/storage-classes/。
(2)開發人員創建一個PVC

其中關鍵的一項是在 storageClassName 中制定 StorageClass 的名稱。
(3)OpenShfit 自動創建一個PV,以及其它資源。
OpenShfit 會根據 StorageClass 及 PVC 中的有關屬性,動態創建一個 PV。

以及 Service:
及其 Endpoints:
OpenShift 是通過該 service 調用 storage provisioner 的。
(4)Volume plugin 會自動地創建存儲卷
Heketi 在 Glusterfs 中創建改卷的過程大致如下:
(a)Glusterfs 系統初始化時會為每個物理磁盤創建一個 Volume Group:
pvcreate --metadatasize=128M --dataalignment=256K '/dev/vde' vgcreate --autobackup=n vg_c04281d30edfa285bb51f0f323ab7690 /dev/vde
gluster --mode=script volume create vol_e22dc22f335de8f8c90f7c66028edf37 172.20.80.7:/var/lib/heketi/mounts/vg_c04281d30edfa285bb51f0f323ab7690/brick_97d37975df78714e2e0bfea850a9e4aa/brick mkdir -p /var/lib/heketi/mounts/vg_c04281d30edfa285bb51f0f323ab7690/brick_97d37975df78714e2e0bfea850a9e4aa lvcreate --autobackup=n --poolmetadatasize 8192K --chunksize 256K --size 1048576K --thin vg_c04281d30edfa285bb51f0f323ab7690/tp_97d37975df78714e2e0bfea850a9e4aa --virtualsize 1048576K --name brick_97d37975df78714e2e0bfea850a9e4aa mkfs.xfs -i size=512 -n size=8192 /dev/mapper/vg_c04281d30edfa285bb51f0f323ab7690-brick_97d37975df78714e2e0bfea850a9e4aa mount -o rw,inode64,noatime,nouuid /dev/mapper/vg_c04281d30edfa285bb51f0f323ab7690-brick_97d37975df78714e2e0bfea850a9e4aa /var/lib/heketi/mounts/vg_c04281d30edfa285bb51f0f323ab7690/brick_97d37975df78714e2e0bfea850a9e4aa
#這個目錄是在 Glusterfs 節點上實際保存數據的目錄
mkdir /var/lib/heketi/mounts/vg_c04281d30edfa285bb51f0f323ab7690/brick_97d37975df78714e2e0bfea850a9e4aa/brick
#該命令會目錄的 gid 修改為前述第(3)步中的 gid
chown :2000 /var/lib/heketi/mounts/vg_c04281d30edfa285bb51f0f323ab7690/brick_97d37975df78714e2e0bfea850a9e4aa/brick chmod 2775 /var/lib/heketi/mounts/vg_c04281d30edfa285bb51f0f323ab7690/brick_97d37975df78714e2e0bfea850a9e4aa/brick
(5)開發人員創建一個使用上述PVC的 Pod
(6)Pod 啟動時,系統會
[root@node2 cloud-user]# mount | grep gluster 172.20.80.7:vol_e22dc22f335de8f8c90f7c66028edf37 on /var/lib/origin/openshift.local.volumes/pods/5d97c7db-ff75-11e8-8b3e-fa163eae8505/volumes/kubernetes.io~glusterfs/pvc-10438bac-ff75-11e8-8b3e-fa163eae8505 type fuse.glusterfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other,max_read=131072)
然后該宿主機目錄作為一個 mountpoint 被掛載給容器:
172.20.80.7:vol_e22dc22f335de8f8c90f7c66028edf37 on /var/volume type fuse.glusterfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other,max_read=131072)
查看用戶,它有id 為 2000 輔助組。
- 在 Ubuntu 16.04 上安裝 Glusterfs:https://wiki.centos.org/zh/HowTos/GlusterFSonCentOS 和 https://www.itzgeek.com/how-tos/linux/ubuntu-how-tos/install-and-configure-glusterfs-on-ubuntu-16-04-debian-8.html
- 以 Volume 形式使用 Glusterfs:https://github.com/kubernetes/examples/tree/master/staging/volumes/glusterfs
- http://blog.leifmadsen.com/blog/2017/09/19/persistent-volumes-with-glusterfs/
- Docker、Kubernetes 和 OpenShift 官方文檔
感謝您的閱讀,歡迎關注我的微信公眾號:

