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的动态存储供应

posted @ 2020-08-19 22:38  名字很长容易被惦记  阅读(514)  评论(0编辑  收藏  举报