Kubernetes数据持久化方案
一、介绍
Volume是Pod中能够被多个容器共享的磁盘目录。我们知道,默认情况下Docker容器中的数据都是非持久化的,在容器消亡后数据也会消失。因此Docker提供了Volume机制以便实现数据的持久化。Kubernetes中Volume的概念与Docker中的Volume类似,但不完全相同。具体区别如下:
- Kubernetes中的Volume与Pod的生命周期相同,但与容器的生命周期不相关。当容器终止或重启时,Volume中的数据也不会丢失。
- 当Pod被删除时,Volume才会被清理。并且数据是否丢失取决于Volume的具体类型,比如emptyDir类型的Volume数据会丢失,而PV类型的数据则不会丢失。
二、数据卷Volume:
K8s提供了非常丰富的Volume类型,比如:emptyDir、hostPath、gcePersistentDisk。注意,这些 volume 并非全部都是持久化的,比如 emptyDir、secret、gitRepo 等,这些 volume 会随着 Pod 的消亡而消失。
emptyDir
一个emptyDir volume是在Pod分配到Node时创建的。顾名思义,它的初始内容为空,在同一个Pod中的所有容器均可以读写这个emptyDir volume。当 Pod 从 Node 上被删除(Pod 被删除,或者 Pod 发生迁移),emptyDir 也会被删除,并且数据永久丢失。
一个简单的例子:
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
emptyDir类型的volume适合于以下场景:
临时空间。例如某些程序运行时所需的临时目录,无需永久保存。
一个容器需要从另一容器中获取数据的目录(多容器共享目录)
hostPath
hostPath类型的volume允许用户挂在Node上的文件系统到Pod中,如果 Pod 需要使用 Node 上的文件,可以使用 hostPath。
一个简单的例子:
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
# directory location on host
path: /data
# this field is optional
type: Directory
hostPath支持type属性,它的可选值如下:
hostPath.PNG
hostPath volume通常用于以下场景:
容器中的应用程序产生的日志文件需要永久保存,可以使用宿主机的文件系统进行存储。
需要访问宿主机上Docker引擎内部数据结构的容器应用,通过定义hostPath为/var/lib/docker目录,使容器内应用可以直接访问Docker的文件系统。
在使用hostPath volume时,需要注意:
在不同的Node上具有相同配置的Pod,可能会因为宿主机上的目录和文件不同,而导致对Volume上目录和文件的访问结果不一致。
gcePersistentDisk
gcePersistentDisk 可以挂载 GCE(Google Compute Engine) 上的永久磁盘到容器,需要 Kubernetes 运行在 GCE 的 VM 中。需要注意的是,你必须先创建一个永久磁盘(Persistent Disk,PD)才能使用gcePersistentDisk volume。
volumes:
- name: test-volume
# This GCE PD must already exist.
gcePersistentDisk:
pdName: my-data-disk
fsType: ext4
awsElasticBlockStore
与gcePersistentDisk类似,awsElasticBlockStore 使用Amazon Web Service(AWS)提供的EBS Volume,挂在到Pod中。需要 Kubernetes 运行在 AWS 的 EC2 上。另外,需要先创建一个EBS Volume才能使用awsElasticBlockStore。
volumes:
- name: test-volume
# This AWS EBS volume must already exist.
awsElasticBlockStore:
volumeID: <volume-id>
fsType: ext4
nfs
NFS 是 Network File System 的缩写,即网络文件系统。Kubernetes 中通过简单地配置就可以挂载 NFS 到 Pod 中,而 NFS 中的数据是可以永久保存的,同时 NFS 支持同时写操作。
volumes:
- name: nfs
nfs:
# FIXME: use the right hostname
server: 10.254.234.223
path: "/"
gitRepo
通过挂在一个空目录,并从GIT仓库克隆一个repository供Pod使用。
volumes:
- name: git-volume
gitRepo:
repository: "git@somewhere:me/my-git-repository.git"
revision: "22f1d8406d464b0c0874075539c1f2e96c253775"
subPath
Pod 的多个容器使用同一个 Volume 时,subPath 非常有用。
apiVersion: v1
kind: Pod
metadata:
name: my-lamp-site
spec:
containers:
- name: mysql
image: mysql
volumeMounts:
- mountPath: /var/lib/mysql
name: site-data
subPath: mysql
- name: php
image: php
volumeMounts:
- mountPath: /var/www/html
name: site-data
subPath: html
volumes:
- name: site-data
persistentVolumeClaim:
claimName: my-lamp-site-data
三、PV的生命周期
PV的生命周期包括 5 个阶段:
- 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 并从云存储中删除后段存储
根据这 5 个阶段,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绑定使用。
四、数据对象
上文我们学习了Kubernetes中的Volume,我们可以发现前文中的Volume(无论何种类型)和使用它的Pod都是一种静态绑定关系,在Pod定义文件中,同时定义了它使用的Volume。
在这种情况下,Volume是Pod的附属品,我们无法像创建其他资源(例如Pod,Node,Deployment等等)一样创建一个Volume。
因此Kubernetes提出了PersistentVolume(PV)的概念。
PersistentVolume和Volume一样,代表了集群中的一块存储区域,然而Kubernetes将PersistentVolume抽象成了一种集群资源,类似于集群中的Node对象,这意味着我们可以使用Kubernetes API来创建PersistentVolume对象。PV与Volume最大的不同是PV拥有着独立于Pod的生命周期。
而PersistentVolumeClaim(PVC)代表了用户对PV资源的请求。用户需要使用PV资源时,只需要创建一个PVC对象(包括指定使用何种存储资源,使用多少GB,以何种模式使用PV等信息),Kubernetes会自动为我们分配我们所需的PV。
PV的静态创建
示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
labels:
name: pv0003
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
有三个参数需要注意,accessModes、mountOptions、persistentVolumeReclaimPolicy
1.accessModes:是PV的访问模式,有三种:
- ReadWriteOnce(RWO):是最基本的方式,可读可写,但只支持被单个 Pod 挂载。
- ReadOnlyMany(ROX):可以以只读的方式被多个 Pod 挂载。
- ReadWriteMany(RWX):这种存储可以以读写的方式被多个 Pod 共享。
注意:不是每一种PV都支持这三种方式,例如ReadWriteMany,目前支持的还比较少。在 PVC 绑定 PV 时通常根据两个条件来绑定,一个是存储的大小,另一个就是访问模式。
2.mountOptions中指定PV的底层数据卷类型,例如上文中创建的PV,底层使用nfs存储。目前Kuberntes支持以下类型:
GCEPersistentDisk
AWSElasticBlockStore
AzureFile
AzureDisk
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)
VMware Photon
Portworx Volumes
ScaleIO Volumes
StorageOS
3.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就会被释放。
PVC的创建
当我们定义好一个PV后,我们希望像使用Volume那样使用这个PV,那么我们需要做的就是创建一个PVC对象,并在Pod定义中使用这个PVC。
定义一个PVC:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
Pod通过挂载Volume的方式应用PVC:
kind: Pod
apiVersion: v1
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: dockerfile/nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
通过示例我们可以看到PVC和PV的绑定,不是简单的通过Label来进行。而是要综合storageClassName,accessModes,matchLabels以及storage来进行绑定。
storageClassName指定了使用哪个数据卷,就是在创建pv时指定的pv的名称。
accessModes设置了需要请求的pv的读写模式
Label标签的意思,这个就不多说了。
总的来说,就是通过这三个参数,请求满足storageClassName=slow,accessModes = ReadWriteOnce并且拥有Label:release: "stable"的PV、
PV的动态创建-云服务的数据卷对象
上文中我们通过PersistentVolume描述文件创建了一个PV。这样的创建方式我们成为静态创建。
这样的创建方式有一个弊端,那就是假如我们创建PV时指定大小为50G,而PVC请求80G的PV,那么此PVC就无法找到合适的PV来绑定。
因此产生了了PV的动态创建。PV的动态创建依赖于StorageClass对象,我们不需要手动创建任何PV,所有的工作都由StorageClass为我们完成。
1)集群管理员预先创建存储类(StorageClass);
2)用户创建使用存储类的持久化存储声明(PVC:PersistentVolumeClaim);
3)存储持久化声明通知系统,它需要一个持久化存储(PV: PersistentVolume);
4)系统读取存储类的信息;
5)系统基于存储类的信息,在后台自动创建PVC需要的PV;
6)用户创建一个使用PVC的Pod;
7)Pod中的应用通过PVC进行数据的持久化;
8)而PVC使用PV进行数据的最终持久化处理。
如此美好,所以得加钱
1.定义存储类
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
type: io1
zones: us-east-1d, us-east-1c
iopsPerGB: "10"
这个例子使用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。
2.供应者
provisioner此参数域决定PV使用什么存储卷插件。
存储卷 | 内置供应者 | 配置例子 |
---|---|---|
AWSElasticBlockStore | ✓ | AWS |
AzureFile | ✓ | Azure File |
AzureDisk | ✓ | Azure Disk |
CephFS | – | – |
Cinder | ✓ | OpenStack Cinder |
FC | – | – |
FlexVolume | – | – |
Flocker | ✓ | – |
GCEPersistentDisk | ✓ | GCE |
Glusterfs | ✓ | Glusterfs |
iSCSI | – | – |
PhotonPersistentDisk | ✓ | – |
Quobyte | ✓ | Quobyte |
NFS | – | – |
RBD | ✓ | Ceph RBD |
VsphereVolume | ✓ | vSphere |
PortworxVolume | ✓ | Portworx Volume |
ScaleIO | ✓ | ScaleIO |
StorageOS | ✓ | StorageOS |
Local | – | Local |
五、使用nfs配置Persistent Volume
kubenetes使用nfs配置Persistent Volume部署mysql
创建pv
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-mysql-data
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: nfs
nfs:
path: /root/nfsdata/pv-mysql-data
server: k8s-master
创建pvc
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-mysql-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: nfs
六、、错误收集
1.k8s pvc在挂载状态下被删除,状态变为Terminating并无法回滚的问题排查和总结
1、原生k8s不允许执行删除后的回滚动作。——所以删除任何东西前,要想好,k8s的最终一致性可是原则,虽然我认为这个原则不应该在这个管理层面上强制执行的。
2、如果真的删除了,想挽留,在不改k8s原生代码的前提下,只能有以下两种方式挽回:
1)将pvc的persistentVolumeReclaimPolicy从Delete改为Retain,这样起码删除pod之后,pvc即使被删除,底层数据也不会被删。
2)通过修改etcd里面的value值,删除deletionTimestamp和deletionGracePeriodSeconds两个key。(需要有protobuf协议到yaml的相互转换,还没试过)
3)还有一个比较简单的方式,就是修改k8s的代码,将上面截图的那部分限制代码删掉,允许kubectl patch删除deletionTimestamp和deletionGracePeriodSeconds这两个key,个人感觉,这个可以有。
2.关于pv和pvc
用户调用PVC申请PV时,PV实际大小和硬盘实际挂在卷大小有关,亦超过申请容量时,仍然可以继续存储,突破PV的容量限制。但是一旦pv和pvc绑定以后,就无法再次绑定,也就是说一个pvc只能绑定一个pv
参考文档:Kubernetes对象之PersistentVolume,StorageClass和PersistentVolumeClaim
Kubernetes-基于StorageClass的动态存储供应