NFS

NFS 是 Network File System 的缩写,即网络文件系统。功能是让客户端通过网络访问不同主机上磁盘里的数据,主要用在类Unix系统上实现文件共享的一种方法。 本例演示 CentOS 7 下安装和配置 NFS 的基本步骤。

记得保证所有pod可部署机器都要安装NFS服务端或者NFS客户端,否则出现类似错误:wrong fs type, bad option, bad superblock on 192.168.1.5:/home/shared, missing codepage or helper program, or other error

NFS服务的优缺点

优点

  • 节省本地存储空间将常用的数据存放在一台服务器可以通过网络访问

  • 简单容易上手

  • 方便部署非常快速,维护十分简单

缺点

  • 局限性容易发生单点故障,及server机宕机了所有客户端都不能访问

  • 在高并发下NFS效率/性能有限

  • 客户端没用用户认证机制,且数据是通过明文传送,安全性一般(一般建议在局域网内使用)

  • NFS的数据是明文的,对数据完整性不做验证

  • 多台机器挂载NFS服务器时,连接管理维护麻烦

环境说明

CentOS 7(Minimal Install)

1
2
$ cat /etc/redhat-release 
CentOS Linux release 7.5.1804 (Core)

本例演示环境如下

NameIP AddrDescprition
Server192.169.0.110服务端 IP
Client192.168.0.101客户端 IP

根据官网说明 Chapter 8. Network File System (NFS) - Red Hat Customer Portal,CentOS 7.4 以后,支持 NFS v4.2 不需要 rpcbind 了,但是如果客户端只支持 NFC v3 则需要 rpcbind 这个服务。

服务端

服务端安装

使用 yum 安装 NFS 安装包。

1
$ sudo yum install nfs-utils -y

注意

只安装 nfs-utils 即可,rpcbind 属于它的依赖,也会安装上。

服务端配置

设置 NFS 服务开机启动

1
2
$ sudo systemctl enable rpcbind
$ sudo systemctl enable nfs

启动 NFS 服务

1
2
$ sudo systemctl start rpcbind
$ sudo systemctl start nfs

防火墙需要打开 rpc-bind 和 nfs 的服务

1
2
3
4
$ sudo firewall-cmd --zone=public --permanent --add-service={rpc-bind,mountd,nfs}
success
$ sudo firewall-cmd --reload
success

配置共享目录

服务启动之后,我们在服务端配置一个共享目录

1
2
$ sudo mkdir /data
$ sudo chmod 755 /data

根据这个目录,相应配置导出目录

1
$ sudo vi /etc/exports

添加如下配置

1
/data/     192.168.0.0/24(rw,sync,no_root_squash,no_all_squash)
  1. /data: 共享目录位置。
  2. 192.168.0.0/24: 客户端 IP 范围,* 代表所有,即没有限制。
  3. rw: 权限设置,可读可写。
  4. sync: 同步共享目录。
  5. no_root_squash: 可以使用 root 授权。
  6. no_all_squash: 可以使用普通用户授权。

:wq 保存设置之后,重启 NFS 服务。

1
$ sudo systemctl restart nfs

可以检查一下本地的共享目录

1
2
3
$ showmount -e localhost
Export list for localhost:
/data 192.168.0.0/24

这样,服务端就配置好了,接下来配置客户端,连接服务端,使用共享目录。

Linux 客户端

客户端安装

与服务端类似

1
$ sudo yum install nfs-utils -y

客户端配置

设置 rpcbind 服务的开机启动

1
$ sudo systemctl enable rpcbind

启动 NFS 服务

1
$ sudo systemctl start rpcbind

注意

客户端不需要打开防火墙,因为客户端时发出请求方,网络能连接到服务端即可。
客户端也不需要开启 NFS 服务,因为不共享目录。

客户端连接 NFS

先查服务端的共享目录

1
2
3
$ showmount -e 192.168.0.110
Export list for 192.168.0.110:
/data 192.168.0.0/24

在客户端创建目录

1
$ sudo mkdir /data

挂载

1
$ sudo mount -t nfs 192.168.0.101:/data /data

挂载之后,可以使用 mount 命令查看一下

1
2
3
4
$ mount
...
...
192.168.0.110:/data on /data type nfs4 (rw,relatime,sync,vers=4.1,rsize=131072,wsize=131072,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.0.100,local_lock=none,addr=192.168.0.101)

这说明已经挂载成功了。

测试 NFS

测试一下,在客户端向共享目录创建一个文件

1
2
$ cd /data
$ sudo touch a

之后取 NFS 服务端 192.168.0.101 查看一下

1
2
3
4
$ cd /data
$ ll
total 0
-rw-r--r--. 1 root root 0 Aug 8 18:46 a

可以看到,共享目录已经写入了。

客户端自动挂载

自动挂载很常用,客户端设置一下即可。

1
$ sudo vi /etc/fstab

在结尾添加类似如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
#
# /etc/fstab
# Created by anaconda on Thu May 25 13:11:52 2017
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/cl-root / xfs defaults 0 0
UUID=414ee961-c1cb-4715-b321-241dbe2e9a32 /boot xfs defaults 0 0
/dev/mapper/cl-home /home xfs defaults 0 0
/dev/mapper/cl-swap swap swap defaults 0 0
# 在这个示例中,`192.168.0.110:/data`是NFS服务器的地址和共享文件夹的路径,后面的`/data`是本地挂载点的路径,`nfs`表示使用NFS协议进行挂载,`defaults`表示使用默认挂载选项。
192.168.0.110:/data /data nfs defaults 0 0

由于修改了 /etc/fstab,需要重新加载 systemctl

1
$ sudo systemctl daemon-reload

之后查看一下

1
2
3
4
$ mount
...
...
192.168.0.110:/data on /data type nfs4 (rw,relatime,vers=4.1,rsize=131072,wsize=131072,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.0.100,local_lock=none,addr=192.168.0.101)

此时已经启动好了。如果实在不放心,可以重启一下客户端的操作系统,之后再查看一下。

卸载共享目录

共享目录使用结束之后,卸载共享目录,运行以下命令:

1
2
# 如果卸载失败,使用 -f 选项强制卸载。
umount /data

PV和PVC使用NFS静态存储

使用volumes显式的配置持久卷方式, 至少存在两个问题:

  1. Pod声明与底层存储耦合在一起,每次声明volume都需要配置存储类型以及该存储插件的一堆配置,如果是第三方存储,配置会非常复杂。

  2. 开发人员的需求可能只是需要一个20GB的卷,这种方式却不得不强制要求开发人员了解底层存储类型和配置。使用第三方的pv, 声明Pod都需要配置地址以及secret,特别麻烦。

于是引入了PV(Persistent Volume),PV其实就是把Volume的配置声明部分从Pod中分离出来:

  • PV(Persistent Volume):持久化存储卷,和node类似,是一种集群资源,由管理员定义,对接不同的存储
  • PVC(Persistent Volume Claims): 持久化存储声明,和pod类似,作为PV的使用者

利用静态的PV和PVC来测试一下NFS系统能否正常工作

pv_pvc.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv #pv的名字
labels:
pv: nfs #pvc可以使用标签选择器来选择该pv,storageClassName和标签选择器是AND关系,如果storageClassName相同,标签选择器不同则pvc一直是pending状态
spec:
capacity:
storage: 5Gi #pv存储空间的大小,这里的空间大小并不能xian'zhpod使用NFS空间的大小,作为pvc和pv绑定选择使用,pv的空间符合pvc的要求则可以进行绑定
volumeMode: Filesystem #存储卷模式,默认为Filesystem,可选项还有Block
accessModes:
- ReadWriteMany #存储卷的访问模式有三种ReadWriteOnce、ReadOnlyMany、ReadWriteMany
persistentVolumeReclaimPolicy: Retain #pv回收策略有三种Retain(保留。pv删除,存储数据保留,需删除需手动)、Recycle(回收。pv删除,存储的数据被清空删除掉)、Delete(删除。pv删除,则后端的存储设备也删除掉,常用语云厂商)
# 存储类别
# 配置了特定类别的PV只能与请求了该类别的PVC进行绑定
# 未设定类别的PV则只能与不请求任何类别的PVC进行绑定
storageClassName: slow #pv和pvc的该项保持一致才可以进行绑定(如果PVC定义了此字段,则PV也必须有对应字段才能进行绑定。)
nfs:
path: /data/k8s #NFS共享目录
server: 172.17.0.2 #NFS服务器地址
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc #pvc名字
spec:
accessModes: #pvc在请求pv的时候访问模式保持一致
- ReadWriteMany #同pv一样三种模式
volumeMode: Filesystem #同pv一样两种模式
resources:
requests:
storage: 3Gi #pvc空间的大小,并不能作为pod使用存储的空间限制,而是作为选择绑定pv的条件。
# 存储类别
# 配置了特定类别的PV只能与请求了该类别的PVC进行绑定
# 未设定类别的PV则只能与不请求任何类别的PVC进行绑定
storageClassName: slow #pvc和pv保持一致才可以进行绑定,否则一直penting状态(如果PVC定义了此字段,则PV也必须有对应字段才能进行绑定。)
selector: #pvc可以通过标签选择器选择绑定pv。如果同时设置selector和storageClassName,那么再进行绑定pv匹配选择的时候是AND的关系,两则同时满足才可以。
matchLabels:
pv: "nfs"
# matchExpressions:
# - {key: environment, operator: In, values: [dev]}
---
# pvc作为卷被pod挂载
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
- mountPath: "/var/www/html"
# 如果想要生效路径为`/data/k8s/html`,则配置subPath,否则会在/data/k8s根路径生效
# subPath: html
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: nfs-pvc

关于hostpath类型type的值 :
DirectoryOrCreate 目录存在就使用,不存在就先创建后使用
Directory 目录必须存在
FileOrCreate 文件存在就使用,不存在就先创建后使用
File 文件必须存在
Socket unix套接字必须存在
CharDevice 字符设备必须存在
BlockDevice 块设备必须存在

关于访问模式(accessModes)的值 :

  • ReadWriteOnce(RWO):如果您需要写入该卷,但您不要求多个 Pod 应该能够写入该卷,请使用ReadWriteOnce.
  • ReadOnlyMany(ROX):如果您只需要从卷中读取,并且您可能有多个 Pod 需要从卷中读取,您更喜欢将这些 Pod 灵活地调度到不同节点,并且ReadOnlyMany是给定 K8s 集群的卷插件的一个选项,使用ReadOnlyMany
  • ReadWriteMany(RWX):如果您需要写入卷,并且您可能有多个 Pod 需要写入卷,您更喜欢将这些 Pod 灵活地调度到不同节点,并且ReadWriteMany是给定 K8s 集群的卷插件的一个选项,使用ReadWriteMany.

关于回收策略(persistentVolumeReclaimPolicy)的值

Retain (保留) 保留数据,需要管理员手工清理数据
Recycle(回收) 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*
Delete (删除) 与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务
需要注意的是,底层不同的存储类型可能支持的回收策略不同

创建后,查看是否能够自动绑定(Bound状态)(kubectl get pv, kubect get pvc)

利用NFS动态提供Kubernetes后端存储卷

本文翻译自nfs-client-provisioner的说明文档,本文将介绍使用nfs-client-provisioner这个应用,利用NFS Server给Kubernetes作为持久存储的后端,并且动态提供PV。前提条件是有已经安装好的NFS服务器,并且NFS服务器与Kubernetes的Slave节点都能网络连通。 所有下文用到的文件来自于git clone https://github.com/kubernetes-incubator/external-storage.git的nfs-client目录。

nfs-client-provisioner

nfs-client-provisioner 是一个Kubernetes的简易NFS的外部provisioner,本身不提供NFS,需要现有的NFS服务器提供存储

  • PV以 ${namespace}-${pvcName}-${pvName}的命名格式提供(在NFS服务器上)
  • PV回收的时候以 archieved-${namespace}-${pvcName}-${pvName} 的命名格式(在NFS服务器上)

安装部署

  • 修改deployment文件并部署 deploy/deployment.yaml

需要修改的地方只有NFS服务器所在的IP地址(10.10.10.60),以及NFS服务器共享的路径(/ifs/kubernetes),两处都需要修改为你实际的NFS服务器和共享目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 10.10.10.60
- name: NFS_PATH
value: /ifs/kubernetes
volumes:
- name: nfs-client-root
nfs:
server: 10.10.10.60
path: /ifs/kubernetes
  • 修改StorageClass文件并部署 deploy/class.yaml

此处可以不修改,或者修改provisioner的名字,需要与上面的deployment的PROVISIONER_NAME名字一致。

1
2
3
4
5
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs

授权

如果您的集群启用了RBAC,或者您正在运行OpenShift,则必须授权provisioner。 如果你在非默认的“default”名称空间/项目之外部署,可以编辑deploy/auth/clusterrolebinding.yaml或编辑`oadm policy“指令。

如果启用了RBAC

需要执行如下的命令来授权。

1
2
3
4
5
6
7
$ kubectl create -f deploy/auth/serviceaccount.yaml
serviceaccount "nfs-client-provisioner" created
$ kubectl create -f deploy/auth/clusterrole.yaml
clusterrole "nfs-client-provisioner-runner" created
$ kubectl create -f deploy/auth/clusterrolebinding.yaml
clusterrolebinding "run-nfs-client-provisioner" created
$ kubectl patch deployment nfs-client-provisioner -p '{"spec":{"template":{"spec":{"serviceAccount":"nfs-client-provisioner"}}}}'

测试

测试创建PVC

  • kubectl create -f deploy/test-claim.yaml

测试创建POD

  • kubectl create -f deploy/test-pod.yaml

在NFS服务器上的共享目录下的卷子目录中检查创建的NFS PV卷下是否有"SUCCESS" 文件。

删除测试POD

  • kubectl delete -f deploy/test-pod.yaml

删除测试PVC

  • kubectl delete -f deploy/test-claim.yaml

在NFS服务器上的共享目录下查看NFS的PV卷回收以后是否名字以archived开头。

我的示例

  • NFS服务器配置

    1
    # cat /etc/exports
    1
    /media/docker        *(no_root_squash,rw,sync,no_subtree_check)
  • nfs-deployment.yaml示例

NFS服务器的地址是ubuntu-master,共享出来的路径是/media/docker,其他不需要修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# cat nfs-deployment.yaml
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: ubuntu-master
- name: NFS_PATH
value: /media/docker
volumes:
- name: nfs-client-root
nfs:
server: ubuntu-master
path: /media/docker
  • StorageClass示例

可以修改Class的名字,我的改成了default。

1
2
3
4
5
6
# cat class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: default
provisioner: fuseim.pri/ifs
  • 查看StorageClass
1
2
3
# kubectl get sc
NAME PROVISIONER AGE
default fuseim.pri/ifs 2d
  • 设置这个default名字的SC为Kubernetes的默认存储后端
1
2
3
4
5
# kubectl patch storageclass default -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
storageclass.storage.k8s.io "default" patched
# kubectl get sc
NAME PROVISIONER AGE
default (default) fuseim.pri/ifs 2d
  • 测试创建PVC

查看pvc文件

1
2
3
4
5
6
7
8
9
10
11
# cat test-claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi

创建PVC

1
2
3
4
5
6
# kubectl apply -f test-claim.yaml 
persistentvolumeclaim "test-claim" created
root@Ubuntu-master:~/kubernetes/nfs# kubectl get pvc|grep test
test-claim Bound pvc-fe3cb938-3f15-11e8-b61d-08002795cb26 1Mi RWX default 10s
# kubectl get pv|grep test
pvc-fe3cb938-3f15-11e8-b61d-08002795cb26 1Mi RWX Delete Bound default/test-claim default 58s
  • 启动测试POD

POD文件如下,作用就是在test-claim的PV里touch一个SUCCESS文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# cat test-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: gcr.io/google_containers/busybox:1.24
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim

启动POD,一会儿POD就是completed状态,说明执行完毕。

1
2
3
4
# kubectl apply -f test-pod.yaml 
pod "test-pod" created
kubectl get pod|grep test
test-pod 0/1 Completed 0 40s

我们去NFS共享目录查看有没有SUCCESS文件。

1
2
3
# cd default-test-claim-pvc-fe3cb938-3f15-11e8-b61d-08002795cb26
# ls
SUCCESS

说明部署正常,并且可以动态分配NFS的共享卷。