目录
一. 存储挂载
- 先提出问题,或者存储挂载出现的原因: 通过k8s部署应用时,可能会生成或者读写一些配置文件,在应用部署完成后运行过程中也可能需要生成或者读写一些文件或数据,进而引出问题
- 这些文件或数据如何存储的
- 这些文件或数据如何读取的
- 如何做到持久化,即使k8s把当前pod杀掉重新拉起,怎样才能读取上一个版本生成的文件数据
- 我理解的卷挂载就是将文件或数据保存到容器外部指定的存储资源中例如主机文件系统、网络存储、云存储等,然后容器内部通过指定的方式例如路径,http请求等读取指定位置的文件或数据,将文件或数据挂载到容器内部的指定位置,进而解决了以下问题:
- 存储:卷挂载允许我们将文件或数据存储在容器之外,可以是主机节点的文件系统、网络存储(如 NFS)、云存储等。确保数据的持久性和可靠性,使得即使 Pod 被重新调度或重启,数据仍然可以被访问。
- 读取:通过将卷挂载到容器内的指定路径,应用程序可以直接读取和写入这些文件或数据。应用程序可以像访问本地文件一样访问挂载的文件。
- 持久化:通过卷挂载,即使 Pod 被杀掉重新拉起,以前生成的文件或数据也可以保留。因为卷通常与持久化存储关联,所以即使 Pod 终止并被替换,新 Pod 仍然可以挂载相同的卷,并继续访问之前生成的文件或数据
- 使用Docker时就有数据卷的概念,存在容器删除数据也被删除的问题,想要持久化使用数据,需要把主机上的目录挂载到Docker中,在K8S中默认情况下如果Pod删除,数据卷也会一起删除,k8s中提出了卷的概念,是docker数据卷的扩展, 参考博客
- 简述一下怎么使用卷挂载一个挂载ymal示例与解释:
- 在编写部署应用pod的yaml时,或者通过deployment部署编写pod模板时,有几个属性:
- 首先通过"spec.volumes"属性声明多个卷,指定卷名称,卷类型
- 然后在容器中通过"spec.containers.volumeMounts"下的name指定使用spec.volumes中的哪个卷,通过"spec.containers.volumeMounts"下的mountPath指定将该卷挂载到容器的哪个位置
- 后续容器应用在运行过程中,如果spec.containers.volumeMounts.mountPath位置需要读取数据,就会通过对应卷指定的方式到指定位置读取
apiVersion: v1
kind: Pod #类型是Podmetadata:labels:name: redis
role: master #定义为主redisname: redis-master
spec:volumes:#先声明需要的卷名称,卷类型-name: redis-data #卷名称emptyDir:{}#卷类型宿主机挂载点 -name: volumn-sls-mydemo #卷名称emptyDir:{}#卷类型-name: acm
secret:secretName: <ENVS>-acm-<APP_NAME>-secret
-name: config-volume
configMap:name: redis-config
containers:-name: master
image: redis:latest
env:#定义环境变量-name: MASTER
value:"true"ports:#容器内端口-containerPort:6379volumeMounts:#容器内挂载点,将指定的卷挂载到容器的指定位置-name: redis-data #挂载哪个卷,要与pod中volumes中的对应mountPath: /data #挂载的路径,也就是当前容器内部需要挂载出去的路径-name: volumn-sls-mydemo
mountPath: /home/app/log
-name: acm
mountPath:"/config"readOnly:true-name: config-volume
mountPath: /usr/local/etc/redis/
volumes卷的分类
- volumes卷是有类型区分的,根据功能不同可以分为: 配置信息相关的, 临时存储相关的, 持久化存储相关
- 配置信息相关的:(实际这两种是配置,因为配置也可以挂载)
- Secret: 密码相关的
- ConfigMap: 配置相关的,例如将存放到配置中心的文件做成ConfigMap放到k8s管理
- 两者区别Secret中保存的信息会使用base64编码,而ConfigMap不会
- 临时存储相关的:
- emptyDir: 相当于分配一个当前pod上的空目录
- hostPath: 挂载一个主机的目录
- 为什么这两种称为临时存储: 如果当前pod宕机,在其它节点拉起,由于其它节点上没有对应的目录,可能访问不到数据
- 持久化存储相关的:
- glusterFS:
- nfs:
- cephfs:
- …
- 也可以这样区分: K8S中适配了各种存储系统:
- 本地存储,非持久性存储: EmptyDir, HostPath
- 网络连接性存储:
SAN:iSCSI、ScaleIO Volumes、FC (Fibre Channel)
NFS:nfs,cfs
- 分布式存储: Glusterfs, RBD (Ceph Block Device), CephFS, Portworx Volumes, Quobyte Volumes
- 云端存储: GCEPersistentDisk, AWSElasticBlockStore, AzureFile, AzureDisk, Cinder (OpenStack block storage), VsphereVolume, StorageOS
- 自定义存储: FlexVolume
- type属性
- subPath属性: 有时,在单个 Pod 中共享卷以供多方使用是很有用的。
volumeMounts.subPath
属性可用于指定所引用的卷内的子路径,而不是其根路径(注意点configMap或Secret使用subPath子路径挂载时不支持热更新)
1. EmptyDir 示例
- EmptyDir是跟随pod的,如果pod删除对应的文件也会被删除,所以称为临时挂载(容器重启不会丢失)
- 编写一个创建 redis 的pod yaml 解释 EmptyDir使用示例,
- 在下方的yaml中,通过"volumes"设置卷,通过"volumeMounts.mountPath"指定挂载点,通过"volumeMounts.name"指定使名称,指定使用哪个挂载点"redis-data"
- 然后执行"kubectl create -f 当前yaml文件名称.yaml" 应用该yaml
apiVersion: v1
kind: Pod #类型是Podmetadata:labels:name: redis
role: master #定义为主redisname: redis-master
spec:volumes:#containers容器中使用挂载时,指定挂载类型-name: redis-data #跟下面volumeMounts设置的挂载的名称对应emptyDir:{}#宿主机挂载点 containers:-name: master
image: redis:latest
env:#定义环境变量-name: MASTER
value:"true"ports:#容器内端口-containerPort:6379volumeMounts:#容器内挂载点-mountPath: /data
name: redis-data #必须有名称
- 当上方yaml应用创建pod成功后,由于内部使用"volumeMounts"设置了挂载,会发现:
- 在宿主机上的访问路径为/var/lib/kubelet/pods/< pod uid>/volumes/kubernetes.io~empty-dir/redis-data,如果在此目录中创建删除文件,都将对容器中的/data目录有影响,
- 如果删除Pod,文件将全部删除,即使是在宿主机上创建的文件也是如此,
- 在宿主机上删除容器则k8s会再自动创建一个容器,此时文件仍然存在
2. hostPath 示例
- 以时间问题为例: 容器内部用容器的时间,但是我们要取机器的时间,可以吧机器的时间挂载到对应的容器上
apiVersion: v1
kind: Pod
metadata:name: test-pd
spec:containers:-image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:-mountPath: /etc/localtime #路径name: test-volume
volumes:-name: test-volume
hostPath:#通过hostPath获取宿主机上的数据挂载到对应的挂载点path: /usr/share/zoneinfo/Asia/Shanghai
type: Directory #可选字段,当前表示是个文件夹,如果没有会报错
3. HostDir 示例
- 在宿主机上指定一个目录,挂载到Pod的容器中,其实跟上面的写法不尽相同,这里只截取不同的部分,当pod删除时,本地仍然保留文件
volumes:-name: redis-data #跟volumeMounts中指定的的名称对应hostPath:path: /data #宿主机挂载点
4. NFS 网络数据卷示例(持久化卷)
- k8s支持不同类型的挂载,并且可以挂载多个,分为临时与持久的,临时卷类型的生命周期与 Pod 相同,持久卷可以比 Pod 的存活期长,不会销毁 持久卷(容器重启不会造成临时数据的丢失,因为临时卷是在pod的)
- 使用卷时, 在
.spec.volumes
字段中设置为 Pod 提供的卷,并在.spec.containers[*].volumeMounts
字段中声明卷在容器中的挂载位置。 - 安装NFS
# 在任意机器
yum install-y nfs-utils
#执行命令 vi /etc/exports,创建 exports 文件,文件内容如下:echo"/nfs/data/ *(insecure,rw,sync,no_root_squash)"> /etc/exports
#/nfs/data 172.26.248.0/20(rw,no_root_squash)# 执行以下命令,启动 nfs 服务;创建共享目录mkdir-p /nfs/data
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server
exportfs -r#检查配置是否生效
exportfs
# 输出结果如下所示
/nfs/data /nfs/data
- 设置挂载的yaml示例
apiVersion: v1
kind: Pod
metadata:name: nfs-web
spec:containers:-name: web
image: nginx
imagePullPolicy: Never #如果已经有镜像,就不需要再拉取镜像ports:-name: web
containerPort:80hostPort:80#将容器的80端口映射到宿主机的80端口volumeMounts:-name: nfs #指定名称必须与下面一致mountPath:"/usr/share/nginx/html"#容器内的挂载点volumes:-name: nfs #指定名称必须与上面一致nfs:#nfs存储server: 192.168.66.50 #nfs服务器ip或是域名path:"/test"#nfs服务器共享的目录
- 因为映射的是代码目录,在/test目录中创建index.html文件后,这个文件也将在容器中生效,当Pod删除时,文件不受影响,实现了数据持久化
- 扩展-NFS文件同步
#服务器端防火墙开放111、662、875、892、2049的 tcp / udp 允许,否则远端客户无法连接。#安装客户端工具
yum install-y nfs-utils
#执行以下命令检查 nfs 服务器端是否有设置共享目录# showmount -e $(nfs服务器的IP)
showmount -e172.26.165.243
# 输出结果如下所示
Export list for172.26.165.243
/nfs/data *
#执行以下命令挂载 nfs 服务器上的共享目录到本机路径 /root/nfsmountmkdir /root/nfsmount
# mount -t nfs $(nfs服务器的IP):/root/nfs_root /root/nfsmount#高可用备份的方式mount-t nfs 172.26.165.243:/nfs/data /root/nfsmount
# 写入一个测试文件echo"hello nfs server"> /root/nfsmount/test.txt
#在 nfs 服务器上执行以下命令,验证文件写入成功cat /root/nfsmount/test.txt
二. 持久化存储的 PV&PVC&StorageClass
- 什么是PV, PVC
- 因为在使用不同的存储时,挂载方式也不同,原始volumes挂载编写起来比较繁琐,为了方便开发,提供了专门的挂载编写方式也就是PV, PVC,内部封装存储和如何供应的细节, 实际就是在集群中定义了一块存储,和这块存储的使用方式的API官方文档,并且PV拥有独立于Pod的生命周期
- PersistentVolumeClaim: 简称PVC, 持久化卷申,与PV是一一对应的,可以看成时一个接口,容器内部在读取卷进行挂载时,只需要调用这个接口即可
- Persistent Volume: 简称PV,可以看成对应PVC接口的实现,比通过volumes更详细的定义了卷的细节,除了可以指定卷的类型比如时nfs还是ceph等等以外,还可以设置卷的容量, 回收策略,共享地址,读写模式等等
- 还可以这样理解: 开发人员并不关注卷底层实现,存储介质,所以只编写pvc, pod中通过pvc去挂载,然后由运维人员编写底层的pv, pvc选中对应的pv即可
- 如下示例
- 编写pv指定实际存储方式为hostPath,容量10G,读写模式ReadWriteOnce等,并给当前pv加上标签my-pv
- 编写pvc,进行指定配置后,通过matchLabels选中指定pv进行绑定也就是my-pv
- pod中首先通过volumes.persistentVolumeClaim选中指定pvc,并命名,然后再pod的container指定容器中设置通过pvc进行挂载
# 步骤1: 创建持久卷(PV)apiVersion: v1
kind: PersistentVolume
metadata:name: my-pv # PV 的名称labels:pv-type: my-pv # 设置 PV 的 labelspec:capacity:storage: 10Gi # 容量大小accessModes:- ReadWriteOnce # 访问模式persistentVolumeReclaimPolicy: Retain # PV 回收策略hostPath:path: /data/my-pv # 主机路径,用于存储 PV 数据# 步骤2: 创建持久卷声明(PVC)apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: my-pvc # PVC 的名称spec:accessModes:- ReadWriteOnce # 访问模式selector:# 设置 PVC 的 selectormatchLabels:pv-type: my-pv # 匹配 PV 的 labelresources:requests:storage: 5Gi # 所需的存储容量# 步骤3: 创建使用 PV 和 PVC 的 PodapiVersion: v1
kind: Pod
metadata:name: my-pod # Pod 的名称spec:containers:-name: my-app
image: nginx
volumeMounts:-mountPath: /data # 挂载路径name: my-volume
volumes:-name: my-volume
persistentVolumeClaim:claimName: my-pvc # 关联的 PVC
PV 的创建与使用细节
- PV与PVC直接的关系
- pv没有namespace名称空间概念,而pvc有namespace名称空间的概念
- pv和pvc一一对应绑定的
- PV的分类
- 静态模式:管理员手工创建许多PV,在定义PV时需要将后端存储的特性进行手动设置
- 动态模式:集群管理员无须手工创建PV,而是通过StorageClass的设置对后端存储进行描述,标记为某种 “类型(Class)”。此时要求PVC对存储的类型进行声明,系统将自动完成PV的创建及PVC的绑定。PVC可以声明Class为"",说明该PVC禁止使用动态模式
- PV的回收策略: 当pod资源被删除时,其相关pv和数据如何操作?kubernetes通过persistentVolumeReclaimPolicy字段进行设置:
- Delete: 数据和pv都会删除
- Recyle: (已废弃)
- Retain: 数据和pv都不动
- 第一步: 搭建一个NFS共享服务器,共享目录为 /data/share
- 创建pv ,并添加NFS共享服务器地址和目录
apiVersion: v1
kind: PersistentVolume #指定为PV类型metadata:name: zhaobei #指定PV的名称labels:#指定PV的标签release: zhaobei
spec:capacity:storage: 1Gi #指定PV的容量accessModes:- ReadWriteOnce #指定PV的访问模式,简写为RWO,只支持挂在1个Pod的读和写persistentVolumeReclaimPolicy: Delete #指定PV的回收策略,Recycle表示支持回收,回收完成后支持再次利用nfs:#指定PV的存储类型,本文是以nfs为例path: /data/share/test-pv #共享的地址server: 10.9.175.79 #服务器地址
StorageClass 动态供应的创建与使用细节
- 在pv,pvc中开发人员并不关注卷底层实现,存储介质,所以只编写pvc, pod中通过pvc去挂载,然后由运维人员编写底层的pv, pvc选中对应的pv即可, 但是每出现一个pvc申请,运营可能就需要配合写一个真实的pv,也比较繁琐,引出了一块通过pvc自动创建pv的StorageClass, 运维只需要针对不同的存储介质编写StorageClass即可,在使用时StorageClass配合pvc可以自动创建对应的pv, 官方文档:StorageClass
- k8s提供了一套可以自动创建PV的机制,即:Dynamic Provisioning。而这个机制的核心在于StorageClass这个API对象,内部会根据用户提交的PVC中定义的存储类型,Volume的大小,存储插件,即存储制备器,找到一个对应的StorageClass,根据StorageClass声明的存储插件,进而创建出需要的PV
- 使用原理: 比如存储后端使用的是 nfs,那么需要使用到一个 nfs-client 的自动配置程序 Provisioner(制备器), 这个程序会通过已经创建号的的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV
- 自动创建的 PV 以$ {namespace}-$ {pvcName}-${pvName}这样的命名格式创建在 NFS 服务器上的共享数据目录中
- 而当这个 PV 被回收后会以archieved-$ {namespace}-$ {pvcName}-${pvName}这样的命名格式存在 NFS 服务器上
- 每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到
- 简单来说就是运维针对不同的持久化存储系统编写StorageClass, 开发人员在挂载时只需要编写pvc,通过pvc指定使用的StorageClass,由StorageClass自动创建pv即可
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: standard
provisioner: kubernetes.io/aws-ebs #设置指定存储系统供应商的名字(要与部署的存储系统中env.PROVISIONER_NAME这个key的value相同?)parameters:#对存储类驱动相关设置(由对应存储系统供应商提供支持的)type: gp2
reclaimPolicy: Retain
allowVolumeExpansion:truemountOptions:- debug
volumeBindingMode: Immediate
1. 存储制备器
- 每个StorageClass都有一个制备器Provisioner,用来决定使用哪个卷插件制备 PV 该字段必须指定,并且不限于下方列出的 “内置” 制备器,还可以运行和指定外部制备器,这些独立的程序遵循由 Kubernetes 定义的 规范。 外部供应商的作者完全可以自由决定他们的代码保存于何处、打包方式、运行方式、使用的插件(包括 Flex)等。 代码仓库 kubernetes-sigs/sig-storage-lib-external-provisioner 包含一个用于为外部制备器编写功能实现的类库。你可以访问代码仓库 kubernetes-sigs/sig-storage-lib-external-provisioner 了解外部驱动列表。例如NFS 没有内部制备器,但可以使用外部制备器。 也有第三方存储供应商提供自己的外部制备器
2. 允许卷扩展
- PersistentVolume 可以配置为可扩展,将此功能设置为 true 时,允许用户通过编辑相应的 PVC 对象来调整卷大小,当下层 StorageClass 的 allowVolumeExpansion 字段设置为 true 时,以下类型的卷支持卷扩展
3. 挂载选项
- 由 StorageClass 动态创建的 PersistentVolume 将使用类中 mountOptions 字段指定的挂载选项。如果卷插件不支持挂载选项,却指定了该选项,则制备操作会失败。 挂载选项在 StorageClass 和 PV 上都不会做验证,所以如果挂载选项无效,那么这个 PV 就会失败
4. 回收策略
- 由 StorageClass 动态创建的 PersistentVolume 会在类的 reclaimPolicy 字段中指定回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy,它将默认为 Delete。通过 StorageClass 手动创建并管理的 PersistentVolume 会使用它们被创建时指定的回收政策。
5. 卷绑定模式
- volumeBindingMode 字段控制了卷绑定和动态制备应该发生在什么时候。默认情况下,Immediate 模式表示一旦创建了 PersistentVolumeClaim 也就完成了卷绑定和动态制备。 对于由于拓扑限制而非集群所有节点可达的存储后端,PersistentVolume 会在不知道 Pod 调度要求的情况下绑定或者制备。
- 集群管理员可以通过指定 WaitForFirstConsumer 模式来解决此问题。 该模式将延迟 PersistentVolume 的绑定和制备,直到使用该 PersistentVolumeClaim 的 Pod 被创建。 PersistentVolume 会根据 Pod 调度约束指定的拓扑来选择或制备。这些包括但不限于 资源需求、 节点筛选器、 pod 亲和性和互斥性、 以及污点和容忍度。
- 以下插件支持动态供应的 WaitForFirstConsumer 模式:
- AWSElasticBlockStore
- GCEPersistentDisk
- AzureDisk
- 以下插件支持预创建绑定 PersistentVolume 的 WaitForFirstConsumer 模式:
- 上述全部
- Local
6. 允许的拓扑结构
- 当集群操作人员使用了 WaitForFirstConsumer 的卷绑定模式, 在大部分情况下就没有必要将制备限制为特定的拓扑结构。如果需要的可以使用 allowedTopologies。
- 下方示例描述了如何将供应卷的拓扑限制在特定的区域,在使用时应该根据插件 支持情况替换 zone 和 zones 参数
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: standard
provisioner: kubernetes.io/gce-pd
parameters:type: pd-standard
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:-matchLabelExpressions:-key: failure-domain.beta.kubernetes.io/zone
values:- us-central1-a
- us-central1-b
7. 创建StorageClass指定provisioner示例
# 创建NFS资源的StorageClassapiVersion: storage.k8s.io/v1
kind: StorageClass # 创建StorageClassmetadata:name: managed-nfs-storage
provisioner: qgg-nfs-storage #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致parameters:archiveOnDelete:"false"# 创建NFS provisionerapiVersion: apps/v1
kind: Deployment # 部署nfs-client-provisionermetadata:name: nfs-client-provisioner
labels:app: nfs-client-provisioner
namespace: default #与RBAC文件中的namespace保持一致spec:replicas:1selector:matchLabels:app: nfs-client-provisioner
strategy:type: Recreate
selector:matchLabels:app: nfs-client-provisioner
template:metadata:labels:app: nfs-client-provisioner
spec:serviceAccountName: nfs-client-provisioner # 指定serviceAccount!containers:-name: nfs-client-provisioner
image: hub.kaikeba.com/java12/nfs-client-provisioner:v1 #镜像地址volumeMounts:# 挂载数据卷到容器指定目录-name: nfs-client-root
mountPath: /persistentvolumes
env:-name: PROVISIONER_NAME # 配置provisioner的Namevalue: qgg-nfs-storage # 确保该名称与 StorageClass 资源中的provisioner名称保持一致-name: NFS_SERVER #绑定的nfs服务器value: 192.168.66.13
-name: NFS_PATH #绑定的nfs服务器目录value: /opt/k8s
volumes:# 申明nfs数据卷-name: nfs-client-root
nfs:server: 192.168.66.13
path: /opt/k8s
PVC 创建与使用细节
- 在用户定义好PVC后,系统将根据PVC对存储资源的请求 (存储空间和访问模式)在已存在的PV中选择一个满足PVC要求的PV,一旦找到,就将该PV与用户定义的PVC进行绑定,然后用户的应用就可以使用这个PVC了。如果系统中没有满足PVC要求的PV,PVC则会无限期处于Pending状态,直到等到系统管理员创建了一个符合要求的PV。PV一旦绑定在某个PVC上,就被这个PVC独占,不能再与其他PVC进行绑定了。在这种情况下,当PVC申请的存储空间比PV的少时,整个PV的空间都能够为PVC所用,可能会造成资源的浪费。如果资源供应使用的是动态模式,则系统在PVC找到合适的StorageClass后,将会自动创建PV并完成PVC的绑定
- PVC 中可以通过"spec.accessModes"设置访问模式,支持:
- ReadWriteOnce: 卷可以被一个节点以读写的方式挂载
- ReadOnlyMany: 卷可以被多个节点以只读的方式挂载
- ReadWriteMany: 卷可以被多个节点以读写的方式挂载
- 先编写pvc申请yaml
apiVersion: v1
kind: PersistentVolumeClaim #类型,当前表示持久化申请metadata:name: nginx-pvc #名字namespace: default
labels:app: nginx-pvc
spec:storageClassName: my-nfs-storage #存储类的名字accessModes:- ReadWriteOnce
resources:requests:storage: 50m
- 需要挂载设置的pod的yaml示例,需要挂载的pod中通过persistentVolumeClaim属性指定使用哪个持久化申请,通过指定的持久化申请获取到挂载
Pod 使用时通过volume的定义,将PVC挂载到容器内的某个路径进行使用。volume的类型为persistentVoulumeClaim,在容器应用挂载了一个PVC后,就能被持续独占使用。不过,多个Pod可以挂载同一个PVC,应用程序需要考虑多个实例共同访问一块存储空间的问题
apiVersion: v1
kind: Pod
metadata:name:"nginx-pvc"namespace: default
labels:app:"nginx-pvc"spec:containers:-name: nginx-pvc
image:"nginx"ports:-containerPort:80name: http
volumeMounts:-name: localtime
mountPath: /etc/localtime
-name: html
mountPath: /usr/share/nginx/html
volumes:-name: localtime
hostPath:path: /usr/share/zoneinfo/Asia/Shanghai
-name: html
persistentVolumeClaim:#通过该注解设置使用哪个持久化申请claimName: nginx-pvc #使用的申请书的名字restartPolicy: Always
版权归原作者 苹果香蕉西红柿 所有, 如有侵权,请联系我们删除。