在kubernetes中Pod的生命周期是很短暂的,会被频繁地销毁和创建。容器销毁时,保存在容器内部文件系统中的数据都会被清除。为了持久化保存容器的数据,需要使用 Kubernetes Volume数据卷,下面文章分别以本地卷,网络数据卷的方式来实践集群的持久化存储。

. 一、hostPath/emptyDir

  • hostPath:用于将目录从宿主机节点的文件系统挂载到pod中,属于单节点集群中的持久化存储,删除pod后,卷里面的文件会继续保持,在同一节点运行的Pod可以继续使用数据卷中的文件,但pod被重新调度到其他节点时,就无法访问到原数据。不适合作为存储数据库数据的目录。

  • emptyDir:用于存储临时数据的简单空目录,生命周期是和pod捆绑的,随着pod创建而创建;删除而销毁,卷的内容将会丢失。emptyDir卷适用于同一个pod中运行的容器之间共享文件。

hostPath定义如下:

1....
2    volumeMounts:
3    - name: data
4      mountPath: /data
5  volumes:
6  - name: data
7    hostPath:
8      path: /opt/data
9      type: Directory

emptyDir定义如下:

1....
2    volumeMounts:
3      - name: data
4        mountPath: /data
5  volumes:
6  - name: data
7    emptyDir: {}

. 二、PV/PVC

前面使用的 hostPath 和 emptyDir 类型的 Volume 并不具备持久化特征,它们既有可能被 kubelet 清理掉,也不能被“迁移”到其他节点上。所以,大多数情况下,持久化 Volume 的实现,往往依赖于一个远程存储服务,比如:远程文件存储(比如,NFS、GlusterFS)、远程块存储(比如,公有云提供的远程磁盘)等等。而 Kubernetes 需要做的工作,就是使用这些存储服务,来为容器准备一个持久化的宿主机目录,以供将来进行绑定挂载时使用。而所谓“持久化”,指的是容器在这个目录里写入的文件,都会保存在远程存储中,从而使得这个目录具备了“持久性”。为了屏蔽底层的技术实现细节,让用户更加方便的使用,Kubernetes 便引入了 PV 和 PVC 两个重要的资源对象来实现对存储的管理。

  • PV:持久化存储数据卷,全称为PersistentVolume,PV其实是对底层存储的一种抽象,通常是由集群的管理员进行创建和配置 ,底层存储可以是Ceph,GlusterFS,NFS,hostpath等,都是通过插件机制完成与共享存储的对接。

  • PVC:持久化数据卷声明,全称为PersistentVolumeClaim,PVC 对象通常由开发人员创建,描述 Pod 所希望使用的持久化存储的属性。比如,Volume 存储的大小、可读写权限等等。PVC绑定PV,消耗的PV资源。

下面创建一个NFS类型的PV,首先节点部署NFS,共享/data/k8s目录

1$ systemctl stop firewalld.service
2$ yum -y install nfs-utils rpcbind
3$ mkdir -p /data/k8s
4$ chmod 755 /data/k8s
5$ vim /etc/exports
6/data/k8s  *(rw,sync,no_root_squash)
7$ systemctl start rpcbind.service
8$ systemctl start nfs.service
9$ mount 192.168.16.173:/data/k8s  /data/k8s

创建nfspv.yaml

 1apiVersion: v1
2kind: PersistentVolume
3metadata:
4  name: nfs
5spec:
6  storageClassName:  manual
7  capacity:
8    storage: 5Gi
9  accessModes:
10    - ReadWriteMany
11  nfs:
12    server: 192.168.16.173
13    path: "/data/k8s"

文件的内容定义如下:

  • spec.storageClassName:定义了名称为 manual 的 StorageClass,该名称用来将 PVC请求绑定到该 PV。

  • spec.Capacity:定义了当前PV的存储空间为storage=10G。

  • spec.nfs:定义了PV的类型为NFS,并指定了该卷位于节点上的 /data/k8s/目录。

  • spec.accessModes:定义了当前的访问模式,可定义的模式如下:

  • ReadWriteMany(RWX):读写权限,可以被多个节点挂载。

  • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载;

  • ReadWriteMany(ROX):只读权限,可以被多个节点挂载。

下图是一些常用的 Volume 插件支持的访问模式:

ff2663575b5decaf2e6fc7c3844ed7c3.png
image.png

创建资源对象:

1$ kubectl  create -f  nfspv.yaml 
2persistentvolume/nfs created
3$ kubectl  get pv
4NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                  STORAGECLASS    REASON   AGE
5nfs           5Gi        RWX            Retain           Available                          manual                   4s

可以看到创建后的PV的Status为Available 可用状态,意味着当前PV还没有被PVC绑定使用。
PV生命周期的不同状态如下:

  • Available(可用):表示可用状态,还未被任何 PVC 绑定

  • Bound(已绑定):表示 PVC 已经被 PVC 绑定

  • Released(已释放):PVC 被删除,但是资源还未被集群重新声明

  • Failed(失败):表示该 PV 的自动回收失败

另外还有一个RECLAIM POLICY字段输出内容为Retain,这个字段输出的其实是PV的回收策略,目前 PV 支持的策略有三种:

  • Retain(保留):保留数据,需要管理员手工清理数据

  • Recycle(回收):清除 PV 中的数据,效果相当于执行 rm -rf /thevoluem/*

  • Delete(删除):与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务,比如 ASW EBS。

注意:目前只有 NFS 和 HostPath 两种类型支持回收策略。

现在我们创建一个PVC来绑定上面的PV

 1apiVersion: v1
2kind: PersistentVolumeClaim
3metadata:
4  name: nfs-pvc
5spec:
6  storageClassName:  manual
7  accessModes:
8  - ReadWriteMany
9  resources:
10    requests:
11      storage: 3Gi

创建PVC

1$ kubectl  get pvc
2  NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
3  nfs-pvc   Bound    nfs      5Gi        RWX            manual         3m59s
4$ kubectl   get pv
5  NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   REASON   AGE 
6  nfs    5Gi        RWX            Retain           Bound    default/nfs-pvc   manual                  20m

可以看到PV和PVC的状态已经为Bound绑定状态,其中绑定需要检查的条件,包括两部分:

  1. 第一个条件,当然是 PV 和 PVC 的 spec 字段。比如,PV 的存储(storage)大小,权限等就必须满足 PVC 的要求。

  2. 而第二个条件,则是 PV 和 PVC 的 storageClassName 字段必须一样。

创建一个Pod,PVC使用方式和hostpath类似

 1apiVersion: v1
2kind: Pod
3metadata:
4  name: web-front
5spec:
6  containers:
7  - name: web
8    image: nginx
9    ports:
10      - name: web
11        containerPort: 80
12    volumeMounts:
13        - name: nfs
14          mountPath: "/usr/share/nginx/html"
15  volumes:
16  - name: nfs
17    persistentVolumeClaim:
18      claimName: nfs-pvc

Pod将PVC挂载到容器的html目录,我们在PVC的目录下创建一个文件用来验证

1$ echo  "hello nginx" > /data/k8s/index.html

创建Pod,并验证是否将文件挂载至容器

1$ kubectl  create -f nfs-nginxpod.yaml
2$ kubectl exec -it  web-front  -- /bin/bash
3  root@web-front:/# curl localhost
4  hello nginx

可以看到输出结果是我们前面写到PVC卷中的index.html 文件内容。

. 三、StorageClass

在上面PV对象创建的方式为Static Provisioning(静态资源调配),在大规模的生产环境里,面对集群中大量的PVC,需要提前手动创建好PV来与之绑定,这其实是一个非常麻烦的工作。还好kubernetes提供了Dynamic Provisioning(动态资源调配)的机制,即:StorageClass对象, 它的作用其实就是创建 PV 的模板。

StorageClass 对象会定义如下两个部分内容:

  1. PV 的属性。比如,存储类型、Volume 的大小等等。

  2. 创建这种 PV 需要用到的存储插件。比如,Ceph 等等。

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

现在我们创建一个NFS类型的StorageClass,首先需要创建nfs-client-provisioner(存储插件):nfs-client 的自动配置程序

 1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: nfs-client-provisioner
5  labels:
6    app: nfs-client-provisioner
7spec:
8  replicas: 1
9  strategy:
10    type: Recreate
11  selector:
12    matchLabels:
13      app: nfs-client-provisioner
14  template:
15    metadata:
16      labels:
17        app: nfs-client-provisioner
18    spec:
19      serviceAccountName: nfs-client-provisioner
20      containers:
21        - name: nfs-client-provisioner
22          image: quay.io/external_storage/nfs-client-provisioner:latest
23          volumeMounts:
24            - name: nfs-client-root
25              mountPath: /persistentvolumes
26          env:
27            - name: PROVISIONER_NAME
28              value: fuseim.pri/ifs
29            - name: NFS_SERVER
30              value: 192.168.16.173 # 修改成自己的 IP
31            - name: NFS_PATH
32              value: /data/k8s
33      volumes:
34        - name: nfs-client-root
35          nfs:
36            server: 192.168.16.173 # 修改成自己的 IP
37            path: /data/k8s

为nfs-client程序绑定相应的集群操作权限 :

 1apiVersion: v1
2kind: ServiceAccount
3metadata:
4  name: nfs-client-provisioner
5
6---
7kind: ClusterRole
8apiVersion: rbac.authorization.k8s.io/v1
9metadata:
10  name: nfs-client-provisioner-runner
11rules:
12  - apiGroups: [""]
13    resources: ["persistentvolumes"]
14    verbs: ["get", "list", "watch", "create", "delete"]
15  - apiGroups: [""]
16    resources: ["persistentvolumeclaims"]
17    verbs: ["get", "list", "watch", "update"]
18  - apiGroups: ["storage.k8s.io"]
19    resources: ["storageclasses"]
20    verbs: ["get", "list", "watch"]
21  - apiGroups: [""]
22    resources: ["events"]
23    verbs: ["list", "watch", "create", "update", "patch"]
24  - apiGroups: [""]
25    resources: ["endpoints"]
26    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
27
28---
29kind: ClusterRoleBinding
30apiVersion: rbac.authorization.k8s.io/v1
31metadata:
32  name: run-nfs-client-provisioner
33subjects:
34  - kind: ServiceAccount
35    name: nfs-client-provisioner
36    namespace: default
37roleRef:
38  kind: ClusterRole
39  name: nfs-client-provisioner-runner
40  apiGroup: rbac.authorization.k8s.io

创建StorageClass

1apiVersion: storage.k8s.io/v1
2kind: StorageClass
3metadata:
4  name: es-data-db
5provisioner: fuseim.pri/ifs

provisioner 字段的值是:fuseim.pri/ifs,这个是NFS提供的分配器,kubernetes也内置了一些存储的分配器:https://kubernetes.io/zh/docs/concepts/storage/storage-classes/

创建资源对象

1$ kubectl  create -f  nfs-client-Provisioner.yaml
2$ kubectl  create -f  nfs-client-sa.yaml
3$ kubectl  create -f  nfs-storageclass.yaml 
4$ kubectl  get      storageclass
5NAME              PROVISIONER                  RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
6nfs-data-db       fuseim.pri/ifs               Delete          Immediate           false                  136m

StorageClass创建完成后,开发人员只需要在 PVC 里指定要使用的 StorageClass 名字即可,PV则会根据PVC的属性定义自动创建。

创建nfs-pvc02.yaml

 1apiVersion: v1
2kind: PersistentVolumeClaim
3metadata:
4  name: nfs-pvc02
5spec:
6  accessModes:
7    - ReadWriteOnce
8  storageClassName: nfs-data-db
9  resources:
10    requests:
11      storage: 1Gi

创建PVC并查看是否绑定相应的PV

 1$ kubectl  create  -f  nfs-pvc02.yaml 
2$ kubectl  get pvc
3  NAME        STATUS   VOLUME                                     CAPACITY     ACCESS MODES   STORAGECLASS   AGE
4  nfs-pvc     Bound    nfs                                        5Gi        RWX            manual         7h49m
5  nfs-pvc02   Bound    pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0   1Gi        RWO            nfs-data-db    7s
6$ kubectl    get pv 
7  NAME                                       CAPACITY   ACCESS MODES   RECLAIM  POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
8  nfs                                        5Gi        RWX            Retain           Bound    default/nfs-pvc     manual                  8h
9  pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0   1Gi        RWO            Delete           Bound    default/nfs-pvc02   nfs-data-db             17s
10$  ls /data/k8s
11  default-nfs-pvc02-pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0  index.html

可以看到创建完PVC后,StorageClass自动为PVC创建并绑定了对应的PV,而且PV的属性是和PVC相同的,在共享卷中也创建了相关的PV目录,这样我们创建Pod时只需要指定PVC的名字即可,不用再去手动的为PVC创建PV。

注意:Kubernetes 只会将StorageClass 定义相同的 PVC 和 PV 绑定起来


总结:

  1. hostPath:属于单节点集群中的持久化存储,Pod需要绑定集群节点。删除pod后,卷里面的文件会继续保持,但pod被重新调度到其他节点时,就无法访问到原数据。不适合作为存储数据库数据的目录。

  2. emptyDir:用于存储临时数据的简单空目录,生命周期是和pod捆绑的,随着pod创建而创建;删除而销毁,卷的内容将会丢失。emptyDir卷适用于同一个pod中运行的容器之间共享文件。

  3. PVC 描述的,是 Pod 想要使用的持久化存储的属性,比如存储的大小、读写权限等。

  4. PV 描述的,则是一个具体的 Volume 的属性,比如 Volume 的类型、挂载目录、远程存储服务器地址等。

  5. StorageClass 的作用,则是充当 PV 的模板。并且,只有同属于一个 StorageClass 的 PV 和 PVC,才可以绑定在一起。当然,StorageClass 的另一个重要作用,是指定 PV 的 Provisioner(存储插件)。这时候,如果你的存储插件支持 Dynamic Provisioning 的话,Kubernetes 就可以自动为你创建 PV 了。


参考资料:

深入剖析Kubernetes-张磊


关注公众号回复【k8s】获取视频教程及更多资料:

a08f419311841cb9738242b2c38c2eac.png
image.png
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐