NFS存储
更新于:2024年3月8日
NFS存储
目录
[TOC]
1、NFS 共享存储
前面我们学习了 hostPath 与 Local PV 两种本地存储方式,但是平时我们的应用更多的是无状态服务,可能会同时发布在不同的节点上,这个时候本地存储就不适用了,往往就需要使用到共享存储了,比如最简单常用的网络共享存储 NFS,本节课我们就来介绍下如何在 Kubernetes 下面使用 NFS 共享存储。
NFS卷:提供对NFS挂载支持,可以自动将NFS共享路径挂载到Pod中
NFS:是一个主流的文件共享服务器。
2、原理
我们只是在 volumes 中指定了我们上面创建的 PVC 对象,当这个 Pod 被创建之后, kubelet 就会把这个 PVC 对应的这个 NFS 类型的 Volume(PV)挂载到这个 Pod 容器中的目录中去。前面我们也提到了这样的话对于普通用户来说完全就不用关心后面的具体存储在 NFS 还是 Ceph 或者其他了,只需要直接使用 PVC 就可以了,因为真正的存储是需要很多相关的专业知识的,这样就完全职责分离解耦了。
普通用户直接使用 PVC 没有问题, 但是也会出现一个问题**,那就是当普通用户创建一个 PVC 对象的时候,这个时候系统里面并没有合适的 PV 来(或者没有默认的stora)和它进行绑定,因为 PV 大多数情况下是管理员给我们创建的,这个时候启动 Pod 肯定就会失败了**,如果现在管理员如果去创建一个对应的 PV 的话,PVC 和 PV 当然就可以绑定了,然后 Pod 也会自动的启动成功,这是因为在 Kubernetes 中有一个专门处理持久化存储的控制器 Volume Controller,这个控制器下面有很多个控制循环,其中一个就是用于 PV 和 PVC 绑定的 PersistentVolumeController
。
PersistentVolumeController
会不断地循环去查看每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与未绑定的 PVC 进行绑定,这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它就能够很快进入绑定状态。而所谓将一个 PV 与 PVC 进行绑定,其实就是将这个 PV 对象的名字,填在了 PVC 对象的 spec.volumeName
字段上。
PV 和 PVC 绑定上了,那么又是如何将容器里面的数据进行持久化的呢,我们知道 Docker 的 Volume 挂载其实就是将一个宿主机上的目录和一个容器里的目录绑定挂载在了一起,具有持久化功能当然就是指的宿主机上面的这个目录了,当容器被删除或者在其他节点上重建出来以后,这个目录里面的内容依然存在,所以一般情况下实现持久化是需要一个远程存储的,比如 NFS、Ceph 或者云厂商提供的磁盘等等。所以接下来需要做的就是持久化宿主机目录这个过程。
当 Pod 被调度到一个节点上后,节点上的 kubelet 组件就会为这个 Pod 创建它 的 Volume 目录,默认情况下 kubelet 为 Volume 创建的目录在 kubelet 工作目录下面:
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
比如上面我们创建的 Pod 对应的 Volume 目录完整路径为:
/var/lib/kubelet/pods/effdc8a1-1981-4a26-924a-c62a246a5865/volumes/kubernetes.io~nfs/pvc-af1b2b91-38b6-4bf0-99f1-cc2fa4fb2d92/
提示
要获取 Pod 的唯一标识 uid:
方法1:可通过命令 `kubectl get pod pod名 -o jsonpath={.metadata.uid}` 获取。
[root@master1 pods]#kubectl get po nfs-sc-pod -o jsonpath={.metadata.uid}
effdc8a1-1981-4a26-924a-c62a246a5865
方法2:可直接导出pod的yaml并用grep筛选。
[root@master1 pods]#kubectl get po nfs-sc-pod -oyaml|grep uid
uid: effdc8a1-1981-4a26-924a-c62a246a5865
然后就需要根据我们的 Volume 类型来决定需要做什么操作了,假如后端存储使用的 Ceph RBD,那么 kubelet 就需要先将 Ceph 提供的 RBD 挂载到 Pod 所在的宿主机上面,这个阶段在 Kubernetes 中被称为 Attach 阶段。Attach 阶段完成后,为了能够使用这个块设备,kubelet 还要进行第二个操作,即:格式化这个块设备,然后将它挂载到宿主机指定的挂载点上。这个挂载点,也就是上面我们提到的 Volume 的宿主机的目录。将块设备格式化并挂载到 Volume 宿主机目录的操作,在 Kubernetes 中被称为 Mount 阶段。但是对于我们这里使用的 NFS 就更加简单了, 因为 NFS 存储并没有一个设备需要挂载到宿主机上面,所以这个时候 kubelet 就会直接进入第二个 Mount
阶段,相当于直接在宿主机上面执行如下的命令:
mount -t nfs 172.29.9.51:/var/lib/k8s/data/ /var/lib/kubelet/pods/effdc8a1-1981-4a26-924a-c62a246a5865/volumes/kubernetes.io~nfs/pvc-af1b2b91-38b6-4bf0-99f1-cc2fa4fb2d92/
同样可以在测试的 Pod 所在节点查看 Volume 的挂载信息:
[root@node1 ~]#findmnt /var/lib/kubelet/pods/effdc8a1-1981-4a26-924a-c62a246a5865/volumes/kubernetes.io~nfs/pvc-af1b2b91-38b6-4bf0-99f1-cc2fa4fb2d92/
TARGET SOURCE FSTYPE OPTIONS
/var/lib/kubelet/pods/effdc8a1-1981-4a26-924a-c62a246a5865/volumes/kubernetes.io~nfs/pvc-af1b2b91-38b6-4bf0-99f1-cc2fa4fb2d92
172.29.9.51:/var/lib/k8s/data/default-nfs-sc-pvc-pvc-af1b2b91-38b6-4bf0-99f1-cc2fa4fb2d92
我们可以看到这个 Volume 被挂载到了 NFS(172.29.9.51:/var/lib/k8s/data/)下面,以后我们在这个目录里写入的所有文件,都会被保存在远程 NFS 服务器上。
这样在经过了上面的阶段过后,我们就得到了一个持久化的宿主机上面的 Volume 目录了,接下来 kubelet 只需要把这个 Volume 目录挂载到容器中对应的目录即可,这样就可以为 Pod 里的容器挂载这个持久化的 Volume 了,这一步其实也就相当于执行了如下所示的命令:
# docker 或者 nerdctl
docker run -v /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>:/<容器内的目标目录> 我的镜像 ...
整个存储的架构可以用下图来说明:
- PV Controller:负责 PV/PVC 的绑定,并根据需求进行数据卷的 Provision/Delete 操作
- AD Controller:负责存储设备的 Attach/Detach 操作,将设备挂载到目标节点
- Volume Manager:管理卷的 Mount/Unmount 操作、卷设备的格式化等操作
- Volume Plugin:扩展各种存储类型的卷管理能力,实现第三方存储的各种操作能力和 Kubernetes 存储系统结合
我们上面使用的 NFS 就属于 In-Tree
这种方式,In-Tree
就是在 Kubernetes 源码内部实现的,和 Kubernetes 一起发布、管理的 ,但是更新迭代慢、灵活性比较差,另外一种方式 Out-Of-Tree
是独立于 Kubernetes 的,目前主要有 CSI
和 FlexVolume
两种机制,开发者可以根据自己的存储类型实现不同的存储插件接入到 Kubernetes 中去,其中 CSI
是现在也是以后主流的方式,接下来我们会主要介绍 CSI
这种存储插件的使用。
3、安装
4、使用
💘 实战:nfs共享存储测试,未使用到pv/pvc(测试成功)-2022.8.13
🍀 创建deployment的yaml并修改
注意:这里在deployment.yaml中修改下nfs字段,工作中一般不这么使用,而是直接使用pv,pvc的,本次实验让你知道下pod之间共享数据的网络存储卷nfs解决方法。
[root@k8s-master ~]#vim nfs.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web2
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: wwwroot
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumes: # 数据卷来源
- name: wwwroot
nfs:
server: 172.29.9.33
path: /ifs/kubernetes
🍀 apply下nfs.yaml并查看
[root@k8s-master ~]#kubectl apply -f nfs.yaml
deployment.apps/web2 created
#暴露deployment web2
[root@k8s-master ~]#kubectl expose deployment web2 --port=80 --target-port=80 --type=NodePort
service/web2 exposed
#查看
[root@k8s-master ~]#kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/web2-7d7fc87d85-9lszr 1/1 Running 0 7m51s
pod/web2-7d7fc87d85-b4pbr 1/1 Running 0 7m51s
pod/web2-7d7fc87d85-w4xll 1/1 Running 0 7m51s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 26d
service/web2 NodePort 10.103.200.91 <none> 80:32359/TCP 13m
[root@k8s-master ~]#
🍀 验证效果
注意:此时通过浏览器访问报错了
403一般是权限类的错误:
[root@k8s-master ~]#kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/web2-7d7fc87d85-9lszr 1/1 Running 0 11m
pod/web2-7d7fc87d85-b4pbr 1/1 Running 0 11m
pod/web2-7d7fc87d85-w4xll 1/1 Running 0 11m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 26d
service/web2 NodePort 10.103.200.91 <none> 80:32359/TCP 16m
[root@k8s-master ~]#kubectl exec -it web2-7d7fc87d85-9lszr -- bash #进入到容器查看
root@web2-7d7fc87d85-9lszr:/# cd /usr/share/nginx/html/
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html# ls
1.txt 2.txt
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html#
#因为这个nginx的主目录是从nfs直接挂载的,且nfs目录下没有相应的index.html文件,所以这里当然报错了;
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html# df -hT
Filesystem Type Size Used Avail Use% Mounted on
overlay overlay 17G 6.5G 11G 38% /
tmpfs tmpfs 64M 0 64M 0% /dev
tmpfs tmpfs 910M 0 910M 0% /sys/fs/cgroup
/dev/mapper/centos-root xfs 17G 6.5G 11G 38% /etc/hosts
shm tmpfs 64M 0 64M 0% /dev/shm
172.29.9.33:/ifs/kubernetes nfs4 17G 6.8G 11G 40% /usr/share/nginx/html #挂载目录
tmpfs tmpfs 910M 12K 910M 1% /run/secrets/kubernetes.io/serviceaccount
tmpfs tmpfs 910M 0 910M 0% /proc/acpi
tmpfs tmpfs 910M 0 910M 0% /proc/scsi
tmpfs tmpfs 910M 0 910M 0% /sys/firmware
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html#
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html# pwd
/usr/share/nginx/html
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html# ls #且nfs目录下没有相应的index.html文件
1.txt 2.txt
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html#
#因此这里手动创建一个index.html文件进行测试:
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html# echo "<h1>i miss you</h1>" > index.html
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html# ls
1.txt 2.txt index.html
root@web2-7d7fc87d85-9lszr:/usr/share/nginx/html#
#nfs服务端挂载目录下也会出现这个文件:
[root@k8s-node2 ~]#ls /ifs/kubernetes/
1.txt 2.txt index.html
[root@k8s-node2 ~]#
#其他2个容器下也是有的,因为用一个pod下各个容器之间是共享一切的;
[root@k8s-master ~]#kubectl get pod
NAME READY STATUS RESTARTS AGE
web2-7d7fc87d85-9lszr 1/1 Running 0 21m
web2-7d7fc87d85-b4pbr 1/1 Running 0 21m
web2-7d7fc87d85-w4xll 1/1 Running 0 21m
[root@k8s-master ~]#kubectl exec -it web2-7d7fc87d85-b4pbr -- bash
root@web2-7d7fc87d85-b4pbr:/# ls /usr/share/nginx/html/
1.txt 2.txt index.html #该index.html 文件存在
root@web2-7d7fc87d85-b4pbr:/#
index.html文件创建好后,再次到浏览器上测试:=>此时已经符合预期实验效果了。
即,可以通过3个节点的nodeip:port均可以访问这个nginx服务;
实验结束,完美。😘
💘 实战:nfs共享存储测试,有使用到pv/pvc(测试成功)-2022.8.13
上面已经安装好了nfs服务,也测试过k8s节点是可以正常访问nfs服务的;
前面我们已经了解到了 PV、PVC、StorgeClass 的使用,那么我们的 NFS 又应该如何在 Kubernetes 中使用呢?
🍀 创建pv.yaml,p
同样创建一个如下所示 nfs 类型的 PV 资源对象:
# 01-nfs-volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
path: /var/lib/k8s/data/ # 指定nfs的挂载点
server: 172.29.9.51 # 指定nfs服务地址
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
🍀 我们知道用户真正使用的是 PVC,而要使用 PVC 的前提就是必须要先和某个符合条件的 PV 进行一一绑定,比如存储容器、访问模式,以及 PV 和 PVC 的 storageClassName 字段必须一样,这样才能够进行绑定,当 PVC 和 PV 绑定成功后就可以直接使用这个 PVC 对象了:
# 02-nfs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-volumes
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs-pvc
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: nfs
subPath: test-volumes #注意 :这个就是在nfs目录会创建有一个子目录`test-volumes`
mountPath: "/usr/share/nginx/html"
🍀 直接创建上面的资源对象即可:
$kubectl apply -f 01-nfs-volume.yaml
persistentvolume/nfs-pv created
persistentvolumeclaim/nfs-pvc created
$kubectl apply -f 02-nfs-pod.yaml
pod/test-volumes created
$kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv 1Gi RWO Retain Bound default/nfs-pvc manual 54s
$kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs-pv 1Gi RWO manual 62s
$kubectl get po test-volumes -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-volumes 1/1 Running 0 66s 10.244.2.60 node2 <none> <none>
🍀 由于我们这里 PV 中的数据为空,所以挂载后会将 nginx 容器中的 /usr/share/nginx/html
目录覆盖,那么访问应用的时候就没有内容了(就会报403错误):
[root@master1 ~]#curl 10.244.2.60:80
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.21.5</center>
</body>
</html>
我们可以在 PV 目录中添加一些内容:
# 在 nfs 服务器上面执行
[root@master1 ~]#echo "nfs pv content" > /var/lib/k8s/data/test-volumes/index.html
[root@master1 ~]#curl 10.244.2.60:80
nfs pv content
然后重新访问就有数据了,而且当我们的 Pod 应用挂掉或者被删掉重新启动后数据还是存在的,因为数据已经持久化了:
$ kubectl delete -f 02-nfs-pod.yaml
pod "test-volumes" deleted
$ kubectl apply -f 02-nfs-pod.yaml
pod/test-volumes created
$ kubectl get po test-volumes -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-volumes 1/1 Running 0 17s 10.244.2.88 node2 <none> <none>
# curl 10.244.2.88:80
nfs pv content
实验结束,完美。😘