learn-tech/专栏/Kubernetes入门实战课/25PersistentVolume+NFS:怎么使用网络共享存储?.md
2024-10-16 13:06:13 +08:00

16 KiB
Raw Permalink Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        25 PersistentVolume + NFS怎么使用网络共享存储
                        你好我是Chrono。

在上节课里我们看到了Kubernetes里的持久化存储对象PersistentVolume、PersistentVolumeClaim、StorageClass把它们联合起来就可以为Pod挂载一块“虚拟盘”让Pod在其中任意读写数据。

不过当时我们使用的是HostPath存储卷只能在本机使用而Kubernetes里的Pod经常会在集群里“漂移”所以这种方式不是特别实用。

要想让存储卷真正能被Pod任意挂载我们需要变更存储的方式不能限定在本地磁盘而是要改成网络存储这样Pod无论在哪里运行只要知道IP地址或者域名就可以通过网络通信访问存储设备。

网络存储是一个非常热门的应用领域有很多知名的产品比如AWS、Azure、CephKubernetes还专门定义了CSIContainer Storage Interface规范不过这些存储类型的安装、使用都比较复杂在我们的实验环境里部署难度比较高。

所以今天的这次课里我选择了相对来说比较简单的NFS系统Network File System以它为例讲解如何在Kubernetes里使用网络存储以及静态存储卷和动态存储卷的概念。

如何安装NFS服务器

作为一个经典的网络存储系统NFS有着近40年的发展历史基本上已经成为了各种UNIX系统的标准配置Linux自然也提供对它的支持。

NFS采用的是Client/Server架构需要选定一台主机作为Server安装NFS服务端其他要使用存储的主机作为Client安装NFS客户端工具。

所以接下来我们在自己的Kubernetes集群里再增添一台名字叫Storage的服务器在上面安装NFS实现网络存储、共享网盘的功能。不过这台Storage也只是一个逻辑概念我们在实际安装部署的时候完全可以把它合并到集群里的某台主机里比如这里我就复用了[第17讲]里的Console。

新的网络架构如下图所示:

在Ubuntu系统里安装NFS服务端很容易使用apt即可

sudo apt -y install nfs-kernel-server

安装好之后你需要给NFS指定一个存储位置也就是网络共享目录。一般来说应该建立一个专门的 /data 目录,这里为了简单起见,我就使用了临时目录 /tmp/nfs

mkdir -p /tmp/nfs

接下来你需要配置NFS访问共享目录修改 /etc/exports指定目录名、允许访问的网段还有权限等参数。这些规则比较琐碎和我们的Kubernetes课程关联不大我就不详细解释了你只要把下面这行加上就行注意目录名和IP地址要改成和自己的环境一致

/tmp/nfs 192.168.10.0/24(rw,sync,no_subtree_check,no_root_squash,insecure)

改好之后,需要用 exportfs -ra 通知NFS让配置生效再用 exportfs -v 验证效果:

sudo exportfs -ra sudo exportfs -v

现在,你就可以使用 systemctl 来启动NFS服务器了

sudo systemctl start nfs-server sudo systemctl enable nfs-server sudo systemctl status nfs-server

你还可以使用命令 showmount 来检查NFS的网络挂载情况

showmount -e 127.0.0.1

如何安装NFS客户端

有了NFS服务器之后为了让Kubernetes集群能够访问NFS存储服务我们还需要在每个节点上都安装NFS客户端。

这项工作只需要一条apt命令不需要额外的配置

sudo apt -y install nfs-common

同样,在节点上可以用 showmount 检查NFS能否正常挂载注意IP地址要写成NFS服务器的地址我在这里就是“192.168.10.208”:

现在让我们尝试手动挂载一下NFS网络存储先创建一个目录 /tmp/test 作为挂载点:

mkdir -p /tmp/test

然后用命令 mount 把NFS服务器的共享目录挂载到刚才创建的本地目录上

sudo mount -t nfs 192.168.10.208:/tmp/nfs /tmp/test

最后测试一下,我们在 /tmp/test 里随便创建一个文件,比如 x.yml

touch /tmp/test/x.yml

再回到NFS服务器检查共享目录 /tmp/nfs应该会看到也出现了一个同样的文件 x.yml这就说明NFS安装成功了。之后集群里的任意节点只要通过NFS客户端就能把数据写入NFS服务器实现网络存储。

如何使用NFS存储卷

现在我们已经为Kubernetes配置好了NFS存储系统就可以使用它来创建新的PV存储对象了。

先来手工分配一个存储卷,需要指定 storageClassName 是 nfs而 accessModes 可以设置成 ReadWriteMany这是由NFS的特性决定的它支持多个节点同时访问一个共享目录。

因为这个存储卷是NFS系统所以我们还需要在YAML里添加 nfs 字段指定NFS服务器的IP地址和共享目录名。

这里我在NFS服务器的 /tmp/nfs 目录里又创建了一个新的目录 1g-pv表示分配了1GB的可用存储空间相应的PV里的 capacity 也要设置成同样的数值,也就是 1Gi。

把这些字段都整理好后我们就得到了一个使用NFS网络存储的YAML描述文件

apiVersion: v1 kind: PersistentVolume metadata: name: nfs-1g-pv

spec: storageClassName: nfs accessModes: - ReadWriteMany capacity: storage: 1Gi

nfs: path: /tmp/nfs/1g-pv server: 192.168.10.208

现在就可以用命令 kubectl apply 来创建PV对象再用 kubectl get pv 查看它的状态:

kubectl apply -f nfs-static-pv.yml kubectl get pv

再次提醒你注意spec.nfs 里的IP地址一定要正确路径一定要存在事先创建好否则Kubernetes按照PV的描述会无法挂载NFS共享目录PV就会处于“pending”状态无法使用。

有了PV我们就可以定义申请存储的PVC对象了它的内容和PV差不多但不涉及NFS存储的细节只需要用 resources.request 来表示希望要有多大的容量这里我写成1GB和PV的容量相同

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-static-pvc

spec: storageClassName: nfs accessModes: - ReadWriteMany

resources: requests: storage: 1Gi

创建PVC对象之后Kubernetes就会根据PVC的描述找到最合适的PV把它们“绑定”在一起也就是存储分配成功

我们再创建一个Pod把PVC挂载成它的一个volume具体的做法和[上节课]是一样的,用 persistentVolumeClaim 指定PVC的名字就可以了

apiVersion: v1 kind: Pod metadata: name: nfs-static-pod

spec: volumes:

  • name: nfs-pvc-vol persistentVolumeClaim: claimName: nfs-static-pvc

containers: - name: nfs-pvc-test image: nginx:alpine ports: - containerPort: 80

  volumeMounts:
    - name: nfs-pvc-vol
      mountPath: /tmp

Pod、PVC、PV和NFS存储的关系可以用下图来形象地表示你可以对比一下HostPath PV的用法看看有什么不同

因为我们在PV/PVC里指定了 storageClassName 是 nfs节点上也安装了NFS客户端所以Kubernetes就会自动执行NFS挂载动作把NFS的共享目录 /tmp/nfs/1g-pv 挂载到Pod里的 /tmp完全不需要我们去手动管理。

最后还是测试一下,用 kubectl apply 创建Pod之后我们用 kubectl exec 进入Pod再试着操作NFS共享目录

退出Pod再看一下NFS服务器的 /tmp/nfs/1g-pv 目录你就会发现Pod里创建的文件确实写入了共享目录

而且更好的是因为NFS是一个网络服务不会受Pod调度位置的影响所以只要网络通畅这个PV对象就会一直可用数据也就实现了真正的持久化存储。

如何部署NFS Provisoner

现在有了NFS这样的网络存储系统你是不是认为Kubernetes里的数据持久化问题就已经解决了呢

对于这个问题,我觉得可以套用一句现在的流行语:“解决了,但没有完全解决。”

说它“解决了”是因为网络存储系统确实能够让集群里的Pod任意访问数据在Pod销毁后仍然存在新创建的Pod可以再次挂载然后读取之前写入的数据整个过程完全是自动化的。

说它“没有完全解决”是因为PV还是需要人工管理必须要由系统管理员手动维护各种存储设备再根据开发需求逐个创建PV而且PV的大小也很难精确控制容易出现空间不足或者空间浪费的情况。

在我们的这个实验环境里只有很少的PV需求管理员可以很快分配PV存储卷但是在一个大集群里每天可能会有几百几千个应用需要PV存储如果仍然用人力来管理分配存储管理员很可能会忙得焦头烂额导致分配存储的工作大量积压。

那么能不能让创建PV的工作也实现自动化呢或者说让计算机来代替人类来分配存储卷呢

这个在Kubernetes里就是“动态存储卷”的概念它可以用StorageClass绑定一个Provisioner对象而这个Provisioner就是一个能够自动管理存储、创建PV的应用代替了原来系统管理员的手工劳动。

有了“动态存储卷”的概念前面我们讲的手工创建的PV就可以称为“静态存储卷”。

目前Kubernetes里每类存储设备都有相应的Provisioner对象对于NFS来说它的Provisioner就是“NFS subdir external provisioner”你可以在GitHub上找到这个项目https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

NFS Provisioner也是以Pod的形式运行在Kubernetes里的在GitHub的 deploy 目录里是部署它所需的YAML文件一共有三个分别是rbac.yaml、class.yaml和deployment.yaml。

不过这三个文件只是示例,想在我们的集群里真正运行起来还要修改其中的两个文件。

第一个要修改的是rbac.yaml它使用的是默认的 default 名字空间,应该把它改成其他的名字空间,避免与普通应用混在一起,你可以用“查找替换”的方式把它统一改成 kube-system。

第二个要修改的是deployment.yaml它要修改的地方比较多。首先要把名字空间改成和rbac.yaml一样比如是 kube-system然后重点要修改 volumes 和 env 里的IP地址和共享目录名必须和集群里的NFS服务器配置一样。

按照我们当前的环境设置就应该把IP地址改成 192.168.10.208,目录名改成 /tmp/nfs

spec: template: spec: serviceAccountName: nfs-client-provisioner containers: ... env: - name: PROVISIONER_NAME value: k8s-sigs.io/nfs-subdir-external-provisioner - name: NFS_SERVER value: 192.168.10.208 #改IP地址 - name: NFS_PATH value: /tmp/nfs #改共享目录名 volumes: - name: nfs-client-root nfs: server: 192.168.10.208 #改IP地址 Path: /tmp/nfs #改共享目录名

还有一件麻烦事deployment.yaml的镜像仓库用的是gcr.io拉取很困难而国内的镜像网站上偏偏还没有它为了让实验能够顺利进行我不得不“曲线救国”把它的镜像转存到了Docker Hub上。

所以你还需要把镜像的名字由原来的“k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2”改成“chronolaw/nfs-subdir-external-provisioner:v4.0.2”,其实也就是变动一下镜像的用户名而已。

把这两个YAML修改好之后我们就可以在Kubernetes里创建NFS Provisioner了

kubectl apply -f rbac.yaml kubectl apply -f class.yaml kubectl apply -f deployment.yaml

使用命令 kubectl get再加上名字空间限定 -n kube-system就可以看到NFS Provisioner在Kubernetes里运行起来了。

如何使用NFS动态存储卷

比起静态存储卷动态存储卷的用法简单了很多。因为有了Provisioner我们就不再需要手工定义PV对象了只需要在PVC里指定StorageClass对象它再关联到Provisioner。

我们来看一下NFS默认的StorageClass定义

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client

provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: archiveOnDelete: "false"

YAML里的关键字段是 provisioner它指定了应该使用哪个Provisioner。另一个字段 parameters 是调节Provisioner运行的参数需要参考文档来确定具体值在这里的 archiveOnDelete: "false" 就是自动回收存储空间。

理解了StorageClass的YAML之后你也可以不使用默认的StorageClass而是根据自己的需求任意定制具有不同存储特性的StorageClass比如添加字段 onDelete: "retain" 暂时保留分配的存储,之后再手动删除:

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client-retained

provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: onDelete: "retain"

接下来我们定义一个PVC向系统申请10MB的存储空间使用的StorageClass是默认的 nfs-client

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-dyn-10m-pvc

spec: storageClassName: nfs-client accessModes: - ReadWriteMany

resources: requests: storage: 10Mi

写好了PVC我们还是在Pod里用 volumes 和 volumeMounts 挂载然后Kubernetes就会自动找到NFS Provisioner在NFS的共享目录上创建出合适的PV对象

apiVersion: v1 kind: Pod metadata: name: nfs-dyn-pod

spec: volumes:

  • name: nfs-dyn-10m-vol persistentVolumeClaim: claimName: nfs-dyn-10m-pvc

containers: - name: nfs-dyn-test image: nginx:alpine ports: - containerPort: 80

  volumeMounts:
    - name: nfs-dyn-10m-vol
      mountPath: /tmp

使用 kubectl apply 创建好PVC和Pod让我们来查看一下集群里的PV状态

从截图你可以看到虽然我们没有直接定义PV对象但由于有NFS Provisioner它就自动创建一个PV大小刚好是在PVC里申请的10MB。

如果你这个时候再去NFS服务器上查看共享目录也会发现多出了一个目录名字与这个自动创建的PV一样但加上了名字空间和PVC的前缀

我还是把Pod、PVC、StorageClass和Provisioner的关系画成了一张图你可以清楚地看出来这些对象的关联关系还有Pod是如何最终找到存储设备的

小结

好了今天的这节课里我们继续学习PV/PVC引入了网络存储系统以NFS为例研究了静态存储卷和动态存储卷的用法其中的核心对象是StorageClass和Provisioner。

我再小结一下今天的要点:

在Kubernetes集群里网络存储系统更适合数据持久化NFS是最容易使用的一种网络存储系统要事先安装好服务端和客户端。 可以编写PV手工定义NFS静态存储卷要指定NFS服务器的IP地址和共享目录名。 使用NFS动态存储卷必须要部署相应的Provisioner在YAML里正确配置NFS服务器。 动态存储卷不需要手工定义PV而是要定义StorageClass由关联的Provisioner自动创建PV完成绑定。

课下作业

最后是课下作业时间,给你留两个思考题:

动态存储卷相比静态存储卷有什么好处?有没有缺点? StorageClass在动态存储卷的分配过程中起到了什么作用

期待你的思考。如果觉得有收获,也欢迎你分享给朋友一起讨论。我们下节课再见。