1. 程式人生 > >Kubernetes 叢集使用 NFS 網路檔案儲存

Kubernetes 叢集使用 NFS 網路檔案儲存

1、NFS 介紹

Kubernetes PersistentVolumes 持久化儲存方案中,提供兩種 API 資源方式: PersistentVolume(簡稱 PV) 和 PersistentVolumeClaim(簡稱 PVC)。PV 可理解為叢集資源,PVC 可理解為對叢集資源的請求,Kubernetes 支援很多種持久化卷儲存型別。NFS 是網路檔案儲存系統,它允許網路中的計算機之間通過 TCP/IP 網路共享資源。通過 NFS,我們本地 NFS 的客戶端應用可以透明地讀寫位於服務端 NFS 伺服器上的檔案,就像訪問本地檔案一樣方便。

2、環境、軟體準備

本次演示環境,我是在虛擬機器上安裝 Linux 系統來執行操作,通過虛擬機器完成 NFS 服務搭建以及 Kubernetes HA 叢集的搭建,以下是安裝的軟體及版本:

  • Oracle VirtualBox: 5.1.20 r114628 (Qt5.6.2)
  • System: CentOS Linux release 7.3.1611 (Core)
  • rpcbind: 0.2.0-38.el7.x86_64
  • nfs-utils: 1.3.0-0.54.el7.x86_64
  • kubernetes: 1.12.1
  • docker: 18.06.1-ce
  • etcd: 3.3.8
  • Keepalived: v1.3.5
  • HaProxy: version 1.5.18
  • cfssl version 1.2.0

注意:這裡我著重描述一下 Kubernetes 叢集如何使用 NFS 來實現持久化儲存,所以需要提前搭建好 Kubernetes 叢集和 NFS 檔案儲存服務,具體搭建過程可參考之前文章

國內使用 kubeadm 在 Centos7 搭建 Kubernetes 叢集Linux 環境下 NFS 服務安裝及配置使用。這裡提一下,使用上邊方案搭建 Kubernetes 叢集亦可以使用 NFS 網路檔案儲存,但是叢集為單主多節點方式,本次演示如何快速搭建 Kubernetes HA 高可用叢集(多主多節點、Etcd HA、LB + VIP),來使用 NFS 網路檔案儲存。

3、Kubernetes HA 叢集搭建

Kubernetes HA 叢集搭建,主要包含 Etcd HA 和 Master HA。Etcd HA 這個很容易辦到,通過搭建 Etcd 叢集即可(注意 Etcd 叢集只能有奇數個節點)。Master HA 這個稍微麻煩一些,多主的意思就是多個 Kubernetes Master 節點組成,任意一個 Master 掛掉後,自動切換到另一個備用 Master,而且整個叢集 Cluster-IP 不發生變化,從而實現高可用。而實現切換時 Cluster-IP 不變化,目前採用最多的方案就是 haproxy + keepalived

實現負載均衡,然後使用 VIP(虛地址) 方式來實現的。

這裡推薦使用 kubeasz 專案,該專案致力於提供快速部署高可用 k8s 叢集的工具,它基於二進位制方式部署和利用 ansible-playbook 實現自動化安裝,同時集成了很多常用外掛,而且都有對應的文件說明,非常方便操作。它提供了單節點、單主多節點、多主多節點、在公有云上部署等方案,通過它很容易就能完成各種型別版本 k8s 叢集的搭建。我覺得非常好的一點就是,他同時提供了搭建 k8s 叢集所需要的離線資源下載,這對於國內網路使用者來說,簡直是太方便了,有木有。本人親測過,確實可行。

本次,我們要搭建的就是多主多節點 HA 叢集,請參照 kubeasz 文件 來執行,非常方便,親測可行,這裡就不在演示了。說明一下:同時由於本機記憶體限制,共開啟了 4 個虛擬機器節點,每個節點都分別充當了一個或多個角色。

高可用叢集節點配置:

  • 部署節點 x 1: 10.222.77.86
  • etcd 節點 x 3: 10.222.77.130、10.222.77.132、10.222.77.134
  • master 節點 x 2: 10.222.77.130、10.222.77.134
  • lb 節點 x 2: 10.222.77.134(主)、10.222.77.130(備)
  • node 節點 x 2 : 10.222.77.132、10.222.77.86
  • nfs 節點 x 1: 10.222.77.86

別看上邊顯示了一堆 IP,其實就 4 個節點,交叉使用,建議生產環境下,每個節點充當一個角色,這樣既方便排查問題,也利於叢集穩定性。如果上邊 IP 看的不直觀的話,那麼就看下下邊這個 k8s HA 叢集架構圖吧!
kubernetes-HA

部署完成後,通過如下命令檢視下是否部署成功。

$ kubectl get cs
NAME                 STATUS    MESSAGE             ERROR
controller-manager   Healthy   ok                  
scheduler            Healthy   ok                  
etcd-2               Healthy   {"health":"true"}   
etcd-1               Healthy   {"health":"true"}   
etcd-0               Healthy   {"health":"true"} 

$ kubectl get nodes
NAME            STATUS                     ROLES    AGE   VERSION
10.222.77.130   Ready,SchedulingDisabled   master   1h    v1.12.1
10.222.77.132   Ready                      node     1h    v1.12.1
10.222.77.134   Ready,SchedulingDisabled   master   1h    v1.12.1
10.222.77.86    Ready                      node     1h    v1.12.1

4、直接掛載 NFS

k8s 叢集已搭建完畢,我們來演示一下直接掛載 NFS 到 Pod 的方式,這種方式最直接。首先,我們去 NFS 服務端機器(10.222.77.86)建立一個 /data/nfs0 目錄作為遠端共享檔案儲存目錄。

$ mkdir -p /data/nfs0
# 修改配置
$ vim /etc/exports
/data/nfs0 *(rw,sync,insecure,no_subtree_check,no_root_squash)

# 使配置生效
$ exportfs -r

# 服務端檢視下是否生效
$ showmount -e localhost
Export list for localhost:
/data/nfs0  *
/data/share 10.222.77.0/24

說明一下,我們要掛載 NFS,那麼需要先到 NFS 服務端建立好對應目錄,否則將掛載不成功。此處 NFS 操作及配置如果不清楚,請參考之前 Linux 環境下 NFS 服務安裝及配置使用 這篇文章。

接下來,直接建立一個掛載該 NFS 路徑的 Pod,yaml 檔案如下:

$ vim nfs-busybox.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nfs-busybox
spec:
  replicas: 1
  selector:
    matchLabels:
      name: nfs-busybox
  template:
    metadata:
      labels:
        name: nfs-busybox
    spec:
      containers:
      - image: busybox
        command:
          - sh
          - -c
          - 'while true; do date > /mnt/index.html; hostname >> /mnt/index.html; sleep 10m; done'
        imagePullPolicy: IfNotPresent
        name: busybox
        volumeMounts:
          - name: nfs
            mountPath: "/mnt"
      volumes:
      - name: nfs
        nfs:
          path: /data/nfs0
          server: 10.222.77.86

簡單說下,該 Deployment 使用 busybox 作為映象,掛載 nfs 的 /data/nfs0 捲到容器 /mnt 目錄,並輸出系統當前時間及 hostname 到 /mnt/index.html 檔案中。那麼來建立一下該 Deployment。

$ kubectl apply -f nfs-busybox.yaml 
deployment.apps/nfs-busybox created

$ kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP           NODE            NOMINATED NODE
nfs-busybox-5c98957964-g7mps   1/1     Running   0          2m    172.20.3.2   10.222.77.132   <none>

建立成功,這裡提一下,如果不確認寫的 yaml 檔案是否有問題,可以加上 --validate 引數驗證一下,如果不想驗證就可加上 --validate=false。最後,進入容器內驗證一下是否成功掛載吧!

$ kubectl exec -it nfs-busybox-5c98957964-g7mps /bin/sh
/ $ df -h
Filesystem                Size      Used Available Use% Mounted on
overlay                  32.0G      2.4G     29.6G   7% /
tmpfs                    64.0M         0     64.0M   0% /dev
tmpfs                     1.1G         0      1.1G   0% /sys/fs/cgroup
10.222.77.86:/data/nfs0  27.0G     10.8G     16.1G  40% /mnt
......

/ $ cat /mnt/index.html 
Tue Nov  6 12:54:11 UTC 2018
nfs-busybox-5c98957964-g7mps

我們看到成功掛載到指定路徑並生成了檔案,NFS 服務端也驗證一下吧!

# NFS 伺服器檢視
$ ll /data/nfs0/
total 4
-rw-r--r-- 1 root root 58 Nov  7 14:16 index.html

5、PV & PVC 方式使用 NFS

我們知道 k8s 提供了兩種 API 資源方式:PersistentVolume 和 PersistentVolumeClaim 來解決 Pod 刪除掉,掛載 volume 中的資料丟失的問題,PV 擁有獨立與 Pod 的生命週期,即使 Pod 刪除了,但 PV 還在,PV 上的資料依舊存在,而 PVC 則定義使用者對儲存資源 PV 的請求消耗。接下來,來演示下如何使用 PV & PVC 方式使用 NFS。同樣,我們也需要去 NFS 服務端機器(10.222.77.86)建立一個 /data/nfs1 目錄作為遠端共享檔案儲存目錄。

$ mkdir -p /data/nfs1
# 修改配置
$ vim /etc/exports
/data/nfs1 *(rw,sync,insecure,no_subtree_check,no_root_squash)

# 使配置生效
$ exportfs -r

# 服務端檢視下是否生效
$ showmount -e localhost
Export list for localhost:
/data/nfs1  *
/data/nfs0  *
/data/share 10.222.77.0/24

然後,分別建立 PV 和 PVC,yaml 檔案如下:

$ vim nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  nfs:
    path: /data/nfs1
    server: 10.222.77.86

$ vim nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: "slow"
  resources:
    requests:
      storage: 1Gi

說明一下,這裡 PV 定義了 accessModesReadWriteMany 即多次讀寫模式,NFS 是支援三種模式的 ReadWriteOnce、ReadOnlyMany、ReadWriteMany,詳細可參考 這裡 檢視,實際使用中,按需配置,待會下邊驗證一下該型別是否支援多次讀寫。接下來,建立並驗證一下 PV & PVC 是否成功。

$ kubectl create -f nfs-pv.yaml 
persistentvolume/nfs-pv created
$ kubectl create -f nfs-pvc.yaml 
persistentvolumeclaim/nfs-pvc created

$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
nfs-pv   1Gi        RWX            Recycle          Bound    default/nfs-pvc   slow                    51s
$ kubectl get pvc
NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nfs-pvc   Bound    nfs-pv   1Gi        RWX            slow           46s

OK,成功建立,並處於 Bound 狀態。接下來,建立一個掛載該 PVC 的 Pod,yaml 檔案如下:

$ vim nfs-busybox-pvc.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nfs-busybox-pvc
spec:
  replicas: 1
  selector:
    matchLabels:
      name: nfs-busybox-pvc
  template:
    metadata:
      labels:
        name: nfs-busybox-pvc
    spec:
      containers:
      - image: busybox
        command:
          - sh
          - -c
          - 'while true; do date > /mnt/index.html; hostname >> /mnt/index.html; sleep 10m; done'
        imagePullPolicy: IfNotPresent
        name: busybox
        volumeMounts:
          - name: nfs
            mountPath: "/mnt"
      volumes:
      - name: nfs
        persistentVolumeClaim:
          claimName: nfs-pvc

這裡就不多分析了,跟上邊的 Deployment 類似,只是更換了掛載卷方式為 PVC。然後,建立一下該 Deployment。

$ kubectl create -f nfs-busybox-pvc.yaml 
deployment.apps/nfs-busybox-pvc created
$ kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
nfs-busybox-pvc-5d5597d976-8lkvs   1/1     Running   0          12s

建立成功。接著,驗證一下是否成功掛載吧。

$ kubectl exec -it nfs-busybox-pvc-5d5597d976-8lkvs /bin/sh
/ $ df -h
Filesystem                Size      Used Available Use% Mounted on
overlay                  32.0G      2.4G     29.6G   7% /
tmpfs                    64.0M         0     64.0M   0% /dev
tmpfs                     1.1G         0      1.1G   0% /sys/fs/cgroup
10.222.77.86:/data/nfs1  27.0G     10.8G     16.1G  40% /mnt
......

/ $ cat /mnt/index.html 
Tue Nov  6 13:28:36 UTC 2018
nfs-busybox-pvc-5d5597d976-8lkvs

最後,驗證一下多次讀寫模式是否可行。

# nfs-busybox-pvc 容器內操作
/ $ echo "This message is from nfs-busybox-pvc." > /mnt/message.txt

# NFS 服務端操作,nfs 端寫入,客戶端檢視
$ cat /data/nfs1/message.txt 
This message is from nfs-busybox-pvc.
$ echo "This message is from nfs-server." >> /data/nfs1/message.txt 

# nfs-busybox-pvc 容器內操作
/ $ cat /mnt/message.txt 
This message is from nfs-busybox-pvc.
This message is from nfs-server.

6、StorageClasses 動態建立 PV 方式使用 NFS

PV 支援 Static 靜態請求,即提前準備好固定大小的資源。但是每次都需要管理員手動去建立對應的 PV資源,確實不方便。還好 PV 同時支援 Dynamic 動態請求,k8s 提供了 provisioner 來動態建立 PV,不僅大大節省了時間,而且還可以根據不同的 StorageClasses 封裝不同型別的儲存供 PVC 使用。接下來,我們演示下如何配置 NFS 型別的 StorageClasses 來動態建立 PV。

這裡要說一下,k8s 預設內部 provisioner 支援列表中,是不支援 NFS 的,如果我們要使用該 provisioner 該怎麼辦呢?方案就是使用外部 provisioner,這裡可參照 kubernetes-incubator/external-storage 這個來建立,跟之前 初試 Kubernetes 叢集使用 Ceph RBD 塊儲存 文章中演示類似,參照這個專案,可以提供外部 provisioner 來支援動態建立 PV 的功能。

在開始建立之前,我們還是需要去 NFS 服務端(10.222.77.86)建立一個 /data/nfs2 共享儲存目錄,後續動態建立的 PV 卷目錄都在該目錄下。

$ mkdir -p /data/nfs2
# 修改配置
$ vim /etc/exports
/data/nfs2 *(rw,sync,insecure,no_subtree_check,no_root_squash)

# 使配置生效
$ exportfs -r

# 服務端檢視下是否生效
$ showmount -e localhost
Export list for localhost:
/data/nfs2  *
/data/nfs1  *
/data/nfs0  *
/data/share 10.222.77.0/24

然後,我們參照 external-storage/nfs-client 文件來完成自定義外部 nfs-provisioner 的建立。參照文件,NFS-Client Provisioner 可以使用 Helm 部署非 Helm 部署方式,採用 Helm 部署非常方便,只需要一條命令即可。

$ helm install stable/nfs-client-provisioner --set nfs.server=x.x.x.x --set nfs.path=/exported/path

如果採用非 helm 部署方式,稍微麻煩一些,不過也不是很難,這裡就演示下這種方式吧!首先,我們需要 Clone 下該專案到本地。

$ git clone https://github.com/kubernetes-incubator/external-storage.git
$ tree external-storage/nfs-client/deploy/
external-storage/nfs-client/deploy/
├── class.yaml
├── deployment-arm.yaml
├── deployment.yaml
├── objects
│   ├── README.md
│   ├── class.yaml
│   ├── clusterrole.yaml
│   ├── clusterrolebinding.yaml
│   ├── deployment-arm.yaml
│   ├── deployment.yaml
│   ├── role.yaml
│   ├── rolebinding.yaml
│   └── serviceaccount.yaml
├── rbac.yaml
├── test-claim.yaml
└── test-pod.yaml

我們用到的檔案就在 external-storage/nfs-client/deploy/ 該目錄下,然後修改 class.yamldeployment.yaml,注意:這兩個檔案也可以不修改,使用預設值也可以,只不過生成的 provisioner 名稱為 fuseim.pri/ifs,我們可以修改成自定義的名稱,同時修改下 nfs-client-provisioner 映象為國內可拉取映象,以及 nfs 配置資訊。

# class.yaml 檔案修改前後對比
-  name: managed-nfs-storage
-provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
+  name: my-nfs-storage-class
+provisioner: my-nfs-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'

# deployment.yaml 檔案修改前後對比
-          image: quay.io/external_storage/nfs-client-provisioner:latest
+          image: jmgao1983/nfs-client-provisioner:latest
           env:
             - name: PROVISIONER_NAME
-              value: fuseim.pri/ifs
+              value: my-nfs-provisioner
             - name: NFS_SERVER
-              value: 10.10.10.60
+              value: 10.222.77.86
             - name: NFS_PATH
-              value: /ifs/kubernetes
+              value: /data/nfs2

           nfs:
-            server: 10.10.10.60
-            path: /ifs/kubernetes
+            server: 10.222.77.86
+            path: /data/nfs2

提一下,這裡的 rbac.yaml 當 k8s 開啟了 rbac 認證時使用,預設是 default 名稱空間,如果非 default,則需要修改為對應名稱空間,這裡我就是使用 default,就不用修改了。然後,建立一下這些資源。

$ kubectl create -f class.yaml 
storageclass.storage.k8s.io/my-nfs-storage-class created
$ kubectl create -f rbac.yaml 
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
$ kubectl create -f deployment.yaml 
serviceaccount/nfs-client-provisioner created
deployment.extensions/nfs-client-provisioner created

建立完畢,檢視下是否建立成功。

$ kubectl get sc
NAME                   PROVISIONER          AGE
my-nfs-storage-class   my-nfs-provisioner   1m27s
$ kubectl get pods 
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-6787dcc59-tgcc5   1/1     Running   0          2m19s

最後,我們需要提供一下使用 my-nfs-storage-class 的 PVC 資源以及使用掛載該 PVC 資源的 Pod。PVC 的 yaml 檔案如下:

$ vim nfs-sc-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nfs-sc-pvc
  annotations:
    volume.beta.kubernetes.io/storage-class: "my-nfs-storage-class"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

建立一下,看下是否能夠建立成功,是否能夠動態建立 PV。

$ kubectl create -f nfs-sc-pvc.yaml 
persistentvolumeclaim/nfs-sc-pvc created

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS           REASON   AGE
pvc-176dbc3b-e1d6-11e8-ac77-0800272ff396   1Gi        RWX            Delete           Bound    default/nfs-sc-pvc   my-nfs-storage-class            60s

$ kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS           AGE
nfs-sc-pvc   Bound    pvc-176dbc3b-e1d6-11e8-ac77-0800272ff396   1Gi        RWX            my-nfs-storage-class   15s

可以看到,成功的動態建立了 PV,達到預期效果。NFS 服務端檢視下是否已建立該 PV 對應的卷目錄。

# NFS 伺服器上操作
$ ll /data/nfs2/
total 0
drwxrwxrwx 2 root root 6 Nov  7 16:22 default-nfs-sc-pvc-pvc-176dbc3b-e1d6-11e8-ac77-0800272ff396

自動建立了,非常方便了,有沒有!最後建立掛載 PVC 的 Deployment,yaml 檔案如下:

$ vim nfs-busybox-sc-pvc.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nfs-busybox-sc-pvc
spec:
  replicas: 1
  selector:
    matchLabels:
      name: nfs-busybox-sc-pvc
  template:
    metadata:
      labels:
        name: nfs-busybox-sc-pvc
    spec:
      containers:
      - image: busybox
        command:
          - sh
          - -c
          - 'while true; do date > /mnt/index.html; hostname >> /mnt/index.html; sleep 10m; done'
        imagePullPolicy: IfNotPresent
        name: busybox
        volumeMounts:
          - name: nfs
            mountPath: "/mnt"
      volumes:
      - name: nfs
        persistentVolumeClaim:
          claimName: nfs-sc-pvc

建立一下該 Deployment。

$ kubectl create -f nfs-busybox-sc-pvc.yaml
deployment.apps/nfs-busybox-sc-pvc created
$ kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-busybox-sc-pvc-5f8d5b4b96-fwh6b      1/1     Running   0          7s
nfs-client-provisioner-6787dcc59-tgcc5   1/1     Running   0          8m57s

建立成功,最後驗證一下是否成功掛載吧。

$ kubectl exec -it nfs-busybox-sc-pvc-5f8d5b4b96-fwh6b /bin/sh
/ $ df -h
Filesystem                Size      Used Available Use% Mounted on
overlay                  32.0G      2.4G     29.6G   8% /
tmpfs                    64.0M         0     64.0M   0% /dev
tmpfs                     1.1G         0      1.1G   0% /sys/fs/cgroup
10.222.77.86:/data/nfs2/default-nfs-sc-pvc-pvc-176dbc3b-e1d6-11e8-ac77-0800272ff396
                         27.0G     10.9G     16.1G  40% /mnt
......

/ $ cat /mnt/index.html 
Tue Nov  6 15:14:04 UTC 2018
nfs-busybox-sc-pvc-5f8d5b4b96-fwh6b

# NFS 伺服器端驗證
$ cat /data/nfs2/default-nfs-sc-pvc-pvc-176dbc3b-e1d6-11e8-ac77-0800272ff396/index.html 
Tue Nov  6 15:14:04 UTC 2018
nfs-busybox-sc-pvc-5f8d5b4b96-fwh6b

最後,多提一點,上邊動態建立 PV 方式,只能動態建立到 nfs-client-provisioner 指定掛載的目錄裡面,如果我們想根據不同的服務分別動態建立到不同的 NFS 共享目錄裡面的話,可以多建立幾個 nfs-client-provisioner,每個 provisioner 指定掛載不同的 NFS 儲存路徑即可。

參考資料