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 存储路径即可。

参考资料

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值