文章图片
目录
文章目录
-
- 目录
- 基础知识
-
- 为什么需要存储卷
- 数据卷概述
- 数据持久化的目的
- 数据集卷生命周期
- pv、pvc概念
-
- 概念
- PV的配置属性
- pv的RECLAIM POLICY(回收策略)
- pv的状态
- pv/pvc案例
- FAQ
-
- pvc是要和pv一一对应的
- pvc和pv的绑定原则
- 如果pvc要明确去和一个pv绑定呢,就可以使用`selector`功能了
- pv是否可以自动扩缩容
- 注意:pv和pvc里面的storageClassName只是一个标识,或者都没有也是可以的
- pv的删除方式
- pv/pvc排错
- 什么是StorageClass
-
- 支持动态供给的存储插件
- 案例:存储类使用
- 延迟`“绑定”`操作
- 1、临时存储卷(emptyDir)
-
- :cupid: 实战:临时存储卷(emptyDir)测试(测试成功)-2022.7.25
-
- 实验环境
- 实验软件(无)
- 1、部署pod资源
- 2、验证效果
- 3、查找临时存储卷在宿主机上实际存放位置
- 2、节点存储卷(hostPath)
-
- 实战1:hostPath测试(测试成功)-2022.7.25
-
- 实验环境
- 实验环境(无)
- 1、部署pod资源
- 2、验证效果
- 实战2:hostPath测试(测试成功)-2022.7.25
-
- 实验环境
- 实验环境(无)
- 1、在node1节点准备测试环境
- 2、编写pv.taml并部署
- 3、编写pvc.yaml并部署
- 4、创建pod.yaml并部署
- 5、测试
- 3、Local PV
-
- 实战:LocalPv测试(测试成功)-2022.7.30
-
- 实验环境
- 实验软件(无)
- 1.准备环境
- 2.部署pv,pvc资源
- 3.部署StorageClass资源
- 4.部署Pod资源
- 关于我
- 最后
基础知识 为什么需要存储卷
容器部署过程中一般有以下三种数据:
? 启动时需要的初始数据:例如配置文件 (init container)
? 启动过程中产生的临时数据:该临时数据需要多个容器间共享
? 启动过程中产生的持久化数据:例如MySQL的data目录 (业务数据–很重要)
文章图片
数据卷概述
? Kubernetes中的Volume提供了在容器中挂载外部存储的能力
? Pod需要设置卷来源(spec.volume)和挂载点(spec.containers.volumeMounts)两个信息后才可以使用相应的Volume
常用的数据卷:–>下面这些都可以叫做
数据卷来源
;? 本地(hostPath,emptyDir) #本地(hostPath,emptyDir)–这2种工作中还是比较常见的的,但是应用场景是非常单一的/有局限性的;
? 网络(NFS,Ceph,GlusterFS)
? 公有云(AWS EBS)
? K8S资源(configmap,secret)
?? 注意:数据卷来源(通过搜索volumes可以看到有很多数据卷类型的)
也可以使用explains命令来查看的。
文章图片
数据持久化的目的
- 通常情况下,容器运行起来后,写入到其容器内部的文件是暂时性的。当容器崩溃后,k8s将这个容器kill掉,然后生成一个新的容器,此时,新运行的容器将没有原来容器内的文件,因为容器是从镜像重新创建的。 (容器一旦重启或者被删除了,里面存放的数据是会被销毁的)
- 数据共享:同一个pod中运行的容器之间,经常会存在共享文件或目录的需求,比如采集容器和主容器;
数据持久化:
pod故障了,数据不会丢失;
pod重建了,还可以利用之前的数据;
版本迭代,数据还可以利用;
我们工作中,对于例如nginx等的网站程序,也是会把网站程序分离出来放在/ifs/kubernetes nfs共享目录的吗?=>不是这样的。
当然上面这种方法可行(但不利于后期管理),但是一般是直接把网站代码打包到镜像里面的(利于管理);
但是,这个网站程序有一部分是可以独立出来进行部署的,比如像一些图片,一些静态资源;
比如像公有云,把你的图片资源存放在对象存储上面,还有一些图库;
这只是一个实验性的,但是工作中不会这么去干;
pod一般是不直接去关联后端的存储信息的;为什么呢?而是使用pv/pvc这种方法。
k8s是一个大平台,是一个基础设施;
后面很多业务都放在k8s上了,后期方便进行统一的管理,统一的调度;
我们运维搭建的这个k8s平台主要是给业务他们使用的;
it岗位中:无非就那么几个:开发,测试,运维,前端,dba,安全运维;
我们搭建的这个k8s平台,开发用的还是挺多的;
现在很多这种部署项目的需求,直接交给开发去做这事儿了–大公司;
开发部署项目:就是要写yaml.
1.安全性:为了安全起见,没有必要把后端存储信心给暴露出来;“最小原则问题”
2.专业性:–>我们后端暴露给开发的要尽可能地"傻瓜式"。
–>平台化、系统流程化
数据集卷生命周期
k8s里面的volume数据卷存在明确的生命周期,并且volume的生命周期比同一容器组pod中的任意容器的生命周期都要长。不管容器本身重启了多少次,数据都被保留下来。当然,pod如果不存在了,数据卷自然就退出了。
注意:可以定义pod所使用的数据卷类型不同,数据可能随着数据卷的退出而删除,也可能被真正的持久化,即使后期重新使用容器时仍然可以使用该数据卷;pv、pvc概念
概念
PV
的全称是:PersistentVolume
(持久化卷),是对底层共享存储的一种抽象,PV 由管理员进行创建和配置,它和具体的底层的共享存储技术的实现方式有关,比如 Ceph
、GlusterFS
、NFS
、hostPath
等,都是通过插件机制完成与共享存储的对接。PVC
的全称是:PersistentVolumeClaim
(持久化卷声明),PVC 是用户存储的一种声明。PVC 和 Pod 比较类似,Pod 消耗的是节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 和内存,而 PVC 可以请求特定的存储空间和访问模式。对于真正使用存储的用户不需要关心底层的存储实现细节,只需要直接使用 PVC 即可。形象说明:
pv/pvc带来的不好之处:k8s管理员用起来可能没那么直接了;
这个技术时如何对k8s运维人员和开发者之间进行分离的?–>pv/pvc;
形象举例:
pv–网管那里的硬盘;
pvc–你开发同事的pc;
应用者–你开发同事;
#说明:
pv可以理解为直接对接你的存储的;
pvs直接对接的是那些应用-业务开发者的;
我们在持久化容器数据的时候使用 PV/PVC 有什么好处呢?比如我们这里之前直接在 Pod 下面也可以使用 hostPath 来持久化数据,为什么还要费劲去创建 PV、PVC 对象来引用呢?PVC 和 PV 的设计,其实跟
“面向对象”
的思想完全一致。PVC 可以理解为持久化存储的“接口”,它提供了对某种持久化存储的描述,但不提供具体的实现;而这个持久化存储的实现部分则由 PV 负责完成。这样做的好处是,作为应用开发者,我们只需要跟 PVC 这个“接口”打交道,而不必关心具体的实现是 hostPath、NFS 还是 Ceph。毕竟这些存储相关的知识太专业了,应该交给专业的人去做,这样对于我们的 Pod 来说就不用管具体的细节了,你只需要给我一个可用的 PVC 即可了,这样是不是就完全屏蔽了细节和解耦了啊,所以我们更应该使用 PV、PVC 这种方式。pv和pvc另一版本说明(simmon企业教练)
数据卷在k8s生产中用的最多的还是PV和PVC (PersistentVolume和PersistentVolumeClaim)。在企业里面,PV由运维工程师来进行维护(存储工程师),而PVC则是由开发人员自己申请使用即可。其中PV是具有持久性的,生命周期独立于Pod,研发人员需要进行一个容器的数据卷挂载,那么写一个PVC来绑定PV就可以了,k8s自身会查找符合条件的PV。
有了pvc,我们在K8s进行卷挂载就只需要考虑要多少容量了,而不用关心真正的空间是用什么存储系统做的等一些底层细节信息,pv这些只有存储管理员才应用去关心它。
K8s支持多种类型的pv,我们这里就以生产中常用的NFS来作演示(在云上的话就用NAS,我们使用了阿里云的NAS),生产中如果对存储要求不是太高的话,建议就用NFS,这样出问题也比较容易解决,如果有性能需求,可以看看rook的ceph。
这里列出了阿里云的NAS相关的性能,价格信息,供大家参考
1.通用型NAS存储(性价比高,可以存放web内容管理,数据库备份,日志存储等。):
- 通用型:容量是1~10PB,读带宽150MBps/TiB,最大IOPS是15K。价格是0.35元/GiB/月
- 性能型:容量是1PB,读带宽600MBps/TiB,最大IOPS是30K。价格是1.85元/GiB/月
- 容量:100GiB256TiB,带宽150MB/s 1200MB/s,最大IOPS是200K,时延0.2ms。价格是1.8元/GiB/月
文章图片
PV的配置属性 下面是关于 PV 的这些配置属性的一些说明:
- Capacity(存储能力):一般来说,一个 PV 对象都要指定一个存储能力,通过 PV 的
capacity
属性来设置的,目前只支持存储空间的设置,就是我们这里的storage=10Gi
,不过未来可能会加入IOPS
、吞吐量等指标的配置。
注意:这里的存储能力大小是一定就给你底层划分这么大的空间吗,不一定,和你底层使用的存储有关,例如是ceph 块存储的话,那么它底层就可能给你划分一个10g大小的空间;但是hostPath这种文件系统,它是限制不了的;
- AccessModes(访问模式):用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载 (Pod之间的数据共享,一般是读写权限,选择这种的。)
这里还需要注意的一点:pv.yaml里面accessModes(访问模式)的声明,仅仅只是一个声明而已,其实起作用的关键还是要看底层存储。例如NFS这里pv.yaml这里只声明了为ReadWriteOnce,但其实也是文件系统它是支持all模式的。??
一些 PV 可能支持多种访问模式,但是在挂载的时候只能使用一种访问模式,多种访问模式是不会生效的;下图是一些常用的 Volume 插件支持的访问模式:
块存储,一般只支持单个节点挂载,不支持多个节点共享; – 只能被一台服务器去挂载,不能被多台服务器挂载;因此适用于RWO、ROX;
文章图片
pv的RECLAIM POLICY(回收策略)
$ kubectl get pv
NAMECAPACITYACCESS MODESRECLAIM POLICYSTATUSCLAIMSTORAGECLASSREASONAGE
pv-hostpath10GiRWORetainAvailablemanual3m41s
其中有一项
RECLAIM POLICY
的配置,同样我们可以通过 PV 的 persistentVolumeReclaimPolicy
(回收策略)属性来进行配置,目前 PV 支持的策略有三种:- Retain(保留):保留数据,需要管理员手工清理数据
- Recycle(回收):清除 PV 中的数据,效果相当于执行
rm -rf /thevoluem/*
- Delete(删除):与 PV 相连的后端存储完成 volume 的删除操作;(当然这常见于云服务商的存储服务,比如 ASW EBS。)
NFS
和 HostPath
两种类型支持回收策略,当然一般来说还是设置为 Retain
这种策略保险一点。?? 注意:
Recycle
策略会通过运行一个 busybox 容器来执行数据删除命令,默认定义的 busybox 镜像是:gcr.io/google_containers/busybox:latest
,并且 imagePullPolicy: Always
,如果需要调整配置,需要增加kube-controller-manager
启动参数:--pv-recycler-pod-template-filepath-hostpath
来进行配置。pv的状态 关于 PV 的状态,实际上描述的是 PV 的生命周期的某个阶段,一个 PV 的生命周期中,可能会处于4种不同的阶段:
- Available(可用):表示可用状态,还未被任何 PVC 绑定 (常见)
- Bound(已绑定):表示 PV 已经被 PVC 绑定 (常见)
- Released(已释放):PVC 被删除,但是资源还未被集群重新声明 (这个是什么情况呢??)
- Failed(失败): 表示该 PV 的自动回收失败
capacity容量大小一致;(pvc里的可以
<=
pv里的大小,但pvc若大于pv里的容量,那么绑定就不会成功!)accessModes访问模式一致;
??注意
需要注意的是目前 PV 和 PVC 之间是一对一绑定的关系,也就是说一个 PV 只能被一个 PVC 绑定。
pv与pvc怎么匹配的?
- 存储空间
- 访问模式
#01-pv-hostpath.yaml
apiVersion: v1
kind: PersistentVolume #pv是全局范围内的,没有namespace的限制
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: manual #这个地方的存储类只是起到一个标识的作用!!!
capacity: #定义了一个10Gi大小的容量
storage: 10Gi
accessModes: #定义访问模式
- ReadWriteOnce
hostPath: #hostPath路径的声明(底层存储)
path: "/data/k8s/test/hostpath"---
# 02-pvc-hostpath.yaml
apiVersion: v1
kind: PersistentVolumeClaim #注意:pvc是具有namesapce限制的
metadata:
name: pvc-hostpath #pvc和pv是随机绑定的
spec:
storageClassName: manual #必须要和绑定发pv一致
accessModes:
- ReadWriteOnce
resources: #存储资源声明
requests: #请求的存储容量,需要和要绑定的pv一致
storage: 3Gi
pvc和pv的绑定原则 如果有多个pv都符合pvc的要求呢,那么
pvc和pv是随机绑定的
。?? 注意下容器匹配策略?
- 匹配最接近的pv容量,向大的匹配
- 如果满足不了,pod处于pending
#01-pv-hostpath.yaml
apiVersion: v1
kind: PersistentVolume #pv是全局范围内的,没有namespace的限制
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: manual #这个地方的存储类只是起到一个标识的作用
capacity: #定义了一个10Gi大小的容量
storage: 10Gi
accessModes: #定义访问模式
- ReadWriteOnce
hostPath: #hostPath路径的声明(底层存储)
path: "/data/k8s/test/hostpath"
---
apiVersion: v1
kind: PersistentVolume #pv是全局范围内的,没有namespace的限制
metadata:
name: pv-hostpath2
labels:
type: local2
spec:
storageClassName: manual2 #这个地方的存储类只是起到一个标识的作用
capacity: #定义了一个10Gi大小的容量
storage: 10Gi
accessModes: #定义访问模式
- ReadWriteOnce
hostPath: #hostPath路径的声明(底层存储)
path: "/data/k8s/test/hostpath2"---
# 02-pvc-hostpath.yaml
apiVersion: v1
kind: PersistentVolumeClaim #注意:pvc是具有namesapce限制的
metadata:
name: pvc-hostpath #pvc和pv是随机绑定的
spec:
storageClassName: manual #必须要和绑定发pv一致
accessModes:
- ReadWriteOnce
resources: #存储资源声明
requests: #请求的存储容量,需要和要绑定的pv一致
storage: 3Gi
# selector: #明确去绑定一个具有type=local标签的pv,这个一般不怎么使用!
#matchLabels:
# type: local #这个标签就是pv的label标签了
之前经实际测试,阿良老师说是遵循
就近原则
的!!!现在的情况是:我管理员只有5/15/50G大小的硬盘。
1、我的开发同事要申请一块17G大小的硬盘,该如何分配呢?
=>经实验测试,可以看出是会给大的的。 (就近原则把。。。。)
2、:此时,开发同事haunt需要一个17G硬盘,那么我这个网关该如何给它分配呢?
由输出结果判断:其状态是pending状态,k8s管理员说,我这里已经没有你需要的硬盘了,过段时间再来吧,不能满足你了。
3、现在,研发同事还需要一个15g大小的硬盘,k8s管理员请帮忙分配下:
由输出结果可以看到,pv2-15G的硬盘被分配出去了。
结论:如果pvc要明确去和一个pv绑定呢,就可以使用
1、容器匹配策略?
- 匹配最接近的pv容量,向大的匹配
- 如果满足不了,pod处于pending
selector
功能了
#01-pv-hostpath.yaml
apiVersion: v1
kind: PersistentVolume #pv是全局范围内的,没有namespace的限制
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: manual #这个地方的存储类只是起到一个标识的作用
capacity: #定义了一个10Gi大小的容量
storage: 10Gi
accessModes: #定义访问模式
- ReadWriteOnce
hostPath: #hostPath路径的声明(底层存储)
path: "/data/k8s/test/hostpath"
---
# 02-pvc-hostpath.yaml
apiVersion: v1
kind: PersistentVolumeClaim #注意:pvc是具有namesapce限制的
metadata:
name: pvc-hostpath #pvc和pv是随机绑定的
spec:
storageClassName: manual #必须要和绑定发pv一致
accessModes:
- ReadWriteOnce
resources: #存储资源声明
requests: #请求的存储容量,需要和要绑定的pv一致
storage: 3Gi
# selector: #明确去绑定一个具有type=local标签的pv,这个一般不怎么使用!
#matchLabels:
# type: local #这个标签就是pv的label标签了
pv是否可以自动扩缩容
#01-pv-hostpath.yaml
apiVersion: v1
kind: PersistentVolume #pv是全局范围内的,没有namespace的限制
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: manual #这个地方的存储类只是起到一个标识的作用,可以随便自定义
capacity: #定义了一个10Gi大小的容量
storage: 10Gi #nfs 这个地方其实没意义,定义10个G,你的pod实际上可以使用大于10G;(和你底层存储有关);底层pv能否扩容,是要依赖于你底层存储的; ceph的rbd是可以扩容的,nfs你本来就限制不住的;
accessModes: #定义访问模式
- ReadWriteOnce
hostPath: #hostPath路径的声明(底层存储)
path: "/data/k8s/test/hostpath"
nfs还需要去扩容吗,不需要,它就是一个文件系统,只是一个声明;
pv不支持扩容/缩容,pv看后端存储支持不支持;
问题:怎么限制pvc向后端申请容量的大小?
回答:
具体要看后端存储支持不支持;
ceph有容量限制的能力,而nfs不具备这种能力;
许多云厂商都表明了硬盘规格大小的;
其他别的存储都有这种限制的能力;
问题:pvc可以写超过后端存储容量的吗?
回答:
完全可以,但是没有什么实际意义。
例如对nfs而言,它所在存储硬盘大小为50G,而你pvc实际申请的是500G,此时nfs明显满足不了,因此创建的pv,pvc会处于pending状态。
存储空间这个字段能限制实际存储容量?
不能,存储空间只是用于匹配的一种标记,具体限制看后端存储,像我们演示环境就要看nfs是否具备限制能力。
注意:pv和pvc里面的storageClassName只是一个标识,或者都没有也是可以的
#01-pv-hostpath.yaml
apiVersion: v1
kind: PersistentVolume #pv是全局范围内的,没有namespace的限制
metadata:
name: pv-hostpath
labels:
type: local
spec:
capacity: #定义了一个10Gi大小的容量
storage: 10Gi #nfs 这个地方其实没意义,定义10个G,你的pod实际上可以使用大于10G;(和你底层存储有关);底层pv能否扩容,是要依赖于你底层存储的; ceph的rbd是可以扩容的,nfs你本来就限制不住的;
accessModes: #定义访问模式
- ReadWriteOnce
hostPath: #hostPath路径的声明(底层存储)
path: "/data/k8s/test/hostpath"---
# 02-pvc-hostpath.yaml
apiVersion: v1
kind: PersistentVolumeClaim #注意:pvc是具有namesapce限制的
metadata:
name: pvc-hostpath #注意:如果有多个pv满足pvc的要求,那么pvc和pv是随机绑定的
spec:
accessModes:
- ReadWriteOnce
resources: #存储资源声明
requests: #请求的存储容量,需要和要绑定的pv一致
storage: 3Gi
# selector: #明确去绑定一个具有type=local标签的pv,这个一般不怎么使用!
#matchLabels:
# type: local #这个标签就是pv的label标签了
注意:
#01-pv-hostpath.yaml
apiVersion: v1
kind: PersistentVolume #pv是全局范围内的,没有namespace的限制
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: manual #这个地方的存储类只是起到一个标识的作用!!!
capacity: #定义了一个10Gi大小的容量
storage: 10Gi
accessModes: #定义访问模式
- ReadWriteOnce
hostPath: #hostPath路径的声明(底层存储)
path: "/data/k8s/test/hostpath"---
# 02-pvc-hostpath.yaml
apiVersion: v1
kind: PersistentVolumeClaim #注意:pvc是具有namesapce限制的
metadata:
name: pvc-hostpath #pvc和pv是随机绑定的
spec:
storageClassName: manual #必须要和绑定发pv一致
accessModes:
- ReadWriteOnce
resources: #存储资源声明
requests: #请求的存储容量,需要和要绑定的pv一致
storage: 3Gi
pv的删除方式 ?? 需要注意的是,我们上面手动创建 PV 的方式,即静态的 PV 管理方式,在删除 PV 时需要按如下流程执行操作:
- 删除使用这个 PV 的 Pod
- 从宿主机移除本地磁盘
- 删除 PVC
- 删除 PV
[root@master1 ~]#kubectl delete -f 07-pv-local-pod.yaml
pod "pv-local-pod" deleted
[root@master1 ~]#kubectl delete pvc pvc-local
persistentvolumeclaim "pvc-local" deleted
[root@master1 ~]#kubectl delete pv pv-local
persistentvolume "pv-local" deleted
pv/pvc排错 1、创建pvc的时候
STATUS
状态一直是Pending
a、查看PV里面的
accessModes
字段和PVC里面的accessModes
是否对应上面,如果不对应则会是这种错误b、PVC的
accessModes
是否是PV的子集,比如:# PV里面的`accessModes`字段如果是集合的写法
accessModes: ["ReadWriteMany","ReadWriteOnce","ReadOnlyMany"]# 此时PVC的写法如下是正确的:
accessModes: ["ReadWriteOnce"]# 如果不在集合里面,则会报错。
2、创建pvc之后,发现pv的
CLAIM
字段为空正常情况下pvc绑定到pv之后,pv的CLAIM字段显示的是对应的PVC的名称,但是现在出现异常,CLAIM字段为空,这可能是pvc和pv的
storage
字段的大小不一致导致的。注意:可能是selector 的label等内容是符合的,但就这个storage的大小不一致,像是绑定成功了,又像是绑定失败了。什么是StorageClass
但是通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求,而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等。(例如:prometheus,etcd等对存储io的性能要求都很高)
为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:
StorageClass
,通过 StorageClass
的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。此外 StorageClass
还可以为我们自动生成 PV,免去了每次手动创建的麻烦。支持动态供给的存储插件 https://kubernetes.io/docs/concepts/storage/storage-classes/
这个打钩的代表k8s支持其存储使用"StorageClass"功能,没打钩的就需要自己去想办法了。
文章图片
没打钩的就需要自己去想办法了,官方也给出了解决办法:就需要自己二次开发了。
文章图片
文章图片
文章图片
案例:存储类使用
注意:存储类是体现在pv和pvc的案例storageClassName
字段里的。
#创建StorageClass资源
# 06-local-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer#创建pv资源
# 04-pv-local.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem #这个其实都可以不用写,因为默认就是filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /data/k8s/localpv# node1节点上的目录,这个提前要在node1创建好
nodeAffinity: #注意:因为pv这里写了node亲和性,你pod想要使用我,那么你就需要先调度到这个node1节点才行,且pod那里不用再写nodeSelector了;
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1 #创建pvc资源
# 05-pvc-local.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-local
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage#创建pod资源
# 07-pv-local-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pv-local-pod
spec:
volumes:
- name: example-pv-local
persistentVolumeClaim:
claimName: pvc-local #没有明确绑定到具体的节点
containers:
- name: example-pv-local
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: example-pv-local
案例
# apiVersion: storage.k8s.io/v1
# kind: StorageClass
# metadata:
#name: local-storage
# provisioner: kubernetes.io/no-provisioner
# volumeBindingMode: WaitForFirstConsumer #注意下这里的卷绑定模式!!!(只有当pod使用了pvc后,pvc和pv才会显示为bound状态)
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: prometheus-data
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 20Gi
storageClassName: local-storage
local:
path: /data/k8s/prometheus
persistentVolumeReclaimPolicy: Retain
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prometheus-data
namespace: kube-vm
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: local-storage
文章图片
延迟
“绑定”
操作 所以我们需要创建对应的 StorageClass
对象:# 06-local-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
这个
StorageClass
的名字,叫作 local-storage,也就是我们在 PV 中声明的,需要注意的是,在它的 provisioner
字段,我们指定的是 no-provisioner
。这是因为我们这里是手动创建的 PV,所以不需要动态来生成 PV.另外这个 StorageClass 还定义了一个
volumeBindingMode=WaitForFirstConsumer
的属性,它是 Local PV
里一个非常重要的特性,即:延迟绑定。通过这个延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度。1、临时存储卷(emptyDir) 临时存储卷:emptyDir
emptyDir卷:是一个临时存储卷,与Pod生命周期绑定一起,如果Pod删除了卷也会被删除。
应用场景:Pod中容器之间数据共享
备注:
当一个pod中有多个容器,如果容器之间有数据共享的需求时,可以使用这个emptyDir数据卷功能;
emptyDir会在宿主机上创建一个空目录
;
- yaml案例
文章图片
#示例:Pod内容器之前共享数据
apiVersion: v1
kind: Pod
metadata:
name: my-podspec:
containers:
- name: write #定义一个写容器
image: centos
command: ["bash","-c","for i in {1..100};
do echo $i >> /data/hello;
sleep 1;
done"]
volumeMounts:
- name: data
mountPath: /data- name: read #定义一个读容器
image: centos
command: ["bash","-c","tail -f /data/hello"]
volumeMounts:
- name: data
mountPath: /datavolumes: #重点看下这个,数据卷的来源。Pod需要设置卷来源(spec.volume)
- name: data #注意,这里的卷name就是上面2个容器挂载点(spec.containers.volumeMounts)名称,要保持一致。
emptyDir: {} #emptyDir这个特别好记,就是一个空的,没什么字段需要去记。
实战:临时存储卷(emptyDir)测试(测试成功)-2022.7.25
实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
k8s version:v1.20 #附近版本都可以
CONTAINER-RUNTIME:docker://20.10.7 #附近版本都可以
实验软件(无) 1、部署pod资源
[root@k8s-master ~]#vim emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-podspec:
containers:
- name: write #定义一个写容器
image: centos
command: ["bash","-c","for i in {1..100};
do echo $i >> /data/hello;
sleep 1;
done"]
volumeMounts:
- name: data
mountPath: /data- name: read #定义一个读容器
image: centos
command: ["bash","-c","tail -f /data/hello"]
volumeMounts:
- name: data
mountPath: /datavolumes: #重点看下这个,数据卷的来源。Pod需要设置卷来源(spec.volume)
- name: data #注意,这里的卷name就是上面2个容器挂载点(spec.containers.volumeMounts)名称,要保持一致。
emptyDir: {} #emptyDir这个特别好记,就是一个空的,没什么字段需要去记。
文章图片
- apply并查看
[root@k8s-master ~]#kubectl apply -f emptydir.yaml
pod/my-pod created[root@k8s-master ~]#kubectl get pod
NAMEREADYSTATUSRESTARTSAGE
my-pod2/2Running030s #2/2代表该pod里有2个容器[root@k8s-master ~]#kubectl get pod -o wide
NAMEREADYSTATUSRESTARTSAGEIPNODENOMINATED NODEREADINESS GATES
my-pod2/2Running033s10.244.169.181k8s-node2 #注意,本次pod所在的node节点是哪一个,本次是在node2节点上
2、验证效果 验证方法:在读容器上查看日志是否会出现读取日志的现象
[root@k8s-master ~]#kubectl get pod
NAMEREADYSTATUSRESTARTSAGE
bs31/1Running66d13h
my-pod2/2Running37m9s[root@k8s-master ~]#kubectl logs my-pod -c read
[root@k8s-master ~]#kubectl logs my-pod -c read -f #-f代表日志持续动输出
=>符合预期,读容器可以实时显示出日志输出来,说明2个容器见的共享数据功能是通过emptyDir已经是实现了的。
文章图片
- 特别注意:这里100过后,又会持续从1开始的。
文章图片
文章图片
3、查找临时存储卷在宿主机上实际存放位置
- 找到该Pod所在node机器:
[root@k8s-master ~]#kubectl get pod -o wide
NAMEREADYSTATUSRESTARTSAGEIPNODENOMINATED NODEREADINESS GATES
bs31/1Running66d13h10.244.36.76k8s-node1
my-pod2/2Running033s10.244.169.181k8s-node2 #注意,本次pod所在的node节点是哪一个,本次是在node2节点上
- 进到
/var/lib/kubelet/pods/
目录下:
文章图片
[root@k8s-node2 ~]#cd /var/lib/kubelet/pods/ #在这个目录下。
[root@k8s-node2 pods]#ls
04456e06-9ee5-4ce0-8106-5300f8ddd0f01ec5f80c-c5fb-4955-9ee6-15f7b4f807b62ae1cb3e-6956-4921-8eaa-bb123dc0cd4c
[root@k8s-node2 pods]#c
- 进一步查看容器ID:
找到对应的额node节点,执行如下命令:
[root@k8s-node2 ~]#docker ps |head -1;
docker ps |grep my-pod
文章图片
- 容器共享卷目录路径如下:
/var/lib/kubelet/pods/1ec5f80c-c5fb-4955-9ee6-15f7b4f807b6/volumes/kubernetes.io~empty-dir/data
文章图片
文章图片
实验到此结束,完美。
2、节点存储卷(hostPath) 我们上面提到了 PV 是对底层存储技术的一种抽象,PV 一般都是由管理员来创建和配置的。
我们首先来创建一个
hostPath
类型的 PersistentVolume
。Kubernetes 支持 hostPath 类型的 PersistentVolume 使用节点上的文件或目录来模拟附带网络的存储,但是需要注意的是在生产集群中,我们不会使用 hostPath。集群管理员会提供网络存储资源,比如 NFS 共享卷或 Ceph 存储卷,集群管理员还可以使用
StorageClasses
来设置动态提供存储。因为 Pod 并不是始终固定在某个节点上面的,所以要使用 hostPath 的话我们就需要将 Pod 固定在某个节点上,这样显然就大大降低了应用的容错性。
hostPath卷:挂载Node文件系统(Pod所在节点)上文件或者目录到Pod中的容器。
应用场景:Pod中容器需要访问宿主机文件
一般用于运维收集日志、监控时使用,业务不会用到次功能;?? 注意:通过这种数据卷挂载时,一般是要先知道pod在哪个节点上。因此,工作中,每个节点上的目录应该尽可能保持一致。
linux–一切皆文件,通过/proc这个虚拟目录可以获取很多信息的。容器有时会希望获取到所在节点的一些信息,因此就可以使用"节点存储卷:hostPath"这个功能。
?? 注意:做完共享卷后,此时容器里的用户权限是rw的。
做完共享卷后,此时容器里的用户权限是rw的,因此,它可以直接删除宿主机的/目录的哈哈。–>一般情况,安全起见,会通过配置一些权限,让镜像里运行程序的用户为普通用户。
这种hostPath方法,容器可以随意挂载宿主机的目录,感觉有点危险。
回答:这是个很好的问题,在cks里,会讲如何通过一定的手段去防止这些风险;(k8s的安全上下文)
文章图片
文章图片
- yaml案例
示例:将宿主机/tmp目录挂载到容器/data目录
apiVersion: v1
kind: Pod
metadata:
name: my-podspec:
containers:
- name: busybox
image: busybox
args:
- /bin/sh
- -c
- sleep 36000volumeMounts:
- name: data
mountPath: /datavolumes:
- name: data
hostPath:
path: /tmp
type: Directory #默认为空,为可选参数
文章图片
实战1:hostPath测试(测试成功)-2022.7.25
实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
k8s version:v1.20 #附近版本都可以
CONTAINER-RUNTIME:docker://20.10.7 #附近版本都可以
实验环境(无) 1、部署pod资源
[root@k8s-master ~]#vim hostpath.yaml #
apiVersion: v1
kind: Pod
metadata:
name: my-pod2spec:
containers:
- name: busybox
image: busybox
args:
- /bin/sh
- -c
- sleep 36000volumeMounts:
- name: data
mountPath: /data #容器里路径
- name: data2
mountPath: /optvolumes:
- name: data
hostPath:
path: /tmp #宿主机目录 挂载点1
type: Directory #Directory或者File
- name: data2 #我这边再挂载一个宿主机目录,类型是hostPath的;
hostPath:
path: / #挂载点2
type: Directory
文章图片
- apply并查看
[root@k8s-master ~]#kubectl apply -f hostpath.yaml
pod/my-pod2 created[root@k8s-master ~]#kubectl get pod -o wide
NAMEREADYSTATUSRESTARTSAGEIPNODENOMINATED NODEREADINESS GATES
my-pod21/1Running067s10.244.169.184k8s-node2
[root@k8s-master ~]# #该pod被创建在了node2上。
2、验证效果 1.验证 挂载点1效果
进入到刚才创建的容器的/data目录下创建一个文件,观察node2的/tmp目录下是否会出现刚才创建的文件(反之也可以测试一下):=>会出现,符合预期效果。
#master节点测试
[root@k8s-master ~]#kubectl get pod
NAMEREADYSTATUSRESTARTSAGE
my-pod21/1Running016m[root@k8s-master ~]#kubectl exec -it my-pod2 -- sh
/ # cd /data/
/data # ls
vmware-root_5301-3854408134vmware-root_5438-3091805268vmware-root_5458-2864710535
vmware-root_5367-4114393124vmware-root_5439-4156469130vmware-root_5471-3887505362
vmware-root_5424-3125359197vmware-root_5455-3854473666vmware-root_5477-3879050870
/data # touch 1.txt
/data # ls
1.txtvmware-root_5367-4114393124vmware-root_5439-4156469130vmware-root_5471-3887505362
2.txtvmware-root_5424-3125359197vmware-root_5455-3854473666vmware-root_5477-3879050870
vmware-root_5301-3854408134vmware-root_5438-3091805268vmware-root_5458-2864710535
/data ##来到node2节点测试:
[root@k8s-node2 ~]#ls /tmp/ #发现node2节点上出现了刚才创建的1.txt文件,符合预期效果
1.txtvmware-root_5424-3125359197vmware-root_5455-3854473666vmware-root_5477-3879050870
vmware-root_5301-3854408134vmware-root_5438-3091805268vmware-root_5458-2864710535
vmware-root_5367-4114393124vmware-root_5439-4156469130vmware-root_5471-3887505362
[root@k8s-node2 ~]#touch /tmp/2.txt
[root@k8s-node2 ~]#
2.验证 挂载点2效果
进入到刚才创建的容器的/opt目录下观察该目录下的文件信息及创建一个文件,看node2节点/目录下是否会出现相同的文件:=>符合预期。
#master节点执行
/ # cd /opt/
/opt # ls
bindevhomelib64mntprocrunsrvtmpvar
bootetclibmediaoptrootsbinsysusr
/opt # touch aaaa
/opt ##来node2节点执行
[root@k8s-node2 ~]#ls / #发现node2节点上/下出现了刚才创建的aaaa文件,符合预期效果
aaaabinbootdevetchomeliblib64mediamntoptprocrootrunsbinsrvsystmpusrvar
[root@k8s-node2 ~]#
实验结束,完美。
实战2:hostPath测试(测试成功)-2022.7.25
实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
k8s version:v1.20 #附近版本都可以
CONTAINER-RUNTIME:docker://20.10.7 #附近版本都可以
实验环境(无) 1、在node1节点准备测试环境 比如我们这里将测试的应用固定在节点 node1 上面,首先在该节点上面创建一个
/data/k8s/test/hostpath
的目录,然后在该目录中创建一个 index.html
的文件:[root@master1 ~]#ssh node1
[root@node1 ~]#mkdir -p /data/k8s/test/hostpath
[root@node1 ~]#echo 'Hello from Kubernetes hostpath storage' > /data/k8s/test/hostpath/index.html
2、编写pv.taml并部署 然后接下来创建一个 hostPath 类型的 PV 资源对象:
#01-pv-hostpath.yaml
apiVersion: v1
kind: PersistentVolume #pv是全局范围内的,没有namespace的限制
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: manual #这个地方的存储类只是起到一个标识的作用,可以随便自定义
capacity: #定义了一个10Gi大小的容量
storage: 10Gi #nfs 这个地方其实没意义,定义10个G,你的pod实际上可以使用大于10G;(和你底层存储有关);底层pv能否扩容,是要依赖于你底层存储的; ceph的rbd是可以扩容的,nfs你本来就限制不住的;
accessModes: #定义访问模式
- ReadWriteOnce
hostPath: #hostPath路径的声明(底层存储)
path: "/data/k8s/test/hostpath"
配置文件中指定了该卷位于集群节点上的
/data/k8s/test/hostpath
目录,还指定了 10G 大小的空间和 ReadWriteOnce
的访问模式。这意味着该卷可以在单个节点上以读写方式挂载。另外还定义了名称为 manual
的 StorageClass
,该名称用来将PersistentVolumeClaim
请求绑定到该 PersistentVolum
。- 直接创建上面的资源对象:
$ kubectl apply -f 01-pv-hostpath.yaml
persistentvolume/pv-hostpath created
创建完成后查看 PersistentVolume 的信息,输出结果显示该
PersistentVolume
的状态(STATUS) 为 Available
。 这意味着它还没有被绑定给 PersistentVolumeClaim
:$ kubectl get pv
NAMECAPACITYACCESS MODESRECLAIM POLICYSTATUSCLAIMSTORAGECLASSREASONAGE
pv-hostpath10GiRWORetainAvailablemanual3m41s
3、编写pvc.yaml并部署 现在我们创建完成了 PV,如果我们需要使用这个 PV 的话,就需要创建一个对应的 PVC 来和他进行绑定了,就类似于我们的服务是通过 Pod 来运行的,而不是 Node,只是 Pod 跑在 Node 上而已。
现在我们来创建一个
PersistentVolumeClaim
,Pod 使用 PVC 来请求物理存储,我们这里创建的 PVC 请求至少 3G 容量的卷,该卷至少可以为一个节点提供读写访问,下面是 PVC 的配置文件:# 02-pvc-hostpath.yaml
apiVersion: v1
kind: PersistentVolumeClaim #注意:pvc是具有namesapce限制的
metadata:
name: pvc-hostpath #注意:如果有多个pv满足pvc的要求,那么pvc和pv是随机绑定的
spec:
storageClassName: manual #必须要和绑定发pv一致
accessModes:
- ReadWriteOnce
resources: #存储资源声明
requests: #请求的存储容量,需要和要绑定的pv一致
storage: 3Gi
# selector: #明确去绑定一个具有type=local标签的pv,这个一般不怎么使用!
#matchLabels:
# type: local #这个标签就是pv的label标签了
- 同样我们可以直接创建这个 PVC 对象:
$ kubectl apply -f 02-pvc-hostpath.yaml
persistentvolumeclaim/pvc-hostpath created
创建 PVC 之后,Kubernetes 就会去查找满足我们声明要求的 PV,比如
storageClassName
、accessModes
以及容量这些是否满足要求,如果满足要求就会将 PV 和 PVC 绑定在一起。我们现在再次查看 PV 的信息:
$ kubectl get pv -l type=local
NAMECAPACITYACCESS MODESRECLAIM POLICYSTATUSCLAIMSTORAGECLASSREASONAGE
pv-hostpath10GiRWORetainBounddefault/pvc-hostpathmanual5m42s
现在输出的 STATUS 为
Bound
,查看 PVC 的信息:$ kubectl get pvc pvc-hostpath
NAMESTATUSVOLUMECAPACITYACCESS MODESSTORAGECLASSAGE
pvc-hostpathBoundpv-hostpath10GiRWOmanual72s
输出结果表明该 PVC 绑定了到了上面我们创建的
pv-hostpath
这个 PV 上面了,我们这里虽然声明的3G的容量,但是由于 PV 里面是 10G,所以显然也是满足要求的。4、创建pod.yaml并部署
- PVC 准备好过后,接下来我们就可以来创建 Pod 了,该 Pod 使用上面我们声明的 PVC 作为存储卷:
# 03-pv-hostpath-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pv-hostpath-pod
spec:
volumes:
- name: pv-hostpath
persistentVolumeClaim:
claimName: pvc-hostpath
nodeSelector: #由于是hostPath类型的存储,所以一般结合nodeSelector固定在一个节点上
kubernetes.io/hostname: node1
containers:
- name: task-pv-container
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html" #index.html(默认的页面)
name: pv-hostpath
这里需要注意的是,由于我们创建的 PV 真正的存储在节点 node1 上面,所以我们这里必须把 Pod 固定在这个节点下面。另外可以注意到 Pod 的配置文件指定了
PersistentVolumeClaim
,但没有指定 PersistentVolume
,对 Pod 而言,PVC
就是一个存储卷。- 直接创建这个 Pod 对象即可:
$ kubectl apply -f 03-pv-hostpath-pod.yaml
pod/pv-hostpath-pod created
$ kubectl get po pv-hostpath-pod -owide
NAMEREADYSTATUSRESTARTSAGEIPNODENOMINATED NODEREADINESS GATES
pv-hostpath-pod1/1Running062s10.244.1.242node1
5、测试
- 验证:运行成功后,我们可以打开一个 shell 访问 Pod 中的容器:
kubectl exec -it pv-hostpath-pod -- /bin/bash
在 shell 中,我们可以验证 nginx 的数据 是否正在从 hostPath 卷提供 index.html 文件:
root@pv-hostpath-pod:/# apt-get update
root@pv-hostpath-pod:/# apt-get install curl -y
root@pv-hostpath-pod:/# curl localhost
Hello from Kubernetes hostpath storage
- 我们再次修改下宿主机文件内容,再次观察下现象:
[root@node1 ~]#echolovexyy >> /data/k8s/test/hostpath/index.html
root@pv-hostpath-pod:/# curl localhost
Hello from Kubernetes hostpath storage
lovexyy
- 我们可以看到输出结果是我们前面写到 hostPath 卷种的 index.html 文件中的内容,同样我们可以把 Pod 删除,然后再次重建再测试一次,可以发现内容还是我们在 hostPath 种设置的内容。
$ kubectl get po -owide
NAMEREADYSTATUSRESTARTSAGEIPNODENOMINATED NODEREADINESS GATES
pv-hostpath-pod1/1Running07m28s10.244.1.242node1
$ kubectl delete -f 03-pv-hostpath-pod.yaml
pod "pv-hostpath-pod" deleted
$ kubectl apply -f 03-pv-hostpath-pod.yaml
pod/pv-hostpath-pod created
$ kubectl get po -owide
NAMEREADYSTATUSRESTARTSAGEIPNODENOMINATED NODEREADINESS GATES
pv-hostpath-pod1/1Running037s10.244.1.243node1再次测试OK:
[root@node1 ~]#curl 10.244.1.243
Hello from Kubernetes hostpath storage
lovexyy
测试结束。
3、Local PV 前面我们有通过
hostPath
或者 emptyDir
的方式来持久化我们的数据,但是显然我们还需要更加可靠的存储来保存应用的持久化数据,这样容器在重建后,依然可以使用之前的数据。但是存储资源和 CPU 资源以及内存资源有很大不同,为了屏蔽底层的技术实现细节,让用户更加方便的使用,Kubernetes 便引入了 PV
和 PVC
两个重要的资源对象来实现对存储的管理。上面我们创建了后端是 hostPath 类型的 PV 资源对象,我们也提到了,使用 hostPath 有一个局限性就是,我们的 Pod 不能随便漂移,需要固定到一个节点上,因为一旦漂移到其他节点上去了宿主机上面就没有对应的数据了,所以我们在使用 hostPath 的时候都会搭配 nodeSelector 来进行使用。但是使用 hostPath 明显也有一些好处的,因为 PV 直接使用的是本地磁盘,尤其是 SSD 盘,它的读写性能相比于大多数远程存储来说,要好得多,所以对于一些对磁盘 IO 要求比较高的应用比如 etcd 就非常实用了。
不过呢,相比于正常的 PV 来说,使用了 hostPath 的这些节点一旦宕机数据就可能丢失,所以这就要求使用 hostPath 的应用必须具备数据备份和恢复的能力,允许你把这些数据定时备份在其他位置。
kafaka,eleatissearch,etcd等几种应用使用local pv也是推荐的;所以在 hostPath 的基础上,Kubernetes 依靠 PV、PVC 实现了一个新的特性,这个特性的名字叫作:
Local Persistent Volume
,也就是我们说的 Local PV
。==其实
Local PV
实现的功能就非常类似于 hostPath
加上 nodeAffinity
。==比如,一个 Pod 可以声明使用类型为 Local 的 PV,而这个 PV 其实就是一个 hostPath 类型的 Volume。如果这个 hostPath 对应的目录,已经在节点 A 上被事先创建好了,那么,我只需要再给这个 Pod 加上一个 nodeAffinity=nodeA
,不就可以使用这个 Volume 了吗?理论上确实是可行的,但是事实上,我们绝不应该把一个宿主机上的目录当作 PV 来使用,因为本地目录的存储行为是完全不可控,它所在的磁盘随时都可能被应用写满,甚至造成整个宿主机宕机。所以,一般来说
Local PV
对应的存储介质是一块额外挂载在宿主机的磁盘或者块设备,我们可以认为就是“一个 PV 一块盘”
。另外一个
Local PV
和普通的 PV 有一个很大的不同在于 Local PV
可以保证 Pod 始终能够被正确地调度到它所请求的 Local PV
所在的节点上面。对于普通的 PV 来说,Kubernetes 都是先调度 Pod 到某个节点上,然后再持久化节点上的 Volume 目录,进而完成 Volume 目录与容器的绑定挂载。
但是对于
Local PV
来说,节点上可供使用的磁盘必须是提前准备好的,因为它们在不同节点上的挂载情况可能完全不同,甚至有的节点可以没这种磁盘,所以,这时候,调度器就必须能够知道所有节点与 Local PV
对应的磁盘的关联关系,然后根据这个信息来调度 Pod,实际上就是在调度的时候考虑 Volume 的分布。实战:LocalPv测试(测试成功)-2022.7.30
实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
k8s version:v1.20 #附近版本都可以
CONTAINER-RUNTIME:docker://20.10.7 #附近版本都可以
实验软件(无) 1.准备环境
- 在测试这个实验之前,将上面测试实验的资源给移除掉
kubectl delete -f .
- 在node1提前创建好
/data/k8s/localpv
目录
[root@node1 ~]#mkdir /data/k8s/localpv
2.部署pv,pvc资源
- 接下来我们来测试下
Local PV
的使用,当然按照上面我们的分析我们应该给宿主机挂载并格式化一个可用的磁盘,我们这里就暂时将 node1 节点上的/data/k8s/localpv
这个目录看成是挂载的一个独立的磁盘。现在我们来声明一个Local PV
类型的 PV,如下所示:
# 04-pv-local.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem #这个其实都可以不用写,因为默认就是filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /data/k8s/localpv# node1节点上的目录,这个提前要在node1创建好
nodeAffinity: #注意:因为pv这里写了node亲和性,你pod想要使用我,那么你就需要先调度到这个node1节点才行,且pod那里不用再写nodeSelector了;
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1
和前面我们定义的 PV 不同,我们这里定义了一个
local
字段,表明它是一个 Local PV
。而 path 字段,指定的正是这个 PV 对应的本地磁盘的路径,即:/data/k8s/localpv
,这也就意味着如果 Pod 要想使用这个 PV,那它就必须运行在 node1 节点上。所以,在这个 PV 的定义里,添加了一个节点亲和性 nodeAffinity
字段指定 node1 这个节点。这样,调度器在调度 Pod 的时候,就能够知道一个 PV 与节点的对应关系,从而做出正确的选择。- 直接创建上面的资源对象
$ kubectl apply -f 04-pv-local.yaml
persistentvolume/pv-local created
$ kubectl get pv
NAMECAPACITYACCESS MODESRECLAIM POLICYSTATUSCLAIMSTORAGECLASSREASONAGE
pv-local5GiRWODeleteAvailablelocal-storage6s
- 可以看到,这个 PV 创建后,进入了
Available
(可用)状态。这个时候如果按照前面提到的,我们要使用这个Local PV
的话就需要去创建一个 PVC 和他进行绑定:
# 05-pvc-local.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-local
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage
- 同样要注意声明的这些属性需要和上面的 PV 对应,直接创建这个资源对象:
$ kubectl apply -f 05-pvc-local.yaml
persistentvolumeclaim/pvc-local created
$ kubectl get pvc
NAMESTATUSVOLUMECAPACITYACCESS MODESSTORAGECLASSAGE
pvc-localBoundpv-local5GiRWOlocal-storage4s
可以看到现在 PVC 和 PV 已经处于
Bound
绑定状态了。但实际上这是不符合我们的需求的,比如现在我们的 Pod 声明使用这个 pvc-local,并且我们也明确规定,这个 Pod 只能运行在 node2 这个节点上,如果按照上面我们这里的操作,这个 pvc-local 是不是就和我们这里的 pv-local 这个
Local PV
绑定在一起了,但是这个 PV 的存储卷又在 node1 这个节点上,显然就会出现冲突了,那么这个 Pod 的调度肯定就会失败了,所以我们在使用 Local PV
的时候,必须想办法延迟这个“绑定”
操作。要怎么来实现这个延迟绑定呢?我们可以通过创建
StorageClass
来指定这个动作,在 StorageClass 种有一个 volumeBindingMode=WaitForFirstConsumer
的属性,就是告诉 Kubernetes 在发现这个 StorageClass 关联的 PVC 与 PV 可以绑定在一起,但不要现在就立刻执行绑定操作(即:设置 PVC 的 VolumeName 字段),而是要等到第一个声明使用该 PVC 的 Pod 出现在调度器之后,调度器再综合考虑所有的调度规则,当然也包括每个 PV 所在的节点位置,来统一决定,这个 Pod 声明的 PVC,到底应该跟哪个 PV 进行绑定。通过这个延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度。3.部署StorageClass资源
- 所以我们需要创建对应的
StorageClass
对象:
# 06-local-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
这个
StorageClass
的名字,叫作 local-storage,也就是我们在 PV 中声明的,需要注意的是,在它的 provisioner
字段,我们指定的是 no-provisioner
。这是因为我们这里是手动创建的 PV,所以不需要动态来生成 PV.另外这个 StorageClass 还定义了一个
volumeBindingMode=WaitForFirstConsumer
的属性,它是 Local PV
里一个非常重要的特性,即:延迟绑定。通过这个延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度。- 现在我们来创建这个 StorageClass 资源对象
$ kubectl apply -f 06-local-storageclass.yaml
storageclass.storage.k8s.io/local-storage created
$ kubectl get sc
NAMEPROVISIONERRECLAIMPOLICYVOLUMEBINDINGMODEALLOWVOLUMEEXPANSIONAGE
local-storagekubernetes.io/no-provisionerDeleteWaitForFirstConsumerfalse3m59s
- 现在我们重新删除上面声明的 PVC 对象,重新创建:
$ kubectl delete -f 05-pvc-local.yaml
persistentvolumeclaim "pvc-local" deleted
$ kubectl apply -f 05-pvc-local.yaml
persistentvolumeclaim/pvc-local created
$ kubectl get pvc
NAMESTATUSVOLUMECAPACITYACCESS MODESSTORAGECLASSAGE
pvc-localPendinglocal-storage7s
我们可以发现这个时候,集群中即使已经存在了一个可以与 PVC 匹配的 PV 了,但这个 PVC 依然处于
Pending
状态,也就是等待绑定的状态,这就是因为上面我们配置的是延迟绑定,需要在真正的 Pod 使用的时候才会来做绑定。4.部署Pod资源
- 同样我们声明一个 Pod 来使用这里的 pvc-local 这个 PVC,资源对象如下所示:
# 07-pv-local-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pv-local-pod
spec:
volumes:
- name: example-pv-local
persistentVolumeClaim:
claimName: pvc-local #没有明确绑定到具体的节点
containers:
- name: example-pv-local
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: example-pv-local
- 直接创建这个 Pod:
$ kubectl apply -f 07-pv-local-pod.yaml
pod/pv-local-pod created
- 创建完成后我们这个时候去查看前面我们声明的 PVC,会立刻变成
Bound
状态,与前面定义的 PV 绑定在了一起:
$ kubectl get pvc
NAMESTATUSVOLUMECAPACITYACCESS MODESSTORAGECLASSAGE
pvc-localBoundpv-local5GiRWOlocal-storage3m53s
- 这时候,我们可以尝试在这个 Pod 的 Volume 目录里,创建一个测试文件
$ kubectl get po
NAMEREADYSTATUSRESTARTSAGE
pv-local-pod1/1Running064s
? kubectl exec -it pv-local-pod /bin/sh
# cd /usr/share/nginx/html
# echo "Hello from Kubernetes local pv storage" > index.html
#
然后,登录到 node1 这台机器上,查看一下它的
/data/k8s/localpv
目录下的内容,你就可以看到刚刚创建的这个文件:# 在node1节点上
[root@node1 ~]#ls /data/k8s/localpv
index.html
test.txt
[root@node1 ~]#cat /data/k8s/localpv/index.html
Hello from Kubernetes local pv storage
如果重新创建这个 Pod 的话,就会发现,我们之前创建的测试文件,依然被保存在这个持久化 Volume 当中:
$ kubectl delete -f 07-pv-local-pod.yaml
pod "pv-local-pod" deleted
$ kubectl apply -f 07-pv-local-pod.yaml
pod/pv-local-pod created
$ kubectl exec -it pv-local-pod /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# cat /usr/share/nginx/html/index.html
Hello from Kubernetes local pv storage
#
到这里就说明基于本地存储的 Volume 是完全可以提供容器持久化存储功能的。
对于 StatefulSet 这样的有状态的资源对象,也完全可以通过声明 Local 类型的 PV 和 PVC,来管理应用的存储状态。
测试结束。
关于我 我的博客主旨:
- 排版美观,语言精炼;
- 文档即手册,步骤明细,拒绝埋坑,提供源码;
- 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!
x2675263825 (舍得), qq:2675263825。
文章图片
微信公众号
《云原生架构师实战》
文章图片
csdn
https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421
文章图片
博客
www.onlyyou520.com
知乎
https://www.zhihu.com/people/foryouone
文章图片
语雀
https://www.yuque.com/books/share/34a34d43-b80d-47f7-972e-24a888a8fc5e?# 《云笔记最佳实践》
最后 好了,关于本次就到这里了,感谢大家阅读,最后祝大家生活快乐,每天都过的有意义哦,我们下期见!
【k8s|Kubernetes之本地存储】
推荐阅读
- docker|Docker中的网络详解
- docker|docker详解
- 云原生之K8S|【云原生之k8s】Kubeadm搭建K8S
- Kubernetes|Kubernetes 笔记 / 生产环境
- docker学习|docker overlay网络详解
- Docker容器+k8s|Docker网络详解
- kubernetes|k8s1.24.3搭建
- 云栖号技术分享|摆脱 AI 生产“小作坊”(如何基于 Kubernetes 构建云原生 AI 平台)
- 从零开始学k8s|【云原生 | 从零开始学Kubernetes】八、命名空间资源配额以及标签