Kubernetes数据存储
数据存储概述
简单来说,存储卷是定义在Pod
资源之上、可被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力取决于存储卷自身是否支持持久机制,Pod
、容器与存储卷的关系图如下。
什么是数据卷
Pod
本身具有生命周期,这就带了一系列的问题,
- 当一个容器损坏之后,
kubelet
会重启这个容器,但是文件会丢失-这个容器会是一个全新的状态; - 当很多容器在同一
Pod
中运行的时候,很多时候需要数据文件的共享。
Docker
支持配置容器使用存储卷将数据持久存储于容器自身文件系统之外的存储空间之中,它们可以是节点文件系统或网络文件系统之上的存储空间。相应的,kubernetes
也支持类似的存储卷功能,不过,其存储卷是与Pod
资源绑定而非容器。
数据持久化的目的
数据销毁
通常情况下,容器运行起来后,写入到其容器内部的文件是暂时性的。
当容器崩溃后,k8s将这个容器kill掉,然后生成一个新的容器,此时,新运行的容器将没有原来容器内的文件,因为容器是从镜像重新创建的。 (容器一旦重启或者被删除了,里面存放的数据是会被销毁的)
数据共享
数据共享:同一个pod中运行的容器之间,经常会存在共享文件或目录的需求,比如采集容器和主容器
生命周期
k8s里面的volume数据卷存在明确的生命周期,并且volume的生命周期比同一容器组pod中的任意容器的生命周期都要长,不管容器本身重启了多少次,数据都被保留下来,当然,pod如果不存在了,数据卷自然就退出了。
注意:可以定义pod所使用的数据卷类型不同,数据可能随着数据卷的退出而删除,也可能被真正的持久化,即使后期重新使用容器时仍然可以使用该数据卷,下面有例子。
存储卷类型
Kubernetes
支持非常丰富的存储卷类型,包括本地存储(节点)和网络存储系统中的诸多存储机制,还支持Secret
和ConfigMap
这样的特殊存储资源。
例如,关联节点本地的存储目录与关联GlusterFS
存储系统所需要的配置参数差异巨大,因此指定存储卷类型时也就限定了其关联到的后端存储设备。通过命令# kubectl explain pod.spec
可以查看当前kubernetes
版本支持的存储卷类型。常用类型如下:
非持久性存储
- emptyDir
- hostPath
网络连接性存储
SAN:iscsi
NFS:nfs、cfs
分布式存储
- glusterfs、cephfs、rbd
云端存储
- awsElasticBlockStore、azureDisk、gitRepo
临时存储卷(emptyDir)
emptyDir
存储卷是Pod
对象生命周期中的一个临时目录,类似于Docker
上的“docker 挂载卷”
,在Pod
对象启动时即被创建,而在Pod
对象被移除时会被一并删除(永久删除)。
当pod的存储方案设定为emptydir的时候,pod启动时,就会在pod所在节点的磁盘空间开辟出一块空卷,最开始里面是什么都没有的,pod启动后容器产生的数据会存放到那个空卷中,空卷变成了一个临时卷供pod内的容器读取和写入数据,一旦pod容器消失,节点上开辟出的这个临时卷就会随着pod的销毁而销毁
注意:一个容器崩溃了不会导致数据的丢失,因为容器的崩溃并不移除Pod
。
使用场景
一般来说emptydir的用途都是用来充当临时存储空间,例如一些不需要数据持久化的微服务,我们都可以用emptydir来当做微服务pod的存储方案
- 充当临时存储空间,当pod内容器产生的数据不需要做持久化存储的时候用emptydir
- 设制检查点以从崩溃事件中恢复未执行完毕的长计算
使用示例
案例说明
这里定义了一个deploy
资源对象(vol-emptydir-deploy
),在其内部定义了两个容器,其中一个容器是辅助容器sidecar
,每隔10秒生成一行信息追加到index.html
文件中;
另一个是nginx
容器,将存储卷挂载到站点家目录。然后访问nginx
的html
页面验证两个容器之间挂载的emptyDir
实现共享。
创建资源清单
1 | vi vol-emptydir-deploy.yml |
1 | apiVersion: apps/v1 |
应用配置
我们应用配置
1 | kubectl apply -f vol-emptydir-deploy.yml |
这样我们就创建出来了一个临时存储
访问测试
我们通过CURL命令进行访问测试
1 | curl 10.244.1.125 |
因为我们每隔一段时间就写入行数据,所以我们可以看到两个容器共享的数据,我们发现
nginx
可以共享来自sidecar
的数据
存储卷测试
共享测试
下面我们测试以下存储卷在多个容器之间的共享问题
登录sidecar
登录
sidecar
,并查看目录文件
1 | kubectl exec vol-emptydir-deploy-86cd768757-x5mtw -c sidecar -it -- /bin/sh |
这样我们发现终端之间是共享数据的
修改数据
为了真实的验证数据共享,我们对
sidecar
容器中的数据进行修改
1 | echo "hello world" > /html/hello |
我们通过命令将
hello world
写入到hello文件中
登录nginx
再打开一个窗口登录
nginx
,查看刚才写入的hello文件是否存在
1 | kubectl exec vol-emptydir-deploy-86cd768757-5mtzx -c nginx -it -- /bin/sh |
删除文件
在nginx容器中删除
hello
文件
1 | rm -rf /usr/share/nginx/html/hello |
我们发现
hello
文件已经被删除了
查看sidecar文件
我们再次登录
sidecar
容器查看hello
文件是否被删除
1 | ls /html |
我们发现文件已经被删除了
持久性测试
登录sidecar
登录
sidecar
并在html中写入一段文件
1 | kubectl exec vol-emptydir-deploy-86cd768757-x5mtw -c sidecar -it -- /bin/sh |
这样我们就把一段文字写入了存储卷中
访问测试
我们访问Nginx测试写入的这一段文字是否存在
1 | curl 10.244.1.125 |
我们发现这一段文字是存在的
删除容器
我们删除Nginx容器后测试是否能够持久化
1 | docker ps |grep nginx |
我们登录对应的节点删除对应的容器,我们发现删除容器后马上就启动了一个新的容器
我们再来验证以下持久化的文字是否存在
1 | curl 10.244.1.125 |
我们发现持久化的文字还存在,说明容器重启不影响临时存储的村九华
删除Pod
手动删除容器模拟容器销毁,用于是pod是被控制器管理的,删除后会被重建新的pod
1 | kubectl delete pod vol-emptydir-deploy-86cd768757-x5mtw |
我们删除POD后Deployment会帮我们重建这个POD,然后我们再来验证下存储的数据是否还存在
我们发现销毁pod后新建的pod转移到node2节点了,并且pod名称也改变了
访问测试
我们再来访问这个POD看下我们写入的文字是否还存在
1 | curl 10.244.2.140 |
这时候在看我们之前写入的文字不见了,说明POD销毁临时存储也会跟着销毁
节点存储卷(hostPath)
hostPath
类型的存储卷是指将工作节点上的某文件系统的目录或文件挂载于Pod
中的一种存储卷,独立于Pod
资源的生命周期,具有持久性,在Pod
删除时,数据不会丢失。
hostPath类型则是映射node文件系统中的文件或者目录到pod里。在使用hostPath类型的存储卷时,也可以设置type字段,支持的类型有文件、目录、File、Socket、CharDevice和BlockDevice。
其实这个功能就相当于docker中的-v 目录映射,只不过在k8s中的时候,pod会漂移,当pod漂移到其他node节点的时候,pod不会跨节点的去读取目录。所以说hostpath只能算一种半持久化的存储方式
使用场景
- 当运行的容器需要访问Docker内部结构时,如使用hostPath映射/var/lib/docker到容器;
- 当在容器中运行cAdvisor时,可以使用hostPath映射/dev/cgroups到容器中;
使用示例
案例说明
这里定义了一个
deploy
资源对象(vol-hostpath-deploy
),在其内部定义了一个容器,将nginx的html文件映射到外部,我们外面给html写入文件,访问nginx来验证是否能够访问。
创建资源清单
1 | vi vol-hostpath-deploy.yml |
1 | apiVersion: apps/v1 |
应用配置
1 | kubectl apply -f vol-hostpath-deploy.yml |
这样我们就创建出来了一个节点存储,我们发现当前的POD处于node01节点
写入文件
我们在node01节点中写入文件,验证是否能够被容器访问
1 | 登录node01节点,进入挂载目录 |
我们发现k8s自动帮我们创建了一个挂载目录
访问测试
我们来访问以下看看刚才写入的文件是否能够被nginx访问到
1 | curl 10.244.1.126 |
我们发现已经能够访问到数据了,说明宿主机和POD之间是互通的
存储卷测试
共享测试
下面我们测试以下存储卷在多个容器之间的共享问题
登录nginx
我们登录Nginx并通过容器写入一些文件
1 | kubectl exec vol-emptydir-deploy-86cd768757-5mtzx -c nginx -it -- /bin/sh |
这样我们就将一个文件写入了容器的挂载目录中
登录宿主机查看
因为使用的是hostpath,我们登录pod所在的节点的宿主机目录查看文件是否存在
1 | 登录node01节点执行以下的命令 |
发现写入的文件是存在的
持久性测试
删除Pod
手动删除容器模拟容器销毁,用于是pod是被控制器管理的,删除后会被重建新的pod
1 | kubectl get pod -o wide |
我们发现删除POD后,新的POD调度到了node02这个节点上了
访问测试
我们知道hostpath是在宿主机创建文件的,现在我们发现pod漂移到了node2节点,我们尝试访问,并登录容器查看
1 | curl 10.244.2.141 |
发现文件不存在符合我们的预期,这个因为node02节点没有数据
再次删除容器
因为上一次删除漂移到了node2,我们再次删除让其漂移到node1
1 | kubectl get pod -o wide |
我们发现POD已经被调度到了node01节点上了
访问测试
我们再次访问测试,检查是否可以访问到持久化的数据
1 | curl 10.244.1.128 |
我们发现已经可以访问到数据了
小结
可以发现容器被删除后,新建的pod也可以看到我们映射的目录,我们写入的文件是存在的
这有个缺点就是不能够跨容器去读取数据,如果删除后的pod被调度到其他节点的话,原来的数据也就没有了,如果能不受节点的影响,并且挂载的数据不会随生命周期的结束而结束,我们应该怎么解决呢?就是我们后面讲到的持久化存储了
共享存储卷(NFS )
nfs
存储卷用于将事先存在的NFS
服务器上导出的存储空间挂载到Pod
中供容器使用。
与emptyDir
不同的是,当pod
资源删除时emptyDir
也会被删除,而NFS
在Pod
对象删除时仅是被卸载而非删除,这就意味NFS
能够允许我们提前对数据进行处理,而且这些数据可以在Pod
之间相互传递,并且NFS
可以同时被多个Pod
挂载并进行读写。
NFS 简介
NFS 是Network File System的缩写,即网络文件系统。一种使用于分散式文件系统的协定,由Sun公司开发,于1984年向外公布。
功能是通过网络让不同的机器、不同的操作系统能够彼此分享个别的数据,让应用程序在客户端通过网络访问位于服务器磁盘中的数据,是在类Unix系统间实现磁盘文件共享的一种方法。
NFS在文件传送或信息传送过程中依赖于RPC协议,RPC远程过程调用 (Remote Procedure Call) 是能使客户端执行其他系统中程序的一种机制,NFS本身是没有提供信息传输的协议和功能的。
NFS应用场景,常用于高可用文件共享,多台服务器共享同样的数据,可扩展性比较差,本身高可用方案不完善,取而代之的数据量比较大的可以采用MFS、TFS、HDFS、GFS等等分布式文件系统。
NFS 安装
NFS分为客户端和服务端,下面我们进行NFS安装
安装计划
下面是我们安装NFS的服务器计划
安装软件 | 服务器IP | 共享目录 |
---|---|---|
NFS服务器端 | 192.168.245.151(master) | /tmp/k8s/data/volumn/ |
NFS客户端 | 192.168.245.152(node01) | /tmp/k8s/data/volumn/ |
NFS客户端 | 192.168.245.153(node02) | /tmp/k8s/data/volumn/ |
服务端安装
我们登录到mastet节点完成以下安装几乎是
创建共享目录
我们先建立需要共享的目录
1 | mkdir -p /tmp/k8s/data/volumn/ |
安装NFS服务端
执行下面的命令安装NFS的服务端工具
1 | yum install nfs-utils rpcbind -y |
配置NFS
NFS安装完毕,需要创建共享目录,共享目录在vi /etc/exports文件里面配置,配置方式如下
1 | vi /etc/exports |
编辑exports配置文件将需要NFS同步的目录配置到配置文件中
1 | /tmp/k8s/data/volumn/ *(rw,no_root_squash,no_all_squash,sync) |
NFS配置参数
可配置参数如下
- /tmp/k8s/data/volumn/: 表示需要共享的目录
- IP:表示允许哪个客户端访问
IP后括号里的设置表示对该共享文件的权限
ro | 只读访问 |
---|---|
rw | 读写访问 |
sync | 所有数据在请求时写入共享 |
all_squash | 共享文件的UID和GID映射匿名用户anonymous,适合公用目录。 |
no_all_squash | 保留共享文件的UID和GID(默认) |
root_squash | root用户的所有请求映射成如anonymous用户一样的权限(默认) |
no_root_squash | root用户具有根目录的完全管理访问权限 |
生效配置
安装完成后还不能马上生效,我们需要将配置文件生效
1 | exportfs -r |
重启NFS服务
最后我们重启以下NFS的服务器
1 | service rpcbind restart;service nfs restart |
客户端安装
这个安装需要在node01和node02上面进行安装
创建共享目录
我们先建立需要共享的目录
1 | mkdir -p /tmp/k8s/data/volumn/ |
安装客户端
我们安装客户端需要的工具
1 | yum -y install nfs-utils |
查看共享目录
安装客户端后就可以尝试连接服务器端,检查需要共享的目录来检查NFS是否安装正常
1 | showmount -e 192.168.245.151 |
挂载目录
验证共享目录没有问题后,我们就需要将我们客户端的共享目录挂载到服务端上,进行数据的同步
Linux客户端,如何想使用这个NFS文件系统,需要在客户端挂载,挂载命令为(为了提高NFS的稳定性,使用TCP协议挂载,NFS默认用UDP协议):
1 | mount -t nfs 192.168.245.151:/tmp/k8s/data/volumn /tmp/k8s/data/volumn -o proto=tcp -o nolock |
开启自动挂载
因为有时候服务器重启,导致同步服务未开启,数据未同步
1 | vi /etc/fstab |
我们一般开启自动挂载,在/etc/fstab下加上如下配置
1 | 192.168.245.151:/tmp/k8s/data/volumn /tmp/k8s/data/volumn nfs defaults 0 0 |
验证NFS
安装完成后,我们来验证下NFS工作是否正常
写入数据
在任意一个参与同步的服务器写入文件
1 | cd /tmp/k8s/data/volumn |
读取数据
在任意一个参与同步的服务器检查数据是否存在
1 | cd /tmp/k8s/data/volumn |
清理数据
在任意一个参与同步的服务器删除目录中的数据
1 | rm -rf /tmp/k8s/data/volumn/* |
使用案例
案例说明
这里定义了一个
deploy
资源对象(vol-nfs-deploy.yml
),在其内部定义了一个容器,将nginx的html文件映射到外部,我们外面给html写入文件,访问nginx来验证是否能够访问。
创建资源清单
1 | vi vol-nfs-deploy.yml |
我们发现和hostpath的配置清单一样,这是因为我们使用的就是hostpath的存储卷,只是挂载目录通过NFS实现了数据同步,完成了数据共享
1 | apiVersion: apps/v1 |
应用配置
1 | kubectl apply -f vol-nfs-deploy.yml |
这样我们就创建出来了一个NFS存储,我们发现当前的POD处于node01节点
写入文件
我们在node01节点中写入文件,验证是否能够被容器访问
1 | 登录node01节点,进入挂载目录 |
我们发现k8s自动帮我们创建了一个挂载目录
访问测试
我们来访问以下看看刚才写入的文件是否能够被nginx访问到
1 | curl 10.244.1.129 |
我们发现已经能够访问到数据了,说明宿主机和POD之间是互通的
存储卷测试
共享测试
下面我们测试以下存储卷在多个容器之间的共享问题
登录nginx
我们登录Nginx并通过容器写入一些文件
1 | kubectl exec vol-nfs-deploy-684496d8cc-xhwjb -c nginx -it -- /bin/sh |
这样我们就将一个文件写入了容器的挂载目录中
登录宿主机查看
因为使用的是hostpath,我们登录pod所在的节点的宿主机目录查看文件是否存在
1 | 登录node01节点执行以下的命令 |
发现写入的文件是存在的
持久性测试
删除Pod
手动删除容器模拟容器销毁,用于是pod是被控制器管理的,删除后会被重建新的pod
1 | kubectl get pod -o wide |
我们发现删除POD后,新的POD调度到了node02这个节点上了
访问测试
我们知道hostpath是在宿主机创建文件的,现在我们发现pod漂移到了node2节点,我们尝试访问,并登录容器查看
1 | curl 10.244.2.142 |
发现文件存在符合我们的预期,这个因为通过NFS已经将数据从node01同步到了node02节点
通过上面测试可以看出,此前创建的index.html
及其数据在Pod
资源重建后依然存在,且不论pod
资源调度到哪个节点。
这表明在删除Pod
资源时,其关联的外部存储卷并不会被一同删除,如果需要删除此类的数据,需要用户通过存储系统的管理接口手动进行。
持久化卷(PV/PVC)
概述
在 Kubernetes 中,存储资源和计算资源(CPU、Memory)同样重要,Kubernetes 为了能让管理员方便管理集群中的存储资源,同时也为了让使用者使用存储更加方便,所以屏蔽了底层存储的实现细节,将存储抽象出两个 API 资源 PersistentVolume
和 PersistentVolumeClaim
对象来对存储进行管理。
PersistentVolume(持久化卷)
PersistentVolume
简称PV
, 是对底层共享存储的一种抽象
将共享存储定义为一种资源,它属于集群级别资源,不属于任何 Namespace
,用户使用 PV 需要通过 PVC 申请,PV 是由管理员进行创建和配置,它和具体的底层的共享存储技术的实现方式有关,比如说 Ceph、GlusterFS、NFS 等,都是通过插件机制完成与共享存储的对接,且根据不同的存储 PV 可配置参数也是不相同。
PersistentVolumeClaim(持久化卷声明)
PersistentVolumeClaim
简称PVC
,是用户存储的一种声明
类似于对存储资源的申请,它属于一个 Namespace
中的资源,可用于向 PV
申请存储资源。PVC
和 Pod
比较类似,Pod
消耗的是 Node
节点资源,而 PVC
消耗的是 PV
存储资源,Pod
可以请求 CPU 和 Memory,而 PVC
可以请求特定的存储空间和访问模式。
StorageClass
上面两种资源 PV
和 PVC
的存在很好的解决了存储管理的问题,不过这些存储每次都需要管理员手动创建和管理,如果一个集群中有很多应用,并且每个应用都要挂载很多存储,那么就需要创建很多 PV
和 PVC
与应用关联,为了解决这个问题 Kubernetes 在 1.4 版本中引入了 StorageClass
对象。
当我们创建 PVC
时指定对应的 StorageClass
就能和 StorageClass
关联,StorageClass
会交由与他关联 Provisioner 存储插件来创建与管理存储,它能帮你创建对应的 PV
和在远程存储上创建对应的文件夹,并且还能根据设定的参数,删除与保留数据。所以管理员只要在 StorageClass
中配置好对应的参数就能方便的管理集群中的存储资源。
PV详解
PersistentVolume
(PV
)是集群中已由管理员配置的一段网络存储。
集群中的资源就像一个节点是一个集群资源,PV
是诸如卷之类的卷插件,但是具有独立于使用PV
的任何单个Pod
的生命周期。该API
对象捕获存储的实现细节,即NFS
,ISCSI
或云提供商特定的存储系统。
PV支持存储的类型
- RBD:Ceph 块存储。
- FC:光纤存储设备。
- NFS:网络问卷存储卷。
- iSCSI:iSCSI 存储设备。
- CephFS:开源共享存储系统。
- Flocker:一种开源共享存储系统。
- Glusterfs:一种开源共享存储系统。
- Flexvolume:一种插件式的存储机制。
- HostPath:宿主机目录,仅能用于单机。
- AzureFile:Azure 公有云提供的 File。
- AzureDisk:Azure 公有云提供的 Disk。
- ScaleIO Volumes:DellEMC 的存储设备。
- StorageOS:StorageOS 提供的存储服务。
- VsphereVolume:VMWare 提供的存储系统。
- Quobyte Volumes:Quobyte 提供的存储服务。
- Portworx Volumes:Portworx 提供的存储服务。
- GCEPersistentDisk:GCE 公有云提供的 PersistentDisk。
- AWSElasticBlockStore:AWS 公有云提供的 ElasticBlockStore。
PV的生命周期
- Available: 可用状态,尚未被 PVC 绑定。
- Bound: 绑定状态,已经与某个 PVC 绑定。
- Failed: 当删除 PVC 清理资源,自动回收卷时失败,所以处于故障状态。
- Released: 与之绑定的 PVC 已经被删除,但资源尚未被集群回收。
配置案例
创建资源清单
1 | vi pv.yml |
这里我们创建了两个PV,一个采用NFS存储,一个采用hostpath模式
1 | apiVersion: v1 |
应用配置
我们执行命令应用配置
1 | kubectl apply -f pv.yml |
我们现在已经创建了两个PV
关键配置参数
PV作为存储资源,主要包括存储能力、访问模式、存储类型、回收策略、后端存储类型等关键信息的设置
存储能力(Capacity)
PV 可以通过配置
capacity
中的storage
参数,对 PV 挂多大存储空间进行设置
一般来说,一个 PV 对象都要指定一个存储能力,通过 PV 的 capacity
属性来设置的,目前只支持存储空间的设置,就是我们这里的 storage=10Gi
,不过未来可能会加入 IOPS
、吞吐量等指标的配置。
1 | capacity: |
注意:这里的存储能力大小是一定就给你底层划分这么大的空间吗,不一定,和你底层使用的存储有关,例如是ceph 块存储的话,那么它底层就可能给你划分一个2g大小的空间;但是NFS这种文件系统,它是限制不了的;
存储卷模式(volumeMode)
PV 可以通过配置
volumeMode
参数,对存储卷类型进行设置,可选项包括:
- Filesystem: 文件系统,默认是此选项。
- Block: 块设备
目前 Block 模式只有 AWSElasticBlockStore、AzureDisk、FC、GCEPersistentDisk、iSCSI、LocalVolume、RBD、VsphereVolume 等支持)
1 | volumeMode: Filesystem |
访问模式(accessModes)
PV 可以通过配置
accessModes
参数,设置访问模式来限制应用对资源的访问权限,有以下机制访问模式:
- ReadWriteOnce(RWO): 读写权限,只能被单个节点挂载。
- ReadOnlyMany(ROX): 只读权限,允许被多个节点挂载读。
- ReadWriteMany(RWX): 读写权限,允许被多个节点挂载。
1 | accessModes: |
这里还需要注意的一点:配置文件里面accessModes(访问模式)的声明,仅仅只是一个声明而已,其实起作用的关键还是要看底层存储,例如NFS这里只声明了为ReadWriteOnce,但其实也是文件系统它是支持all模式的
一些 PV 可能支持多种访问模式,但是在挂载的时候只能使用一种访问模式,多种访问模式是不会生效的。
块存储,一般只支持单个节点挂载,不支持多个节点共享; – 只能被一台服务器去挂载,不能被多台服务器挂载;因此适用于RWO、ROX;
不过不同的存储所支持的访问模式也不相同,具体如下:
Volume Plugin | ReadWriteOnce | ReadOnlyMany | ReadWriteMany |
---|---|---|---|
AWSElasticBlockStore | √ | - | - |
AzureFile | √ | √ | √ |
AzureDisk | √ | - | - |
CephFS | √ | √ | √ |
Cinder | √ | - | - |
FC | √ | √ | - |
FlexVolume | √ | √ | - |
Flocker | √ | - | - |
GCEPersistentDisk | √ | √ | - |
GlusteFS | √ | √ | √ |
HostPath | √ | - | - |
iSCSI | √ | √ | - |
PhotonPersistentDisk | √ | - | - |
Quobyte | √ | √ | √ |
NFS | √ | √ | √ |
RBD | √ | √ | - |
VsphereVolume | √ | - | - |
PortworxVolume | √ | - | √ |
ScaleIO | √ | √ | - |
StorageOS | √ | - | - |
挂载参数(mountOptions)
PV 可以根据不同的存储卷类型,设置不同的挂载参数,每种类型的存储卷可配置参数都不相同,如 NFS 存储,可以设置 NFS 挂载配置
下面例子只是 NFS 支持的部分参数,其它参考官网的配置项。
1 | mountOptions: |
存储类 (storageClassName)
PV 可以通过配置
storageClassName
参数指定一个存储类StorageClass
资源
具有特定 StorageClass
的 PV
只能与指定相同 StorageClass
的 PVC
进行绑定,没有设置 StorageClass
的 PV
也是同样只能与没有指定 StorageClass
的 PVC
绑定
1 | storageClassName: slow |
回收策略(persistentVolumeReclaimPolicy)
PV 可以通过配置
persistentVolumeReclaimPolicy
参数设置回收策略,可选项如下
- Retain(保留): 保留数据,需要由管理员手动清理。
- Recycle(回收): 删除数据,即删除目录下的所有文件,比如说执行
rm -rf /thevolume/*
命令,目前只有 NFS 和 HostPath 支持。 - Delete(删除): 删除存储资源,仅仅部分云存储系统支持,比如删除 AWS EBS 卷,目前只有 AWS EBS,GCE PD,Azure 磁盘和 Cinder 卷支持删除。
1 | persistentVolumeReclaimPolicy: Recycle |
PVC详解
PersistentVolumeClaim
(PVC
)是用户存储的请求。它类似于Pod
。Pod
消耗节点资源,PVC
消耗存储资源,Pod
可以请求特定级别的资源(CPU
和内存)
PVC作为用户对存储资源的需求申请,主要包括存储空间请求、访问模式、PV选择条件和存储类别等信息的设置
创建资源清单
1 | vi pvc.yml |
这样我们就创建出来了一个PVC
1 | apiVersion: v1 |
应用配置
现在我们应用配置,并观察绑定前后的状态变化
1 | kubectl apply -f pvc.yml |
我们现在已经将PVC绑定到了PV上面了
查看PV状态
我们现在看下PV的状态
1 | kubectl get pv -o wide |
我们发现当前的PV已经进入绑定的状态了
常用配置参数
筛选器(selector)
PVC 可以通过在
Selecter
中设置Laberl
标签,筛选出带有指定Label
的PV
进行绑定。
Selecter
中可以指定 matchLabels
或 matchExpressions
,如果两个字段都设定了就需要同时满足才能匹配
1 | selector: |
资源请求(resources)
PVC 设置目前只有
requests.storage
一个参数,用于指定申请存储空间的大小。
1 | resources: |
存储类(storageClass)
PVC 要想绑定带有特定
StorageClass
的PV
时,也必须设定storageClassName
参数,且名称也必须要和PV
中的storageClassName
保持一致。
如果要绑定的 PV
没有设置 storageClassName
则 PVC
中也不需要设置
当 PVC 中如果未指定 storageClassName
参数或者指定为空值,则还需要考虑 Kubernetes
中是否设置了默认的 StorageClass
:
- 未启用 DefaultStorageClass:等于 storageClassName 值为空。
- 启用 DefaultStorageClass:等于 storageClassName 值为默认的 StorageClass。
如果设置 storageClassName=””,则表示该 PVC 不指定 StorageClass。
1 | storageClassName: slow |
访问模式(accessModes)
PVC 中可设置的访问模式与 PV 种一样,用于限制应用对资源的访问权限。
存储卷模式(volumeMode)
PVC 中可设置的存储卷模式与 PV 种一样,分为
Filesystem
和Block
两种。
StorageClass详解
StorageClass作为对存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节,一方面减少了用户对存储资源细节的关注,另一方面减少了管理员手工管理PV的工作,由系统自动完成PV的创建和绑定,实现了动态的资源供应
配置案例
这里使用 NFS 存储,创建 StorageClass 示例
1 | apiVersion: storage.k8s.io/v1 |
常用配置参数
提供者(provisioner)
在创建
StorageClass
之前需要Kubernetes
集群中存在Provisioner
(存储分配提供者)应用,如 NFS 存储需要有NFS-Provisioner
(NFS 存储分配提供者)应用,如果集群中没有该应用,那么创建的StorageClass
只能作为标记,而不能提供创建PV
的作用。
1 | provisioner: nfs-client |
参数(parameters)
后端存储提供的参数,不同的 Provisioner 可与配置的参数也是不相同
1 | arameters: |
挂载参数(mountOptions)
在
StorageClass
中,可以根据不同的存储来指定不同的挂载参数,此参数会与StorageClass
绑定的Provisioner
创建PV
时,将此挂载参数与创建的PV
关联。
1 | mountOptions: |
设置默认的 StorageClass
可与在 Kubernetes 集群中设置一个默认的 StorageClass,这样当创建 PVC 时如果未指定 StorageClass 则会使用默认的 StorageClass。
1 | metadata: |
hostPath使用案例
一般
Deployment
中使用 PVC,大部分都是静态供给方式创建,即先创建 PV,再创建 PVC 与 PV 绑定,在设置应用于 PVC 关联。
创建资源清单
1 | vi pvc-hostpath.yml |
1 | apiVersion: apps/v1 |
应用配置
1 | kubectl apply -f pvc-hostpath.yml |
现在我们就创建了一个PVC的Deployment
写入文件
我们在node01节点检查PV目录是否创建,如果创建在目录中写入文件
1 | 登录node01节点,进入挂载目录 |
我们发现k8s自动帮我们创建了一个挂载目录
访问测试
我们来访问以下看看刚才写入的文件是否能够被nginx访问到
1 | curl 10.244.1.126 |
我们发现已经能够访问到数据了,说明宿主机和POD之间是互通的
共享测试
下面我们测试以下存储卷在多个容器之间的共享问题
登录nginx
我们登录Nginx并通过容器写入一些文件
1 | kubectl exec vol-emptydir-deploy-86cd768757-5mtzx -c nginx -it -- /bin/sh |
这样我们就将一个文件写入了容器的挂载目录中
登录宿主机查看
因为使用的是hostpath,我们登录pod所在的节点的宿主机目录查看文件是否存在
1 | 登录node01节点执行以下的命令 |
发现写入的文件是存在的
持久性测试
删除Pod
手动删除容器模拟容器销毁,用于是pod是被控制器管理的,删除后会被重建新的pod
1 | kubectl get pod -o wide |
我们发现删除POD后,新的POD调度到了node02这个节点上了
访问测试
我们知道hostpath是在宿主机创建文件的,现在我们发现pod漂移到了node2节点,我们尝试访问,并登录容器查看
1 | curl 10.244.2.143 |
发现文件不存在符合我们的预期,这个因为node02节点没有数据
再次删除容器
因为上一次删除漂移到了node2,我们再次删除让其漂移到node1
1 | kubectl get pod -o wide |
我们发现POD已经被调度到了node01节点上了
访问测试
我们再次访问测试,检查是否可以访问到持久化的数据
1 | curl 10.244.1.138 |
我们发现已经可以访问到数据了
NFS使用案例
创建资源清单
1 | vi pvc-nfs-deploy.yml |
配置基本上和上面一样,只是PVC我们采用NFS的方式,我们发现使用PVC后,PVC帮我们屏蔽了底层存储设备的不一致特性
1 | apiVersion: apps/v1 |
应用配置
1 | kubectl apply -f pvc-nfs-deploy |
这样我们就创建出来了一个NFS存储,我们发现当前的POD处于node01节点
写入文件
我们在node01节点中写入文件,验证是否能够被容器访问
1 | 登录node01节点,进入挂载目录 |
我们发现k8s自动帮我们创建了一个挂载目录
访问测试
我们来访问以下看看刚才写入的文件是否能够被nginx访问到
1 | curl 10.244.1.139 |
我们发现已经能够访问到数据了,说明宿主机和POD之间是互通的
共享测试
下面我们测试以下存储卷在多个容器之间的共享问题
登录nginx
我们登录Nginx并通过容器写入一些文件
1 | kubectl exec pvc-nfs-deploy-684496d8cc-xhwjb -c nginx -it -- /bin/sh |
这样我们就将一个文件写入了容器的挂载目录中
登录宿主机查看
因为使用的是hostpath,我们登录pod所在的节点的宿主机目录查看文件是否存在
1 | 登录node01节点执行以下的命令 |
发现写入的文件是存在的
持久性测试
删除Pod
手动删除容器模拟容器销毁,用于是pod是被控制器管理的,删除后会被重建新的pod
1 | kubectl get pod -o wide |
我们发现删除POD后,新的POD调度到了node02这个节点上了
访问测试
我们知道hostpath是在宿主机创建文件的,现在我们发现pod漂移到了node2节点,我们尝试访问,并登录容器查看
1 | curl 10.244.2.142 |
发现文件存在符合我们的预期,这个因为通过NFS已经将数据从node01同步到了node02节点
清理环境
使用完成环境后需要清空环境用于下一次测试
查看当前PV状态
我们可以看下当前的PV状态
1 | kubectl get pvc -o wide |
我们发现当前的PV以及PVC都处于绑定状态
删除Deployment
我们将Deployment删除
1 | kubectl delete deployment pvc-nfs-deploy |
这样我们就将Deployment删除了
我们再来检查以下PV和PVC的状态
1 | kubectl get pvc -o wide |
我们发现当前的PV以及PVC都还处于绑定状态,说明PV还处于使用状态,因为PVC连接着PV
删除PVC
最后我们将PVC删除掉
1 | kubectl delete pvc pvc1 pvc2 |
这样我们就将PVC全部删除掉了
检查PV状态
最后我们检查PV的的状态
1 | kubectl get pv -o wide |
我们发现当前的PV已经进入了可用状态
检查数据状态
我们最后在检查下NFS中的数据
1 | cd /tmp/k8s/data/volumn |
我们发现数据都已经被删除了,这是因为我们PV设置的回收策略是
Recycle
,PVC删除后自动删除PV的数据
生命周期转化(PV/PVC)
PV 是 Kubernetes 集群的存储资源,而 PVC 则是对存储资源的需求,创建 PVC 需要对 PV 发起使用申请,即和 PV 进行绑定
PV 和 PVC 是一一对应的关系,它们二者的交互遵循如下生命周期:
存储供给
存储供给(Provisioning)是指为 PVC 准备可用的 PV 的一种机制。Kubernetes 支持 PV 供给方式有
静态供给
和动态供给
两种:
- 静态供给: 指由集群管理员手动创建一定数量的 PV,创建 PV 时需要根据后端存储的不同,配置的参数也不同。
- 动态供给: 指不需要集群管理员手动创建 PV,将 PV 的创建工作交由 StorageClass 关联的 Provisioner 进行创建,会根据存储的不同自动配置相关的参数。创建完成 PV 后系统会自动将 PVC 与其绑定。
存储绑定
静态模式
在静态模式下,在用户定义好 PVC 后,Kubernetes 将根据 PVC 提出的“申请空间的大小”、“访问模式”从集群中寻找已经存在且满足条件的 PV 进行绑定,如果集群中没有匹配的 PV 则 PVC 将处于 Pending 等待状态,知道系统创建了符合条件的 PV 再与其绑定。PV 与 PVC 绑定后就不能和别的 PVC 进行绑定。
动态模式
在动态模式下,当创建 PVC 并且指定 StorageClass 后,与 StorageClass 关联的存储插件会自动创建对应的 PV 与该 PVC 进行绑定。
资源使用
Pod使用Volume定义,将PVC挂载到容器内的某个路径进行使用。
Volume的类型为Persistent VolumeClaim,在容器挂载了一个PVC后,就能被持续独占使用,多个Pod可以挂载到同一个PVC上。
存储回收
完成存储卷的使用目标之后删除 PVC 对象,以便进行资源回收,不过,至于如何操作则取决于 PV 的回收策略 ,目前有三种策略:
保存策略(Retain)
删除 PVC 之后,Kubernetes 系统不会自动删除 PV ,而仅是将它标识为 Released
状态,不过处于 Released
状态的 PV 不能被其他 PVC 申请所绑定,因为之前 PVC 绑定的应用数据仍然存在,需要由管理员手动清理数据,然后决定如何处理 PV 的使用。
回收策略
当 PVC 删除后,此 PV 变成 Available
可用状态。不过此策略需要后端存储插件的支持
删除策略
当删除 PVC 后 PV 和存储中的数据会被立即删除,不过此策略也需要后端存储插件的支持
使用注意
下面是PV/PVC使用的注意事项
请求大于PV
如果我们在PVC中的请求值大于PV中的最大值,那么绑定不会成功
创建资源清单
1 | vi pvc.yml |
我们创建一个请求大于PV的PVC,我们NFS的PV最大2G,而我们请求的是5G
1 | apiVersion: v1 |
应用配置
我们应用这个PVC的配置
1 | kubectl apply -f pvc.yml |
我们发现这个PVC处于pending的状态,没有完成绑定
我们查看下当前的PVC的详情
1 | kubectl describe pvc pvc1 |
我们发现没有找到对应的PV,主要原因则是没有找到对应大小的PV
多个PVC符合条件
如果有多个pv都符合pvc的要求呢,那么PVC会智能选择最匹配的PV
创建PV资源清单
我们创建一个PV的资源清单,两个PV都是1G
1 | vi pv.yml |
1 | apiVersion: v1 |
然后我们应用配置
1 | kubectl apply -f pv.ym |
我们发现创建了两个1G的PV
创建PVC资源清单
我们创建一个没有条件的PVC,请求有1G,符合条件的有PV1和PV2
1 | vi pvc.yml |
1 | apiVersion: v1 |
应用配置
1 | kubectl apply -f pvc.yml |
我们看到当前的PV和PVC绑定了,我们申请1G,我们发现和PV1绑定到了一起
我们删除PVC,然后在绑定一次试试
1 | kubectl delete pvc pvc1 |
我们发现和PV2绑定到了一起,也就是说如果同时符合多个条件会
随机
找一个绑定
容器匹配策略
如果的PV资源大小不一致,则不会产生这种问题,PVC绑定PV遵循
就近原则
的,容器匹配策略
匹配最接近的pv容量,向大的匹配
如果满足不了,pod处于pending