学习本节内容前,希望你已经对Kubernetes中Volume的概念有了初步的了解,具体请参考这篇文章:
- [Kubernetes基本概念之Volume]
简介
Kubernetes的pod本身是无状态的(stateless),生命周期通常比较短,只要出现了异常,Kubernetes就会自动创建一个新的Pod来代替它。
而容器产生的数据,会随着Pod消亡而自动消失。
为了实现Pod内数据的存储管理,Kubernetes引入了两个API资源:Persistent Volume(持久卷,以下简称PV)和Persistent Volume Claim(持久卷申请,以下简称PVC)。
PV
PV是Kubernetes集群中的一种网络存储实现,跟Node一样,也是属于集群的资源。
PV跟Docker里的Volume(卷)类似,不过会有独立于Pod的生命周期。
PersistentVolume和Volume一样,代表了集群中的一块存储区域,然而Kubernetes将PersistentVolume抽象成了一种集群资源,类似于集群中的Node对象,这意味着我们可以使用Kubernetes API来创建PersistentVolume对象。
PV与Volume最大的不同是PV拥有着独立于Pod的生命周期。
PVC
PersistentVolumeClaim(PVC)代表了用户对PV资源的请求。用户需要使用PV资源时,只需要创建一个PVC对象(包括指定使用何种存储资源,使用多少GB,以何种模式使用PV等信息),Kubernetes会自动为我们分配我们所需的PV。
如果把PersistentVolume类比成集群中的Node,那么PersistentVolumeClaim就相当于集群中的Pod,Kubernetes为Pod分配可用的Node,为PersistentVolumeClaim分配可用的PersistentVolume。
1. PV的静态创建
首先是一个创建PV的简单例子:
1 | apiVersion: v1 |
PV 的访问模式(accessModes)有三种:
- ReadWriteOnce(RWO):是最基本的方式,可读可写,但只支持被单个 Pod 挂载。
- ReadOnlyMany(ROX):可以以只读的方式被多个 Pod 挂载。
- ReadWriteMany(RWX):这种存储可以以读写的方式被多个 Pod 共享。
不是每一种PV都支持这三种方式,例如ReadWriteMany,目前支持的还比较少。在 PVC 绑定 PV 时通常根据两个条件来绑定,一个是存储的大小,另一个就是访问模式。
PV 的回收策略(persistentVolumeReclaimPolicy,即 PVC 释放卷的时候 PV 该如何操作)也有三种:
- Retain,不清理, 保留 Volume(需要手动清理)
- Recycle,删除数据,即 rm -rf /thevolume/*(只有 NFS 和 HostPath 支持)
- Delete,删除存储资源,比如删除 AWS EBS 卷(只有 AWS EBS, GCE PD, Azure Disk 和 Cinder 支持)
PVC释放卷是指用户删除一个PVC对象时,那么与该PVC对象绑定的PV就会被释放。
1.1 PV支持的类型
定义PV时,我们需要指定其底层存储的类型,例如上文中创建的PV,底层使用nfs存储。目前Kuberntes支持以下类型:
1 | GCEPersistentDisk |
2. PVC的创建
当我们定义好一个PV后,我们希望像使用Volume那样使用这个PV,那么我们需要做的就是创建一个PVC对象,并在Pod定义中使用这个PVC。
定义一个PVC:
1 | kind: PersistentVolumeClaim |
Pod通过挂在Volume的方式应用PVC:
1 | kind: Pod |
下面简要分析一下定义的PVC文件的关键:
- 首先关注这个配置:storageClassName: slow。此配置用于绑定PVC和PV。这表明这个PVC希望使用storageClassName=slow的PV。返回到上文中PV的定义,我们可以看到PV定义中也包含storageClassName=slow的配置。
- 接下来是accessModes = ReadWriteOnce。这表明这个PV希望使用storageClassName=slow,并且accessModes = ReadWriteOnce的PV。
- 在上述条件都满足后,PVC还可以指定PV必须满足的Label,如matchLabels: release: “stable”。这表明此PVC希望使用storageClassName=slow,accessModes = ReadWriteOnce并且拥有Label:release: “stable”的PV。
- 最后是storage: 8Gi。这表明此PVC希望使用8G的Volume资源。
通过上面的分析,我们可以看到PVC和PV的绑定,不是简单的通过Label来进行。而是要综合storageClassName,accessModes,matchLabels以及storage来进行绑定。
3. PV的动态创建
上文中我们通过PersistentVolume描述文件创建了一个PV。这样的创建方式我们成为静态创建。这样的创建方式有一个弊端,那就是假如我们创建PV时指定大小为50G,而PVC请求80G的PV,那么此PVC就无法找到合适的PV来绑定。因此产生了了PV的动态创建。
PV的动态创建依赖于StorageClass对象。我们不需要手动创建任何PV,所有的工作都由StorageClass为我们完成。一个例子如下:
1 | kind: StorageClass |
这个例子使用AWS提供的插件( kubernetes.io/aws-ebs)创建了一个基于AWS底层存储的StorageClass。这意味着使用这个StorageClass,那么所有的PV都是AWSElasticBlockStore类型的。
StorageClass的定义包含四个部分:
- provisioner:指定 Volume 插件的类型,包括内置插件(如 kubernetes.io/aws-ebs)和外部插件(如 external-storage 提供的 ceph.com/cephfs)。
- mountOptions:指定挂载选项,当 PV 不支持指定的选项时会直接失败。比如 NFS 支持 hard 和 nfsvers=4.1 等选项。
- parameters:指定 provisioner 的选项,比如 kubernetes.io/aws-ebs 支持 type、zone、iopsPerGB 等参数。
- reclaimPolicy:指定回收策略,同 PV 的回收策略。
手动创建的PV时,我们指定了storageClassName=slow的配置项,然后Pod定义中也通过指定storageClassName=slow,从而完成绑定。而通过StorageClass实现动态PV时,我们只需要指定StorageClass的metadata.name。
回到上文中创建PVC的例子,此时PVC指定了storageClassName=slow。那么Kubernetes会在集群中寻找是否存在metadata.name=slow的StorageClass,如果存在,此StorageClass会自动为此PVC创建一个accessModes = ReadWriteOnce,并且大小为8GB的PV。
通过StorageClass的使用,使我们从提前构建静态PV池的工作中解放出来。
4. PV的生命周期
PV的生命周期包括 6 个阶段:
- Provisioning,即 PV 的创建,可以直接创建 PV(静态方式),也可以使用 StorageClass 动态创建
- Binding,将 PV 分配给 PVC
- Using,Pod 通过 PVC 使用该 Volume,并可以通过准入控制 StorageProtection(1.9及以前版本为PVCProtection)阻止删除正在使用的 PVC
- Releasing,Pod 释放 Volume 并删除 PVC
- Reclaiming,回收 PV,可以保留 PV 以便下次使用,也可以直接从云存储中删除
- Deleting,删除 PV 并从云存储中删除后段存储
根据这 6 个阶段,PV 的状态有以下 4 种
- Available:可用
- Bound:已经分配给 PVC
- Released:PVC 解绑但还未执行回收策略
- Failed:发生错误
一个PV从创建到销毁的具体流程如下:
- 一个PV创建完后状态会变成Available,等待被PVC绑定。
- 一旦被PVC邦定,PV的状态会变成Bound,就可以被定义了相应PVC的Pod使用。
- Pod使用完后会释放PV,PV的状态变成Released。
- 变成Released的PV会根据定义的回收策略做相应的回收工作。有三种回收策略,Retain、Delete 和 Recycle。
- Retain就是保留现场,K8S什么也不做,等待用户手动去处理PV里的数据,处理完后,再手动删除PV。
- Delete 策略,K8S会自动删除该PV及里面的数据。
- Recycle方式,K8S会将PV里的数据删除,然后把PV的状态变成Available,又可以被新的PVC绑定使用。
5. DefaultStorageClass
前面我们说到,PVC和PV的绑定是通过StorageClassName进行的。然而如果定义PVC时没有指定StorageClassName呢?这取决与admission插件是否开启了DefaultDefaultStorageClass功能:
如果DefaultStorageClass功能开启,那么此PVC的StorageClassName就会被指定为DefaultStorageClass。DefaultStorageClass从何处而来呢?原来在定义StorageClass时,可以在Annotation中添加一个键值对:storageclass.kubernetes.io/is-default-class: true,那么此StorageClass就变成默认的StorageClass了。
如果DefaultStorageClass功能没有开启,那么没有指定StorageClassName的PVC只能被绑定到同样没有指定StorageClassName的PV。
常见问题
参考文章
http://blog.csdn.net/liukuan73/article/details/60089305
https://www.jianshu.com/p/99e610067bc8