持久卷 PersistentVolume
PersistentVolume(PV)是集群中一块存储资源,由管理员主动创建提供或使用 StorageClass 动态提供。它与节点资源一样,不属于任何命名空间,有着自己独立的生命周期。而用户则通过 PersistentVolumeClaim (PVC) 来申请所需的存储资源。
1. 生命周期
在 Kubernetes 集群中,PV 作为存储资源存在,Pod 通过 PVC 来使用 PV。PV 和 PVC 之间的交互过程有着自己的生命周期,这个生命周期分为5个阶段:
- 供应(Provisioning):即 PV 的创建,可以直接创建 PV(静态方式),也可以使用 StorageClass(动态方式);
- 绑定(Binding):将 PV 分配给 PVC;
- 使用(Using):Pod 通过 PVC 使用该 Volume;
- 释放(Releasing):Pod 释放 Volume 并删除 PVC;
- 回收(Reclaiming):回收 PV,可以保留 PV 以便下次使用,也可以直接从云存储中删除;
根据上述的5个阶段,存储卷的存在下面的 4 种状态:
- Available:可用状态,处于此状态表明 PV 以及准备就绪了,可以被 PVC 使用了;
- Bound:绑定状态,表明 PV 已被分配给了 PVC;
- Released:释放状态,表明 PVC 解绑 PV,但还未执行回收策略;
- Failed:错误状态,表明 PV 发生错误;
1.1 供应(Provisioning)
供应是为集群提供可用的存储卷,在 Kubernetes 中有两种持久化存储卷的提供方式:静态或者动态,两者区别在于。
1.1.1 静态 (Static)
PV 是由 Kubernetes 的集群管理员创建的,代表真实的存储,可被 Pod 作为真实存储使用。在静态供应的情况下,由集群管理员预先创建 PV,开发者创建 PVC 和 Pod,Pod 通过 PVC 使用 PV 提供的存储。静态供应方式的过程如下图所示:
[图片上传中...(image-5c62e8-1597076467904-3)]
<figcaption></figcaption>
2.1.2 动态(Dynamic)
对于动态的提供方式,当管理员创建的静态 PV 都不能够匹配用户的 PVC 时,集群会尝试自动为 PVC 提供一个存储卷,这种提供方式基于 StorageClass。在动态提供方面,PVC 需要请求一个存储类,但此存储类必须由管理员预先创建和配置。集群管理员需要在 API-Server 中启用 DefaultStorageClass 控制器。动态供应过程如下图所示:
[图片上传中...(image-7fd658-1597076467904-2)]
<figcaption></figcaption>
1.2 绑定(Binding)
Kubernetes 会动态的将 PVC 与可用的 PV 的进行绑定,如果一个 PV 曾经动态供给到了一个新的 PVC,那么 PVC 绑定就是专属的。另外,用户总是能得到它们所要求的存储,但是 volume 可能超过它们的请求。一旦绑定了,PVC绑定就是专属的,无论它们的绑定模式是什么。
如果没有匹配的 PV,那么 PVC 会无限期的处于未绑定状态,直到存在匹配的 PV。比如,就算集群中存在很多的 50G 的 PV,需要 100G 容量的 PVC 也不会匹配满足需求的 PV,直到集群中有 100G 的 PV 时,PVC 才会被绑定。PVC 会基于下面的条件绑定PV,如果下面的条件同时存在,则选择符合所有要求的 PV 进行绑定:
如果PVC指定了存储类,则只会绑定指定了同样存储类的PV;
如果PVC设置了选择器,则选择器去匹配符合的PV;
如果没有指定存储类和设置选取器,PVC会根据存储空间容量大小和访问模式匹配符合的PV。
1.3 使用(Using)
Pod 把 PVC 作为卷来使用,集群会通过 PVC 查找绑定的 PV,并将其挂接至 Pod。对于支持多种访问方式的卷,用户在使用 PVC 作为卷时,可以指定需要的访问方式。一旦用户拥有了一个已经绑定的 PVC,被绑定的 PV 就归该用户所有。用户能够通过在 Pod 的存储卷中包含的 PVC,从而访问所占有的 PV。
1.4释放(Releasing)
当用户完成对卷的使用时,可以通过请求 API-Server 来删除 PVC,删除后还可以重新申请,在删除 PVC 后对应的持久化存储卷被视为“被释放”,但这时还不能给其他的 PVC 使用。之前的 PVC 数据还保存在卷中,要根据策略来进行后续处理。
1.5 回收(Reclaiming)
PV 的回收策略向集群阐述了在 PVC 释放卷时,应如何进行后续工作。目前可以采用三种策略:保留,回收或者删除。保留策略允许重新申请这一资源。在 PVC 能够支持的情况下,删除策略会同时删除卷以及AWS EBS/GCE PD或者Cinder卷中的存储内容。如果插件能够支持,回收策略会执行基础的擦除操作(rm -rf /thevolume/*),这一卷就能被重新申请了。
1.5.1 保留
保留回收策略允许手工回收资源。当 PVC 被删除,PV 将继续存储现有的数据,虽然存储卷被处于已释放的状态,但是它对于其他的 PVC 仍是不可用的。管理员能够通过下面的步骤手工回收存储卷:
删除 PV:在 PV 被删除后,在外部设施中相关的存储资产仍然还在;
手工删除遗留在外部存储中的数据;
手工删除存储资产,如果需要重用这些存储资产,则需要创建新的 PV;
1.5.2 循环
此策略将会被遗弃,建议后续使用动态供应的模式。
循环回收会在存储卷上执行基本擦除命令 rm -rf /thevolume/
,使数据对于新的 PVC 可用。
1.5.3 删除
对于支持删除回收策略的存储卷插件,删除即会从 Kubernetes 中移除 PV,也会从相关的外部设施中删除存储资产,例如 AWS EBS, GCE PD, Azure Disk 或者 Cinder 存储卷。
2. 创建持久存储卷
在创建持久卷 PV 时需要指定实际存储类型,类型是作为插件实现的,目前支持以下插件:
- GCEPersistentDisk
- AWSElasticBlockStore
- AzureFile
- AzureDisk
- CSI
- FC (Fibre Channel)
- FlexVolume
- Flocker
- NFS
- iSCSI
- RBD (Ceph Block Device)
- CephFS
- Cinder (OpenStack block storage)
- Glusterfs
- VsphereVolume
- Quobyte Volumes
- HostPath (Single node testing only -- local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)
- Portworx Volumes
- ScaleIO Volumes
- StorageOS
下面是一个持久卷的声明 YAML 配置文件。在此配置文件中要求提供 5Gi 的存储空间,存储模式为Filesystem ,访问模式是 ReadWriteOnce,通过 Recycle 回收策略进行持久化存储卷的回收,指定存储类为 slow,使用 nfs 的插件类型。
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv
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
复制代码
2.1 容量(Capacity)
PV 需通过 capcity
属性指定存储容量,目前 capcity
属性仅有 storage
(存储大小)这唯一一个设置。
2.2 存储卷模式(Volume Mode)
存储卷模式的默认值为 Filesystem
。在 Kubernetesv1.9 版本后用户可以指定volumeMode
的值,除了支持文件系统(files ystem)外,也支持块设备(raw block devices)。
2.3 访问模式(Access Modes)
访问模式的可选范围如下:
- ReadWriteOnce(缩写 RWO):仅允许单个节点挂载进行读写;
- ReadOnlyMany(缩写 ROX):允许多个节点挂载且只读;
- ReadWriteMany(缩写 RWX):允许多个节点挂载进行读写;
即使某个存储卷插件支持多种访问模式,但一次也只能配置一种访问模式。下面是各个存储卷插件支持的访问模式清单:
存储卷插件 | ReadWriteOnce | ReadOnlyMany | ReadWriteMany |
---|---|---|---|
AWSElasticBlockStore | ✓ | – | – |
AzureFile | ✓ | ✓ | ✓ |
AzureDisk | ✓ | – | – |
CephFS | ✓ | ✓ | ✓ |
Cinder | ✓ | – | – |
FC | ✓ | ✓ | – |
FlexVolume | ✓ | ✓ | – |
Flocker | ✓ | – | – |
GCEPersistentDisk | ✓ | ✓ | – |
Glusterfs | ✓ | ✓ | ✓ |
HostPath | ✓ | – | – |
iSCSI | ✓ | ✓ | – |
PhotonPersistentDisk | ✓ | – | – |
Quobyte | ✓ | ✓ | ✓ |
NFS | ✓ | ✓ | ✓ |
RBD | ✓ | ✓ | – |
VsphereVolume | ✓ | – | – (works when pods are collocated) |
PortworxVolume | ✓ | – | ✓ |
ScaleIO | ✓ | ✓ | – |
StorageOS | ✓ | – | – |
2.4 类(Class)
通过 storageClassName
属性指定存储类别,即 StorageClass 资源对象的名称。具有特定类别的 PV 只能与请求了该类别的 PVC 进行绑定。
2.5 回收策略
当前的回收策略可选值包括:
- Retain -- 持久化卷被释放后,仍保留数据,需要手工进行回收操作;
-
Recycle -- 回收空间,删除 PVC 后,执行基础擦除命令
rm-rf /thevolume/*
(NFS、HostPath 存储支持); - Delete -- 删除 PVC 后,会删除与 PV 相关的存储数据(AWSEBS、GCE PD 存储支持);
2.6 挂载参数(Mount Options)
当 PV 挂载到一个节点时,可能需要设置额外的挂载参数,可以通过 mountOptions
字段设置。但只有下面的存储卷类型支持挂载参数:
- GCEPersistentDisk
- AWSElasticBlockStore
- AzureFile
- AzureDisk
- NFS
- iSCSI
- RBD (Ceph Block Device)
- CephFS
- Cinder (OpenStack block storage)
- Glusterfs
- VsphereVolume
- Quobyte Volumes
- VMware Photon
3. 持久卷声明
下面是一个名称为 myclaim 的持久卷声明 YAML 配置文件,它的访问模式为 ReadWriteOnce,存储卷模式是 Filesystem,需要的存储空间大小为 8Gi,指定的存储类为 slow,并设置了标签选择器和匹配表达式。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc
spec:
accessModes: #访问模式
- ReadWriteOnce
volumeMode: Filesystem #存储卷模式
resources: #资源
requests:
storage: 8Gi
storageClassName: slow #存储类
selector: #选择器
matchLabels:
release: "stable"
matchExpressions: #匹配表达式
- {key: environment, operator: In, values: [dev]}
复制代码
3.1 选择器
在 PVC 中,可以通过标签选择器来进一步的过滤 PV。选择器的有两种:
- matchLabels: 只有存在与此处的标签一样的 PV 才会被 PVC 选中;
- matchExpressions :匹配表达式由键、值和操作符组成,操作符包括 In, NotIn, Exists 和DoesNotExist,只有符合表达式的 PV 才能被选择;
如果同时设置了 matchLabels 和 matchExpressions,则会进行求与(&),即只有同时满足上述匹配要求的 PV 才会被选择。
3.2 存储类
除了使用标签过滤 PV,还可通过 storageClassName
属性指定存储类,PV 只有为该存储类的才能被绑定到 PVC 上。
4. Pod 中使用 PVC
Pod 通过使用 PVC 来访问 PV,Pod 需与 PVC 在同一个命名空间中,PVC 会与集群中合适的 PV 进行绑定,并将 PV 挂载到主机和 Pod 上。
kind: Pod
apiVersion: v1
metadata:
name: mypod
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd # 挂载的存储卷的名称
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim # PVC名称
复制代码
5. 本地持久化存储
本地持久化存储(Local Persistent Volume)就是把数据存储到 Pod 运行的宿主机上,其必须保证 Pod 被调度到具有本地持久化存储的节点上。
为什么需要这种类型的存储呢?有时候你的应用对磁盘 IO 有很高的要求,网络存储性能肯定不如本地的高,尤其是本地使用了SSD这种磁盘。
在非本地持久化存储的场景中,我们会先创建 PV,然后创建 PVC,此时如果两者匹配则会自动进行绑定。即使是动态 PV 创建,Pod 也会先调度到某个节点上,然后根据 PVC 进行创建 PV 最后绑定到 Pod 中。
可是对于本地持久化存储有一个问题就是 PV 必须提前准备好,而且并非每一个集群节点都有该 PV,因此 Pod 不能随意调度 。那么如何保证 Pod 一定被调度到有 PV 的节点上呢?这时就需要在 PV 中声明节点亲和性,且 Pod 被调度的时候还要考虑卷的分布情况。
- 下面是 PV 的定义,即使 node02 节点上也有
/data/vol1
目录,但 PV 也不一定会在 node2 上,因为nodeAffinity
属性设置了亲和主机 node01。
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local: # local类型
path: /data/vol1 # 节点上的具体路径
nodeAffinity: # 这里就设置了节点亲和
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node01 # 这里我们使用node01节点,该节点有/data/vol1路径
复制代码
- 定义存储类:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
复制代码
这里的 volumeBindingMode: WaitForFirstConsumer
很关键,表示为延迟绑定(等待第一个消费者后再进行绑定),该配置会影响 PVC 与 PV 的绑定时机,只有在出现第一个使用指定 PVC 的 Pod 时,才会将 PVC 与 PV 进行绑定。
因为通常 PVC 会在有合适的 PV 时立即绑定,但如果 Pod 被调度到没有该 PV 的节点上,会导致 Pod 一直被挂起。而延迟绑定的作用即 Pod 的调度要参考卷的分布。当调度器开始调度 Pod 的时候会看看它要求的 LPV 在哪里,然后再调度到该节点,进行 PVC 的绑定,最后再挂载到 Pod 中,保证了 Pod 所在的节点一定就是 LPV 所在的节点。所以让 PVC 延迟绑定,就是要等到使用这个 PVC 的 Pod 出现在调度器上之后,根据综合评估在来绑定 PVC。
- 定义 PVC:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: local-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage
复制代码
可以看到这个 PVC 是 pending 状态,这也就是延迟绑定,因为此时还没有 Pod。
- 定义 Pod:
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deploy
spec:
replicas: 1
selector:
matchLabels:
appname: myapp
template:
metadata:
name: myapp
labels:
appname: myapp
spec:
containers:
- name: myapp
image: tomcat:8.5.38-jre8
ports:
- name: http
containerPort: 8080
protocol: TCP
volumeMounts:
- name: tomcatedata
mountPath : "/data"
volumes:
- name: tomcatedata
persistentVolumeClaim:
claimName: local-claim
复制代码
此时因为 PV 在 node01 节点上,因此这个 Pod 被调度到该节点上,即使删除在创建该 Pod,依然会被调度到 node01 节点上。并且 PVC 已经为绑定状态。