Skip to content

Kubernetes数据持久化管理

Kubernetes 为了能更好的支持有状态应用的数据存储问题,除了基本的 HostPath 和 EmptyDir 提供的数据持久化方案之外,还提供了 PV,PVC 和 StorageClass 资源对象来对存储进行管理。

PV 的全称是 Persistent Volume(持久化卷),是对底层数据存储的抽象,PV 由管理员创建、维护以及配置,它和底层的数据存储实现方法有关,比如 Ceph,NFS,ClusterFS 等,都是通过插件机制完成和共享存储对接。

PVC 的全称是 Persistent Volume Claim(持久化卷声明),我们可以将 PV 比喻为接口,里面封装了我们底层的数据存储,PVC 就是调用接口实现数据存储操作,PVC 消耗的是 PV 的资源。

StorageClass 是为了满足用于对存储设备的不同需求,比如快速存储,慢速存储等,通过对 StorageClass 的定义,管理员就可以将存储设备定义为某种资源类型,用户根据 StorageClass 的描述可以非常直观的知道各种存储资源的具体特性,这样就可以根据应用特性去申请合适的资源了。

安装存储系统

存储系统的选择有很多,常见的有 NFS、Ceph、GlusterFS、FastDFS 等,具体使用什么根据企业情况而定。在这里使用的是 NFS,下面简单介绍一下如何安装。

(1)安装服务

bash
yum install nfs-utils rpcbind -y

(2)创建共享目录

bash
mkdir /data/k8s -p

(3)配置 NFS 配置文件

bash
vim /etc/exports
/data/k8s *(rw,sync,no_root_squash)

(4)启动服务

bash
systemctl start rpcbind
systemctl start nfs
systemctl enable rpcbind
systemctl enable nfs

(5)测试

bash
showmount -e 192.168.205.128
Export list for 192.168.205.128:
/data/k8s *

PS:所有节点都需要安装 NFS 客户端

PV

PV(Persistent Volume)作为 Kubernetes 存储设备,可以由管理员提前配置,也可以通过 StorageClass 来动态供应。

PV 是集群资源,可以通过 kubectl explain pv 来查看如何配置,主要包括存储能力,访问模式,存储类型,回收信息等关键信息。例如:

yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv01
  labels:
    storage: pv
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/k8s
    server: 192.168.205.128

参数说明:

(1)、accessMode:访问模式,有 ReadWriteOnce,ReadOnlyMany,ReadWriteMany。其中:

  • ReadWriteOnce:表示具有读写权限,但是只能被一个 node 挂载一次
  • ReadOnlyMany:表示具有只读权限,可以被多个 node 多次挂载
  • ReadWriteMany:表示具有读写权限,可以被多个 node 多次挂载

(2)、capacity:持久卷资源和容量的描述,存储大小是唯一可设置或请求的资源。

(3)、persistentVolumeReclaimPolicy: 回收策略,也就是释放持久化卷时的策略,其有以下几种:

  • Retain:保留数据,如果要清理需要手动清理数据,默认的策略;
  • Delete:删除,将从 Kubernetes 中删除 PV 对象,以及外部基础设施中相关的存储资产,比如 AWS EBS, GCE PD, Azure Disk, 或 Cinder volume;
  • Recycle:回收,清楚 PV 中的所有数据,相当于执行 rm -rf /pv-volume/*;

创建过后,PV 的状态如下:

bash
kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
my-pv01   1Gi        RWO            Recycle          Available                                   5s

kubectl describe pv my-pv01
Name:            my-pv01
Labels:          storage=pv
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:
Status:          Available
Claim:
Reclaim Policy:  Recycle
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        1Gi
Node Affinity:   <none>
Message:
Source:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    192.168.205.128
    Path:      /data/k8s
    ReadOnly:  false
Events:        <none>

当前 PV 的状态是 Available,表示处于随时可用状态。PV 总共有以下四种状态:

  • Available(可用):表示可用状态,还未被任何 PVC 绑定
  • Bound(已绑定):表示 PVC 已经被 PVC 绑定
  • Released(已释放):PVC 被删除,但是资源还未被集群重新声明
  • Failed(失败): 表示该 PV 的自动回收失败

单纯的创建 PV,我们并不能直接使用,需要使用 PVC(Persistent Volume Claim)来进行声明。

PVC

PVC(Persistent Volume Claim)用于表达用户对存储的需求,申请 PVC 会消耗掉 PV 的资源,可以通过kubectl explain pvc来查看帮助文档。

在上一节我们创建了 PV,现在要申明 PVC,如下:

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-test
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

spec 参数说明:

(1)、accessModes:主要定义卷所应该拥有的访问模式

(2)、resources:主要定义卷应该拥有的最小资源

(3)、dataSource:定义如果提供者具有卷快照功能,就会创建卷,并将数据恢复到卷中,反之不创建

(4)、selector:定义绑定卷的标签查询

(5)、storageClassName:定义的 storageClass 的名字

(6)、volumeMode:定义卷的类型

(7)、volumeName:需要绑定的 PV 的名称链接

创建过后,查看 PV 和 PVC 的状态,如下:

bash
kubectl get pvc
NAME       STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-test   Bound    my-pv01   1Gi        RWO                           2s
kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
my-pv01   1Gi        RWO            Recycle          Bound    default/pvc-test                           20m

我们从上面可以看到 pvc 处于 Bound 状态,Bound 的 VOLUME 是 my-pv01,我们再看 pv 的状态有 Available 变为 Bound,其 CLAIM 是 default/pvc-test,其中 default 为 namespace 名称。

在上面我们创建了一个 PVC,其绑定了我们创建的 PV,如果此时我们再创建一个 PVC,结果又会如何?

我们 copy 以下上面的 PVC 文件,将其名称改一下,如下:

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-test2
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

然后查看 PVC 的状态,如下

bash
kubectl get pvc
NAME        STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-test    Bound     my-pv01   1Gi        RWO                           3m57s
pvc-test2   Pending                                                      4s

我们可以看到我们刚创建的 pvc-test2 的 STATUS 处于 Pending 状态,这是由于集群里声明的 PV 都使用完了,PVC 在申请的时候没有找到合适的 PV,所以处于这个状态,这时候如果我们创建一个新的并满足要求的 PV,则可以看到这个 PVC 会处于 Bound 状态。如下:

bash
kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM              STORAGECLASS   REASON   AGE
my-pv01   1Gi        RWO            Recycle          Bound       default/pvc-test                           27m
my-pv02   1Gi        RWO            Recycle          Available                                              5s
kubectl get pvc
NAME        STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-test    Bound    my-pv01   1Gi        RWO                           6m50s
pvc-test2   Bound    my-pv02   1Gi        RWO                           2m57s

PVC 也在申领 PV 的时候也不是随意申领的,它需要符合以下要求:

(1)PVC 申领的模式要和 PV 匹配上,假如 PVC 的模式是 ReadWriteOnce,而 PV 的模式是 ReadWriteMany,则申领部成功。

(2)PVC 申领的容量要小于等于 PV 的容量,否则申请不成功。

(3)一个 PV 只能绑定一个 PVC

另外,如果我们的 PVC 需求的容量小于 PV 的可用容量,绑定的容量是 PV 的可用容量。

StorageClass

上面介绍的 PV 和 PVC 模式是需要运维人员先创建好 PV,然后开发人员定义好 PVC 进行一对一的 Bond,但是如果 PVC 请求成千上万,那么就需要创建成千上万的 PV,对于运维人员来说维护成本很高,Kubernetes 提供一种自动创建 PV 的机制,叫 StorageClass,它的作用就是创建 PV 的模板。

具体来说,StorageClass 会定义一下两部分:

  1. PV 的属性 ,比如存储的大小、类型等;
  2. 创建这种 PV 需要使用到的存储插件,比如 Ceph 等;

有了这两部分信息,Kubernetes 就能够根据用户提交的 PVC,找到对应的 StorageClass,然后 Kubernetes 就会调用 StorageClass 声明的存储插件,创建出需要的 PV。

这里我们以 NFS 为例,要使用 NFS,我们就需要一个 nfs-client 的自动装载程序,我们称之为 Provisioner,这个程序会使用我们已经配置好的 NFS 服务器自动创建持久卷,也就是自动帮我们创建 PV。

说明:

  • 自动创建的 PV 会以${namespace}-${pvcName}-${pvName}的目录格式放到 NFS 服务器上;
  • 如果这个 PV 被回收,则会以 archieved-${namespace}-${pvcName}-${pvName}这样的格式存放到 NFS 服务器上;

安装 NFS Provisioner

(1)创建 ServiceAccount,为 NFS Provisioner 授权

yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-client-provisioner-clusterrole
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: nfs-client-provisioner-clusterrolebinding
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-clusterrole
  apiGroup: rbac.authorization.k8s.io

(2)创建 NFS Provisioner

yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-prosioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-prosioner
  template:
    metadata:
      labels:
        app: nfs-client-prosioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-prosioner
          image: registry.cn-hangzhou.aliyuncs.com/rookieops/nfs-client-provisioner:4.0
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-client-root
              mountPath: /data/pv
          env:
            - name: PROVISIONER_NAME
              value: rookieops/nfs
            - name: NFS_SERVER
              value: 192.168.205.128
            - name: NFS_PATH
              value: /data/k8s
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.205.128
            path: /data/k8s

执行完成后,查看 NFS Provisioner 的状态,如下:

bash
kubectl get po
NAME                                    READY   STATUS    RESTARTS   AGE
nfs-client-prosioner-54d64dfc85-b4ht4   1/1     Running   0          10s

使用 StorageClass

上面已经创建好 NFS Provisioner,现在我们可以直接创建 StroageClass,如下:

yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs
provisioner: rookieops/nfs

每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到。

在配置 StorageClass 的时候,如果没有指定 reclaimPolicy,则默认是 Delete,除此之外,还有 Retain。

StorageClass 对象的命名很重要,用户使用这个命名来请求生成一个特定的类。 当创建 StorageClass 对象时,管理员设置 StorageClass 对象的命名和其他参数,一旦创建了对象就不能再对其更新。

使用kubectl apply -f sc.yaml创建 StorageClass,创建完成过后如下:

bash
kubectl get sc
NAME   PROVISIONER     RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs    rookieops/nfs   Delete          Immediate           false                  9m41s

现在,我们就可以使用动态存储申领 PVC,如下:

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-from-sc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs
  resources:
    requests:
      storage: 1Gi

使用kubectl apply -f pvc-from-sc.yaml,查看 PVC 创建情况,如下:

bash
kubectl get pvc
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-from-sc   Bound    pvc-a4a71b8c-5664-4d1a-b286-9e4adcf6f96a   1Gi        RWO            nfs            8s
kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   REASON   AGE
pvc-a4a71b8c-5664-4d1a-b286-9e4adcf6f96a   1Gi        RWO            Delete           Bound    default/pvc-from-sc   nfs                     86s

可以看到自动创建了一个 PV,然后和 PVC 进行绑定。

为了方便使用,有时候会给集群默认设置一个 StorageClass,以便在需要使用动态存储,但是未声明的情况下使用默认的动态存储。设置方式如下:

bash
kubectl patch storageclass nfs -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

通过向其添加 storageclass.kubernetes.io/is-default-class 注解来将特定的 StorageClass 标记为默认。 当集群中存在默认的 StorageClass 并且用户创建了一个未指定 storageClassName 的 PersistentVolumeClaim 时, DefaultStorageClass 准入控制器会自动向其中添加指向默认存储类的 storageClassName 字段。

请注意,集群上最多只能有一个 默认 存储类,否则无法创建没有明确指定 storageClassName 的 PersistentVolumeClaim。

如果要取消默认 StorageClass,只需要把注解设置为 flase 即可,如下:

yaml
kubectl patch storageclass nfs -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

如果我们要在 Pod 中使用 PVC,则直接如下声明:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - name: nfs-pvc
          mountPath: /mnt
  restartPolicy: Never
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: pvc-from-sc

可以进入容器,到挂载目录输出,例如:

bash
kubectl exec -it nginx -- /bin/bash
root@nginx:/mnt## echo "test" > /mnt/text.txt

然后到 NFS 对应的目录查看是否一致。

bash
cd /data/k8s/default-pvc-from-sc-pvc-a4a71b8c-5664-4d1a-b286-9e4adcf6f96a
cat text.txt
test

这表示 Pod 使用持久化成功。

总结

在 Kubernetes 中,虽然我们建议使用无状态应用,但是对于有些特殊应用,数据持久化还是必不可少的。数据持久化的难度不在于创建几个 PV 或者 PVC,而是后端的存储系统,比如 Ceph,如果使用它作为后端存储,你必须对其非常熟悉,方便在出问题的时候好排查,如果你对这些存储系统都不熟悉,在使用的时候可能会出现很多问题。

最近更新