博客:cbb777.fun
全平台账号:安妮的心动录
github: https://github.com/anneheartrecord
下文中我说的可能对,也可能不对,鉴于笔者水平有限,请君自辨。有问题欢迎大家找我讨论
为什么需要卷?
容器中的文件在磁盘上是临时存放 的,这给在容器中运行较重要的应用带来一些问题。 当容器崩溃或停止的时候会出现一个问题:此时容器状态未保存,因此在容器生命中期内创建或修改的文件都将丢失。
在崩溃期间,kubelet会以clean的状态重新启动容器,当多个容器在一个Pod中运行并且需要共享文件的时候,会出现另一个问题:无法做到跨容器访问文件
K8S支持很多类型的卷,Pod可以同时使用任意数目的卷类型,临时卷类型的生命周期和Pod相同,但持久卷可以比Pod更长。当Pod不存在的时候,K8S也会销毁临时卷,不会销毁持久卷。在容器重启期间,Pod中任何类型的卷都不会丢失
卷的核心是一个目录,其中可能存有数据,Pod中的容器可以访问该目录中的数据。所采用的特定的卷类型会决定该目录是如何形成的、使用何种介质保存数据以及目录中存放的内容 在使用卷的时候,.spec.volumes
字段中设置为pod提供的卷,并在.spec.containers[*].volumeMounts
字段中声明卷在容器中的挂载位置。容器中的进程看到的文件系统视图是由他们的容器镜像的初始内容以及挂载在容器中的卷所组成的。其中根文件系统同容器镜像的内容相吻合。任何在该文件系统下的写入操作,如果被允许的话都会影响到容器中进程访问文件系统时看到的内容
卷挂载在镜像的指定路径下,pod配置中的每个容器必须独立指定各个卷的挂载位置 卷不能挂载到其他卷之上(不过存在一种subPath的机制,可以实现卷上挂载卷)
卷类型
cephfs
cephfs
卷允许你将现存的cephFS
卷挂载到Pod
中,不像EmptyDir
那样会在Pod被删除的同时也删除,同一个CephFs
可以被多个写者挂载
apiVersion: v1 kind: Pod metadata: name: cephfs spec: containers: - name: cephfs-rw image: kubernetes/pause volumeMounts: - mountPath: "/mnt/cephfs" name: cephfs volumes: - name: cephfs cephfs: monitors: - 10.16.154.78:6789 - 10.16.154.82:6789 - 10.16.154.83:6789 # by default the path is /, but you can override and mount a specific path of the filesystem by using the path attribute # path: /some/path/in/side/cephfs user: admin secretFile: "/etc/ceph/admin.secret" readOnly: true
configMap
configMap
卷提供了向Pod
注入配置数据的方法,ConfigMap
对象中存储的数据可以被解析,然后被Pod
中运行的容器化应用使用。在使用ConfigMap
之前首先要创建它,并且ConfigMap
总是以readOnly
的模式挂载,容器以subPath卷挂载方式使用ConfigMap
时,将无法接收ConfigMap
的更新
apiVersion: v1 kind: Pod metadata: name: configmap-pod spec: containers: - name: test image: busybox:1.28 volumeMounts: - name: config-vol mountPath: /etc/config volumes: - name: config-vol config-Map: name: log -config items: - key: log_level path: log_level
emptyDir
当Pod
被分配到某个节点上的时候,emptyDir
会被创建,并且在Pod
在该节点上运行期间,卷一直存在。就像其名称表示的那样,卷最初是空的。尽管Pod
中的容器挂载emptyDir
卷的路径可能相同也可能不同,这些容器都可以读写emptyDir
卷中相同的文件。当pod
因为某些原因被从节点上删除的时候,emptyDir
卷中的数据也会被永久删除。容器崩溃并不会导致Pod
从节点上移除,因此容器崩溃期间emptyDir
的数据是安全的
emptyDir.medium
字段用来控制emptyDir
卷的存储位置,默认情况下,emptyDir
卷存储在该节点所使用的介质上;此处的介质可以是磁盘、SSD或者网络存储,可以将emptyDir.medium
设置为Memory
,以告诉K8S为你挂载tmpfs
emptyDir
的一些用途
为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行
在web服务器容器服务数据时,保存内容管理器容器获取的文件
apiVersion: v1 kind: Pod metadata: name: test -pd spec: containers: - image: registry.k8s.io/test -webserver name: test -container volumeMounts: - mountPath: /cache name: cache-volume volumes: - name: cache-volume emptyDir: sizeLimit: 500Mi
fc
fc(光纤通道)类型允许将现有的光纤通道快存储卷挂载到Pod中,可以使用卷配置中的参数来制定单个或者多个目标WWN
hostPath
hostPath
卷存在许多安全风险,最佳做法是应该尽可能避免HostPath
,当必须使用HostPath
的时候,它的范围应该仅限于所需的文件或者目录,并以只读的方式挂载
如果通过AdmissionPolicy
限制HostPath
对特定目录的访问,则必须要求volumeMounts
使用readOnly
挂载以使得策略生效
hostPath
卷能将主机结点文件系统上的文件或目录挂载到你的Pod
中,虽然这不是大多数Pod
需要的,但是它为一些应用程序提供了强大的逃生舱
hostPath的一些用法
运行一个需要访问Docker内部机制的容器,可以使用
hostPath
挂载
/var/lib/docker
路径
在容器中运行
cAdvisor
的时候,以
hostPath
方式挂载
/sys
允许
Pod
指定给定的
hostPath
在运行
Pod
之前是否应该存在,是否应该创建以及应该以什么方式存在
除了必须的path属性之外,你可以选择性地为hostPath卷指定type,支持的值如下
取值
行为
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。
DirectoryOrCreate
如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。
Directory
在给定路径上必须存在的目录。
FileOrCreate
如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。
File
在给定路径上必须存在的文件。
Socket
在给定路径上必须存在的 UNIX 套接字。
CharDevice
在给定路径上必须存在的字符设备。
BlockDevice
在给定路径上必须存在的块设备。
使用hostpath
类型的卷要小心,因为:
HostPath
卷可能会暴露特权,可用于容器逃逸或攻击集群的其他部分
具有相同配置(比如基于同一
PodTemplate
创建)的多个
Pod
会由于节点上文件的不同而在不同节点上有不同的行为
下层主机上创建的文件或目录只能由
root
用户写入,你需要在特权容器中以root身份运行进程,或者修改主机上的文件权限以便容器能够写入
hostPath
卷
apiVersion: v1 kind: Pod metadata: name: test -pd spec: containers: - image: registry.k8s.io/test -webserver name: test -container volumeMounts: - mountPath: /test -pd name: test -volume volumes: - name: test -volume hostPath: # 宿主上目录位置 path: /data # 此字段为可选 type : Directory
iscsi
iscsi
卷能将iscsi
卷挂载到你的pod
中。不像emptyDir
那样会在删除Pod
的同时也会被删除,issci
卷的内容在删除Pod
时会被保留,卷只是被卸载。这意味着iscsi
卷可以被预先填充数据,并且这些数据可以在Pod
之间共享。它的一个特点是它可以同时被多个用户以只读的方式挂载,这意味着你可以先用数据集填充卷,然后根据需要在尽可能多的Pod
上使用它。ISCSI
卷只能由单个使用者以读写模式挂载,不允许同时写入。
local
local
卷所代表的是某个被挂载的本地存储设备,例如磁盘、分区或者目录。
local
卷只能作为静态创建的持久卷,不支持动态配置
与hostPath
相比,local
卷是可持久化并且可移植的,无需手动将pod
调度到结点,系统通过查看PersistentVolume
的节点亲和性配置,就能了解卷的节点约束 然而local
卷仍然取决于底层结点的可用性,并不适合所有应用程序,如果结点变得不健康,那么local
卷也将变得不可被Pod
访问,使用它的Pod
将不能运行,使用local
卷的应用程序必须能够容忍这种可用性的降低,以及因底层磁盘的耐用性特征而带来的潜在的数据丢失风险。
apiVersion: v1 kind: PersistentVolume metadata: name: example-pv spec: capacity: storage: 100Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local -storage local : path: /mnt/disks/ssd1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - example-node
nfs
nfs
卷能将网络文件系统挂载到Pod
,不像emptyDir
那样会在删除Pod
的同时也会被删除,nfs
卷的内容在删除Pod
时会被保存,卷只是被卸载,这意味着nfs
卷可以被预先填充数据,并且这些数据可以在Pod之间共享
apiVersion: v1 kind: Pod metadata: name: test -pd spec: containers: - image: registry.k8s.io/test -webserver name: test -container volumeMounts: - mountPath: /my-nfs-data name: test -volume volumes: - name: test -volume nfs: server: my-nfs-server.example.com path: /my-nfs-volume readOnly: true
PVC
persistentVolumeClaim
卷用来将持久卷挂载到Pod中
rdb
rbd
卷允许将块设备卷挂载到Pod
上,不像EmptyDir
一样会被删除,rdb
卷的内容在删除Pod
的时候会被保存,卷只是被卸载。这意味着rdb卷可以被预先填充数据,并且这些数据可以在Pod
之间共享。
在使用RDB之前
必须先安装允许Ceph
RDB
的一个特性是它可以同时被多个用户以只读方式挂载,这意味着你可以用数据集预先填充卷,然后根据需要在即可能多的Pod
中使用卷RDB
卷只能由单个使用者以读写模式安装,不允许同时写入
secret
secret
卷用来给Pod
传递敏感信息,例如密码,你可以将Secret
存储在K8S API服务器上,然后以文件的形式挂载到Pod
中,不需要直接与K8S耦合。secret
卷由tmpfs
提供存储,它们不会被持久化
容器以
subPath
卷挂载方式使用
Secret
时,将无法接收Secret的更新
subPath
volumeMounts.subPath
属性可用于指定所引用的卷内的子路径,而不是根路径
apiVersion: v1 kind: Pod metadata: name: my-lamp-site spec: containers: - name: mysql image: mysql env: - name: MYSQL_ROOT_PASSWORD value: "rootpasswd" volumeMounts: - mountPath: /var/lib/mysql name: site-data subPath: mysql - name: php image: php:7.0-apache volumeMounts: - mountPath: /var/www/html name: site-data subPath: html volumes: - name: site-data persistentVolumeClaim: claimName: my-lamp-site-data
使用带有扩展环境变量的subPath
使用subPATHExpr
字段可以基于download API
环境变量来构造subPath
目录名
apiVersion: v1 kind: Pod metadata: name: pod1 spec: containers: - name: container1 env: - name: POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name image: busybox:1.28 command : [ "sh" , "-c" , "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ] volumeMounts: - name: workdir1 mountPath: /logs # 包裹变量名的是小括号,而不是大括号 subPathExpr: $(POD_NAME) restartPolicy: Never volumes: - name: workdir1 hostPath: path: /var/log /pods
CSI
CSI(Container Storage Interface 容器存储接口)为容器编排系统定义标准接口,以将任意存储系统暴露给它们的容器工作负载
一旦在K8S集群上部署了CSI兼容卷驱动程序,用户就可以使用CSI卷类型来接挂、挂载CSI驱动所提供的卷,csi卷可以在Pod中以三种方式使用:
持久卷
Persistent Volume
存储的管理是一个与计算实例的管理完全不同的问题,PersistenVolume
子系统为用户和管理员提供了一组API,将存储如何制备的细节从使用中抽离出来。为了实现这点,我们引入了两个新的API资源:PersistentVolume
和PersistentVolumeCliam
持久卷(PV)是集群中的一块存储,可以由管理员事先制备或者使用Storage Class
来动态制备。持久卷是集群资源,就像Node
也是集群资源一样,PV
持久卷和普通的Volume
一样也是通过卷插件来实现的,只是它们拥有独立于任何使用PV
的Pod
的生命周期。并且PV
中会标记存储的实现细节,比如NFS
、ISCSI
、云平台存储
持久卷申领(PVC)是用户对存储的请求,概念上和Pod
类似。Pod
会耗用NODE
资源,而PVC
会耗用PV
资源,Pod
可以请求特定数量的资源(CPU 内存),同样PVC
也可以请求特定的大小和访问模式
尽管PVC
允许用户消耗抽象的存储资源,常见的情况是针对不同的问题用户需要的是具有不同属性的PV
卷,集群管理员需要提供不同性质的PV
,并且PV之间
的差别不仅仅是大小和访问模式的区别,同时又不能将PV是如何实现的这些细节暴露给用户。为了满足这类需求,就有了存储类(SC)资源
PV和PVC的生命周期
PV是集群中的资源,PVC是对资源的请求,也被用来执行对资源的申明、检查,PV和PVC遵循以下生命周期:
制备
PV
的制备有两种方式:静态制备或动态制备
静态制备
集群管理员创建某些PV
卷,这些卷带有真实存储的细节,并且对集群可见,PV
卷对象存在于K8S API中,可供用户使用
动态制备
为PVC动态申领一个PV
,这是基于SC
实现的,PVC
必须请求某个SC
完成一个PV
的创建
绑定
用户创建一个带有特定存储容量和访问模式需求的PVC
,在动态场景下,这个PVC
可能已经创建完毕。主控节点中的Control Plane
监测新的PVC
对象,并寻找与之对应的PV
卷,并将二者绑定在一起。如果新的PVC
制备了PV
卷,那么PV
和PVC
则会直接绑定,一旦绑定关系建立,则PVC
的绑定就是排他性的,也就是说PV
和PVC
是一种一对一的映射,实现上使用ClaimRef
来记述PV
和PVC
间的双向绑定关系
如果找不到对应的PV
,那么PVC
将会无限的处于未绑定状态,当与之匹配的PV
可用的时候,则会立马绑定
使用
Pod
将PVC
当成存储卷来使用,集群会查询PVC
,找到对应 PV
,并为pod
挂载这个PV
。对于支持多种访问模式的卷,用户要在Pod
中以卷的实行使用PVC
中的指定期望的访问模式
一旦用户有了PVC
,并且PVC
已经绑定了PV
,那么PV
在用户所需要它的期间一直属于该用户。用户通过在Pod
的volumes
块中包含persistentVolumeClaim
节区来调度Pod
,访问所申领的PV
卷
保护
保护使用中的存储对象(Storage Object in Use Protection)是为了确保仍被Pod
使用的PVC
及其绑定的PV对象在系统中不会被删除,因为这样做可以会引起数据丢失 如果用户删除被某Pod使用的PVC
对象,PVC
不会被立即移除,PVC
的移除会被推迟,直到不再被任何Pod
使用,此外,如果管理员删除已绑定到某PVC
的PV
,PV
也不会被立刻删除,而是会推迟到该PV
不再绑定到PVC
回收
当用户不再使用PV
时,可以通过API
将PVC
删除,从而语序该资源被回收再利用。PV
对象的回收策略告诉集群,当前释放时该如何处理该数据卷,目前,支持的策略有Retained Recyceld 和 Deleted
保留(Retain)
回收策略Retain使得用户可以手动回收资源,当PVC被删除的时候,PV仍然存在,对应的数据卷会被视为Released,卷仍属于当前用户,不会被其他PVC使用,只能被用户手动回收
删除(Delete)
对于支持Delete的卷插件,删除动作会将PersistentVolume对象从K8S中移除,同时也会从外部基础设施中移除所关联的存储资产。动态制备的卷会继承其他的回收策略,默认为Delete
回收(Recycle)
目前已被废弃,建议的访问为动态制备
持久卷的类型
PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件:
fc - Fibre Channel (FC) 存储
hostPath - HostPath 卷 (仅供单节点测试使用;不适用于多节点集群;请尝试使用 local 卷作为替代)
iscsi - iSCSI (SCSI over IP) 存储
以下的持久卷已被弃用。这意味着当前仍是支持的,但是 Kubernetes 将来的发行版会将其移除。
cinder - Cinder(OpenStack 块存储)(于 v1.18
弃用 )
旧版本的 Kubernetes 仍支持这些“树内(In-Tree)”持久卷类型:
photonPersistentDisk - Photon 控制器持久化盘。(从 v1.15 版本开始将
不可用 )
flocker - Flocker 存储 (v1.25 之后
不可用 )
每个PV对象都包含spec
部分和status
部分,分别对应卷的规约和状态,PV对象的名称必须是合法的DNS子域名
apiVersion: v1 kind: PersistentVolume metadata: name: pv003 spec: capacity: # 容量 storage: 5Gi volumeMode: Filesystem # 卷模式 accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: slow mountOptions: - hard - nfsvers=4.1 nfs: path: /tmp server: 172.17.0.2
卷模式:针对PV持久卷,K8S支持两种volumeMode,FileSystem
和Block
,这是一个可选的参数,如果被省略则默认为FileSystem
为Filesystem的卷会被Pod挂载(Mount)到某个目录
Block的卷则作为原始块设备来使用,卷上没有任何文件系统,pod和卷之间不存在文件系统层
访问模式
ReadWriteOnce 卷可以被一个结点以读写方式挂载
ReadOnlyMany 卷可以被多个结点以只读方式挂载
ReadWriteMany 卷可以被多个结点以读写方式挂载
ReadWriteOncePod 卷可以被单个Pod以读写方式挂载
每个卷同一时刻只能以一种访问模式挂载,即使该卷能够支持多种访问模式。
类
每个PV可以属于某个类,通过将其storageClassName设置为某个StorageClass的名称来制定,特定的PV卷只能绑定到请求该类存储卷的PVC申领。未设置storageClassName的PV卷没有类设定,只能绑定到哪些没有制定StorageClass的PVC
PVC
每个PVC对象都有spec和status部分,分别对应申领的规约和状态,PVC对象的名称必须是合法的DNS子域名
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 8Gi storageClassName: slow selector: matchLabels: release: "stable" matchExpressions: - {key: environment, operator: In, values: [dev]}
PVC和PV具有同样的访问模式,卷模式
PVC可以通过设置storageClassName属性设置StorageClass的名称来请求特定的存储类,只有所请求的类的PV卷,即storageClassName值与PVC设置相同的PV卷,才能被绑定到PV PVC不一定需要请求某个SC,如果PVC的SC字段为空,则被视为要请求的是没有设置存储类的PV卷,这类PVC只能绑定到未设置SC的PV,这种PV在系统中不会被删除,因为这样做可能会引起数据丢失
使用PVC
apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: myfrontend image: nginx volumeMounts: - mountPath: "/var/www/html" name: mypd volumes: - name: mypd persistentVolumeClaim: claimName: myclaim
image.png
临时卷
有些应用程序需要额外的存储,但是并不关心数据在重启之后是否仍然可用,例如缓存服务经常受限于内存大小,而且可以将不常用的数据转移到比内存慢的存储中,对总体性能的影响不大 另外有些应用程序需要以文件形式注入的只读数据,比如配置数据和密钥
临时卷就是为此类用例设计的,因为卷会遵从pod的生命周期,和pod一起创建和删除,所以停止和启动pod时,不会受持久卷在何处可用的限制,临时卷在pod规约中以内联的方式定义 K8S为了不同的用途,支持几种不同类型的临时卷
emptyDir:pod启动时为空,存储空间来自本地的kubelet根目录或者内存
configMap downwardAPI secret:将不同类型的K8S数据注入到Pod中
CSI临时卷: 类似于前面的卷类型,但由指定的CSI驱动程序提供
存储类
StorageClass为管理员提供了描述存储"类"的方法,不同的类型可能会映射到不同的服务质量等级或者备份策略,或是由集群管路员制定的任意策略,K8S本身并不清楚各种类代表的具体含义,在这里,类的概念类似于"配置文件"
SC资源
每个SC都会包含provisioner parameters 和 recalimPolicy字段,这些字段会在SC需要动态制备PV的时候用到,SC的命名很重要,用户使用这个命名来请求生成一个特定的类,当创建SC对象的时候,管理员设置SC对象的命名和其他参数,一旦创建了对象就不能再更新了
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/aws-ebs parameters: type : gp2 reclaimPolicy: Retain allowVolumeExpansion: true mountOptions: - debug volumeBindingMode: Immediate
卷绑定模式
volumeBindingMode字段控制了卷绑定和动态制备应该发生在什么时候,如果没有设置则默认使用Immediate
模式,这个模式表示一旦创建了PVC,则完成了卷绑定和动态制备,对于由于拓扑限制而非集群所有节点可达的存储后端,PV会在不知道Pod调度要求的情况下绑定或者制备 可以通过WaitForFirstConsumer
模式来解决该问题,该模式将延迟PV的制备和绑定,直到使用该PVC的Pod被创建,PV会根据Pod调度约束指定的拓扑来选择或制备