文章目录
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 集群架构图吧!
部署完成后,通过如下命令查看下是否部署成功。
$ 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 定义了 accessModes
为 ReadWriteMany
即多次读写模式,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.yaml
和 deployment.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 存储路径即可。
参考资料