Preface
在前一篇文章中 Kubernetes X Storage (I) 已經跟大家介紹過基本的儲存概念,包含了 PV/PVC/StorageClass
等基本元件。
而本篇文章則會跟大家分享一下在 Kubernetes
內可以如何使用NFS (Network File System)
,主要會涵蓋兩大主題,其中一個是基於 Kubernetes
原生支援的 NFS
使用方式,透過 PV/PVC
或是 Pod
內直接透過 VolumeMounts
來描述 NFS
的資訊來使用。
另外一種則是會透過 kubernetes-incubator
內相關的專案,譬如 NFS-Provisioner
或是 NFS-Client-Provisaioner
等來介紹關於 StorageClass
以及 NFS
互相整合的使用方式。
網路上關於 NFS
的用法非常多種,大致上分成
- 透過 Volume 的架構使用
NFS
作為背後的儲存容器 - 透過 Pod/Deployment 的方式於
Kubernetes
內架設一個NFS Server
- 通常這種範例,背後還會接譬如 Public Cloud Disk當作 NFS Server 的後端儲存來源。
- 上述兩種混合,一邊當 NFS Client(Volume), 一邊當 Server (NFS Server)
而本文介紹的方式基本上會概括(1)/(2) 兩種用法。
NFS Server
本文不會介紹太多關於 NFS Server
的建置,這部分請讀者自行完成,當建置完成後,可以透過 showmount
的方式來確認該 NFS Server
有正確的 export
出相關的資料夾以及權限。
本文的範例 NFS Server
資訊如下
IP: 172.17.8.101
ExportPath: /nfsshare
vagrant@vortex-dev:~$ showmount -e 172.17.8.101
Export list for 172.17.8.101:
/nfsshare
Nativer Support
對於 NFS
來說, 官方有提供了一些使用範例來介紹如何使用,詳細的可以參考下列連結 kubernetes/examples/nfs.
Pod
如果是要在 Pod
內直接使用 NFS
, 非常簡單,只要在 Volume
的欄位描述相關的資訊即可。
這種使用方式就是簡單,但是不方便,每次都要描述相關的 IP/Path
等資訊,所以一般使用上還是會採用 PV/PVC
的架構。
spec:
containers:
- name: busybox
image: hwchiu/netutils:latest
volumeMounts:
- name: nfs-volume
mountPath: /nfs
volumes:
- name: nfs-volume
nfs:
server: 172.17.8.101
path: /nfsshare
PV/PVC
如果要使用 PV/PVC
的架構,流程如下
- 創立
PV
資源,與事先架設好的NFS Server
綁定 - 創立
PVC
,去尋找符合需求的PV
- 創立
Pod
, 透過VolumeMounts
的方式去掛載創立好的PVC
.
PV/PVC/Pod
PV
必須要描述跟 NFS Server
最直接的資訊,譬如 IP 位置以及相關的路徑
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
capacity:
storage: 1Mi
accessModes:
- ReadOnlyMany
nfs:
server: 172.17.8.101
path: "/nfsshare"
PVC
中直接因為這個範例很簡單,也沒有其他的 PV
存在,因此只要條件滿足就可以選擇到對應的 PV
並且連接起來
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadOnlyMany
storageClassName: ""
resources:
requests:
storage: 1Mi
Pod
中透過Volumes
的資訊去綁定對應的 PVC
即可
apiVersion: v1
kind: Pod
metadata:
name: hwchiu
labels:
app: hwchiu
spec:
containers:
- name: busybox
image: hwchiu/netutils:latest
volumeMounts:
- name: nfs-volume
mountPath: /nfs
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs
基本上按照上述範例,就可以順利在 Pod
裡面的 /nfs
資料夾使用到外部 NFS
儲存伺服器的資料了
Limitation
Description
NFS
使用起來看起來非常順利簡單,實際上卻有一些限制,接下來好好探討一下這些限制
在上述的 PV
的創建過程中,我們可以看到我設立了
capacity:
storage: 1Mi
accessModes:
- ReadOnlyMany
然而實際上,我們到真正的 Pod
裡面執行下列指令,卻是可以正常執行的
dd if=/dev/zero of=/nfs/ii count=40960
該指令會在 /nfs/
底下寫入一個將近 200Mb
的檔案。
這個操作完全忽略了上述的設定
- 大小
1Mi
ReadOnly
的權限設定
這部分主要是因為 NFS
本身架構導致的結果。
首先, NFS
本身並不是根據需求
而動態分配空間
出來的一種檔案系統,本身檔案系統有多大,你可以存取的人就有多大可以使用。
因此過往在使用經驗上,通常都會仔細設計每個分享出來的資料夾架構以及透過其他的系統工具來限制該資料夾的能夠使用的空間大小。
此外,對於 NFS
來說,控管權限的部分分成兩個部分來看
- MountOption
- Unix 檔案權限系統 (0777)
首先,在 MountOption
的部分,我們可以在 NFS Server/NFS Client
的部分設定 RO/RW
等不同的權限,但是這些權限範圍過大,基本上就是控制該 NFS Client
是否有讀寫的權限而已
至於 Unix 檔案權限系統方面,基本上就是根據使用者的 UID/GID
以及該檔案本身的 UID/GID/OTHER
相關的的權限來控制。
所以有一個很有趣的現象就是,假設你什麼都沒有處理的情況下,你叫起的 Pod
預設都是以 root
的身份去執行,意味該使用者擁有對 NFS
分享資料夾下所有檔案的所有權利,Read/Write/Rename/Delete...
都可以執行。
所以要好好地針對 NFS
的檔案權限去使用的人,必須要有
- 完整了解整個 UNIX 檔案權限的知識
- 完整規劃不同權限
UID/GID
等架構 - 每個對應的
Pod
運行的時候都要處理對應的RunAsUser
等設定,來切換對應的使用者
當你完成了上面這些設定,你就會發現實際上還是不能通,最主要的問題是在UNIX
裡面, UID/GID
實際上是一堆數字,該檔案權限系統比對的也是數字。
但是在你的 Pod
裡面,你的 /etc/password
不一定有該數字對應的使用者資料,所以你今天透過 RunAsUser
去換一個使用者名稱 hwchiu
來運行這個 Pod
.
但是因為該 Pod
內並沒有找不到這個使用者的名稱,也沒辦法找到對應的 UID
. 最後你就會被系統給強制轉換成 Root
. 所以一切的檔案權限又回到原點了。
這部分非常麻煩處理,過往可能會想要透過 NIS(YP)
或是修改 /etc/nsswitch.con
的方式來調整查詢的順序。 但是這些舉動對於 Pod
來說非常麻煩及極度困難。
因此有想要完整的在 Kubernetes
提供 NFS Clinet
服務且也要支援外部權限系統的人,要好好的思考這些步驟該怎麼處理。
Kubernetes
針對上述的限制,kubernetes
內也有一些額外的選項可以處理。
首先關於 ReadOnly
這些的選項,我們可以透過下列的參數
nfs:
server: 172.17.8.101
path: "/nfsshare"
readOnly: true
讓 Kubernetes
知道NFS
在掛載的時候可以請幫忙提供 RO
的參數。
接下來在使用 PVC
的時候也要給予類似的參數
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs
readOnly: true
這樣的話我們在使用的 Pod
內使用mount
指令可以觀察到 RO
而非RW
的選項
root@hwchiu:/# mount | grep nfs
172.17.8.101:/nfsshare on /nfs type nfs4 (ro,relatime,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.17.8.101,local_lock=none,addr=172.17.8.101)
root@hwchiu:/# touch /nfs/test
touch: cannot touch '/nfs/test': Read-only file system
root@hwchiu:/#
Kubernetes-incubator: nfs-client
前面我們提到了透過 PV/PVC
的方式來事先設定好相關的儲存資源,並且分配讓對應的 Pod
來使用。
這種情況下,基本上每個 Pod
都可以共享相同的資料夾,並且看到相同的內容。如果該 NFS Server
有不一樣的 Export Path
且有不同的用途,通常就要創立不同的 PV/PVC
組合來供最上層的 Pod
來使用。
如果今天想要透過 StorageClass
這種動態分配的方式來使用 NFS Server
有沒有辦法?
預設的 Kubernetes
是沒有提供這類型的功能,但是我們可以透過額外的套件功能來幫我們滿足這個功能
也就是接下來要介紹的 kubernetes-incubator
內的專案 NFS-Provisioner: nfs-client
詳細的資訊可以參考其 Kubernetes-Incubator External-Storage NFS-Client
Introduction
該專案的目標很簡單,讓你可以透過 StorageClass
動態產生 PV/PVC
供上層的 Pod
使用.
其原理很簡單,其實就是透過 Kubernetes
內建的 Provisioner
介面去額外實現一個 Provisioner Controller
.
接下來當我們部署一個 StorageClass
的時候,可以於 Provisioner
的欄位去指向事先創立好的 Provisioner
. 這樣當未來有任何 PVC
的需求要指向 StorageClass
, 最後就會將該請求傳送到該 Deploykent(Provisioner)
內去處理。
Installation
我們最主要需求就是運行起一個 Provisioner Controller
. 這部分 nfs-client
有提供 helm
來安裝
helm install stable/nfs-client-provisioner \
--name nfs-client
--set nfs.server=172.17.8.101 \
--set nfs.path=/nfsshare
這邊我們需要針對 nfs.server
以及 nfs.path
去進行設定。當一切安裝完畢後,我們透過 helm status nfs-client
來觀察一下所有安裝的 kubernetes
資源
~$ helm status nfs-client
LAST DEPLOYED: Sun Dec 30 15:16:04 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ServiceAccount
NAME AGE
nfs-client-nfs-client-provisioner 1m
==> v1/ClusterRole
nfs-client-nfs-client-provisioner-runner 1m
==> v1/ClusterRoleBinding
run-nfs-client-nfs-client-provisioner 1m
==> v1/Role
leader-locking-nfs-client-nfs-client-provisioner 1m
==> v1/RoleBinding
leader-locking-nfs-client-nfs-client-provisioner 1m
==> v1/Deployment
nfs-client-nfs-client-provisioner 1m
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
nfs-client-nfs-client-provisioner-986bcfb76-svqb4 1/1 Running 0 1m
==> v1/StorageClass
NAME AGE
nfs-client 1m
這邊我們可以觀察的是 StorageClass
以及 Deployment
.
~$ kubectl describe storageclass nfs-client
Name: nfs-client
IsDefaultClass: No
Annotations: <none>
Provisioner: cluster.local/nfs-client-nfs-client-provisioner
Parameters: archiveOnDelete=true
AllowVolumeExpansion: True
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: Immediate
Events: <none>
可以看到 Provisioner
這邊指定的是 cluster.local/nfs-client-nfs-client-provisioner
, 而這個數值我們可以透過檢視該 Deployment
的環境變數來觀察到
~$ kubectl describe deployment nfs-client-nfs-client-provisioner | grep PRO
PROVISIONER_NAME: cluster.local/nfs-client-nfs-client-provisioner
接下來我們就可以透過部署 PVC
以及對應的 Pod
來使用了
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
annotations:
volume.beta.kubernetes.io/storage-class: "nfs-client"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
apiVersion: v1
kind: Pod
metadata:
name: hwchiu
labels:
app: hwchiu
spec:
containers:
- name: busybox
image: hwchiu/netutils:latest
volumeMounts:
- name: nfs-volume
mountPath: /nfs
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: test-claim
這邊可以注意的是,當對應的 PVC
被創建之後,我們可以在 NFS Server
那邊觀察到,對應的 /nfsshare
資料夾裡面又被創建了一個新的資料夾 default-test-claim-pvc-e0ae384c-0c45-11e9-be59-02e1e1d1e477
這個含義就是該 Deployment(Provisioner)
針對該 PVC
的需求,動態的創建了一個資料夾,並且把該資料夾當作 mount path
給之後的 Pod
去使用。 所以使用不同 PVC
的 Pod
即使背後都是相同的 NFS Server
, 也可以因為 StorageClass
動態規劃的幫忙而不會互相看到彼此的資料。
Summary
最後用一個簡單的架構圖來進行一下比較
原生的 Kubernetes NFS
的支援下,NFS Server
怎麼設定, 使用的 NFS Client(Pod)
就只能怎麼使用,掛載以及使用的資料夾比較固定。基本上要依賴 NFS Server
端的設計來滿足各種不同的使用情境,此外管理者必須要自己先行創立對應的 PV/PVC
供要使用的 Pod
來使用。
如果想要使用 StorageClass
這種動態分配空間的使用方式的話,可以參考一些孵化中的專案,NFS-Client
就是其中一個,透過自行實作 Provisioner
,根據需求在整個 NFS Server
的目錄下創建更多更多的小目錄供對應的 PVC
去使用。
舉例來說,今天可以創建多個 PVC, 譬如 DB-Data
, Container-Log
等不同的需求,然後 Provisioner
就會創建不同的資料夾供這些應用使用,最後每個 Pod
可以根據自己的需要來掛載特定的資料夾,而且互相彼此不會看到彼此資料。
唯一要注意的時,這邊也是有權限以及容量的問題,這部分就是 NFS
的基本限制。