Z.S.K.'s Records

Kubernetes学习(NFS如何实现 Volume Dynamic Provisioning)

NFS在kubernetes中做为共享存储,用的还是比较多的,之前也就是一直用着,也没有太关注是如何实现Dynamic provisioning,今天花了点时间研究了一下原理,还是挺有意思.

NFS Servers

1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装
apt install yum -y install nfs-utils rpcbind
# 新建目录
mkdir -p /data/nfs
# 加入
echo "/data/nfs/fluentd-conf *(rw,sync,no_subtree_check,no_root_squash)" >> /etc/export
# 验证
exportfs -r
# 启动nfs
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server

NFS Client

client端只需要二步即可使用server端share出来的目录

1
2
3
4
5
# 创建目录
mkdir /media/nfs_dir
# mount
mount 192.168.8.150:/data/nfs /media/nfs_dir
# 查看

当然,在kubernetes中主要还是使用Dynamic NFS provisioning为主, 不会直接像上面这样使用nfs

这里还是将3种方式都贴一下.

使用volume

nfs做为volume还是有好几种使用方式的:

做为普通volume

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
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: redis
spec:
selector:
matchLabels:
app: redis
revisionHistoryLimit: 2
template:
metadata:
labels:
app: redis
spec:
containers:
# 应用的镜像
- image: redis
name: redis
imagePullPolicy: IfNotPresent
# 应用的内部端口
ports:
- containerPort: 6379
name: redis6379
env:
- name: ALLOW_EMPTY_PASSWORD
value: "yes"
- name: REDIS_PASSWORD
value: "redis"
# 持久化挂接位置,在docker中
volumeMounts:
- name: redis-persistent-storage
mountPath: /data
volumes:
# 宿主机上的目录
- name: redis-persistent-storage
nfs:
path: /data/nfs/redis/data
server: 192.168.8.150

作为persistentVolume

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1 
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.1
# 此持久化存储卷使用nfs插件
nfs:
# nfs共享目录为/tmp
path: /data/nfs/data
# nfs服务器的地址
server: 192.168.5.150

上面两种方式在kubernetes用nfs做为持久卷时用的还是比较少,因为需要事先在nfs中创建好相关的目录,存在一些手工操作. 重点在下.

作为Dynamic NFS provisioning

helm部署参考nfs-client-provisioner

这里选择重要的资源对象来阐述nfs是如何实现dynamic provision功能的

首先来看一下storageclass,这个也没什么好说的.

1
2
3
4
5
6
7
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storageclass # IMPORTANT pvc needs to mention this name
provisioner: nfs-test # name can be anything
parameters:
archiveOnDelete: "false"

还会部署一个deployment资源

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-pod-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-pod-provisioner
spec:
serviceAccountName: nfs-pod-provisioner-sa # name of service account created in rbac.yaml
containers:
- name: nfs-pod-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-provisioner-v
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME # do not change
value: nfs-test # SAME AS PROVISONER NAME VALUE IN STORAGECLASS
- name: NFS_SERVER # do not change
value: 192.168.1.5 # Ip of the NFS SERVER
- name: NFS_PATH # do not change
value: /nfs/data # path to nfs directory setup
volumes:
- name: nfs-provisioner-v # same as volumemouts name
nfs:
server: 192.168.1.5
path: /nfs/data

这个deployment其实是做为一个nfs client起作用,它把整个nfs server的共享目录/nfs/data都挂载到了/persistentvolumes下, 这为后面根据pvc自动生成创建pv目录有关.

生成一个pvc

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc-test
spec:
storageClassName: nfs-storageclass # SAME NAME AS THE STORAGECLASS
accessModes:
- ReadWriteMany # must be the same as PersistentVolume
resources:
requests:
storage: 50Mi

当使用一个pod指定使用使用上面的pvc后,上面的deployment会根据pvc自动生成一个pv, pvc与该pv进行绑定, 且在nfs servers上创建相应的目录,pod将该目录挂载进了自己容器目录中,这样就实现了Dynamic provisioning功能了

1
2
3
4
$ kubectl apply -f  nfs_pvc_dynamic.yaml
nfs-pvc-test Bound pvc-620ff5b1-b2df-11e9-a66a-080027db98ca 50Mi RWX nfs-storageclass 7s
$ ls /nfs/data/
default-nfs-pvc-test-pvc-620ff5b1-b2df-11e9-a66a-080027db98ca

nfs-pvc-test为pvc, pvc-620ff5b1-b2df-11e9-a66a-080027db98ca为pv, default-nfs-pvc-test-pvc-620ff5b1-b2df-11e9-a66a-080027db98ca即为在nfs-server共享目录下创建的目录,这个目录最后被为使用该pvc的pod挂载.

这个deployment的源码可参考nfs-client-provisioner, deployment核心代码如下:

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
func (p *nfsProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
if options.PVC.Spec.Selector != nil {
return nil, fmt.Errorf("claim Selector is not supported")
}
glog.V(4).Infof("nfs provisioner: VolumeOptions %v", options)

pvcNamespace := options.PVC.Namespace
pvcName := options.PVC.Name

pvName := strings.Join([]string{pvcNamespace, pvcName, options.PVName}, "-")

fullPath := filepath.Join(mountPath, pvName) // 这里指定了生成目录的格式, 其中 mountPath即为/persistentvolumes
glog.V(4).Infof("creating path %s", fullPath)
if err := os.MkdirAll(fullPath, 0777); err != nil {
return nil, errors.New("unable to create directory to provision new pv: " + err.Error())
}
os.Chmod(fullPath, 0777)

path := filepath.Join(p.path, pvName)
size, err := getNfsCapacity()
if err != nil {
return nil, errors.New("unable to get nfs size to provision new pv: " + err.Error())
}
fmt.Println("=====", size.Value())

pv := &v1.PersistentVolume{ // 根据pvc 生成pv
ObjectMeta: metav1.ObjectMeta{
Name: options.PVName,
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
AccessModes: options.PVC.Spec.AccessModes,
MountOptions: options.MountOptions,
Capacity: v1.ResourceList{
//v1.ResourceName(v1.ResourceStorage): options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)],
v1.ResourceName(v1.ResourceStorage): *size,
},
PersistentVolumeSource: v1.PersistentVolumeSource{
NFS: &v1.NFSVolumeSource{
Server: p.server,
Path: path,
ReadOnly: false,
},
},
},
}
return pv, nil
}

因此整个流程为:

pod生成使用一个pvc, 当pvc发布到集群中时, nfs-client-provisioner deployment就会根据该pvc的yaml文件获取namespace, pvc-name等信息在挂载的nfs共享目录下创建新的目录, 然后生成一个pv与该pvc绑定,pod使用该pv就相当于使用nfs server共享目录一样

那nfs server的共享目录是如何做到的呢?

NFS 原理

NFS 使用RPC(Remote Procedure Call)的机制进行实现,RPC使得客户端可以调用服务端的函数。同时,由于有 VFS 的存在,客户端可以像使用其它普通文件系统一样使用 NFS 文件系统。经由操作系统的内核,将 NFS 文件系统的调用请求通过 TCP/IP 发送至服务端的 NFS 服务。NFS服务器执行相关的操作,并将操作结果返回给客户端

我觉得上面那段话说的很明白, nfs实现共享存储还是通过网络将本地操作写入到nfs server端,只不过这个网络操作由RPC来完成, 同时也需要内核的支持.

nfs更加详细的原理,感兴趣的可以参考NFS原理详解

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)

 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?