k8s十一 | 持久化存储 PV&PVC&StorageClass

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

一、hostPath/emptyDir


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

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

hostPath定义如下:

....
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    hostPath:
      path: /opt/data
      type: Directory

emptyDir定义如下:

....
    volumeMounts:
      - name: data
        mountPath: /data
  volumes:
  - name: data
    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目录

$ systemctl stop firewalld.service
$ yum -y install nfs-utils rpcbind
$ mkdir -p /data/k8s
$ chmod 755 /data/k8s
$ vim /etc/exports
/data/k8s  *(rw,sync,no_root_squash)
$ systemctl start rpcbind.service
$ systemctl start nfs.service
$ mount 192.168.16.173:/data/k8s  /data/k8s

创建nfspv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  storageClassName:  manual
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.16.173
    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 插件支持的访问模式:


image.png

创建资源对象:

$ kubectl  create -f  nfspv.yaml 
persistentvolume/nfs created
$ kubectl  get pv
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                  STORAGECLASS    REASON   AGE
nfs           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

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  storageClassName:  manual
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 3Gi

创建PVC

$ kubectl  get pvc
  NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
  nfs-pvc   Bound    nfs      5Gi        RWX            manual         3m59s
$ kubectl   get pv
  NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   REASON   AGE 
  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类似

apiVersion: v1
kind: Pod
metadata:
  name: web-front
spec:
  containers:
  - name: web
    image: nginx
    ports:
      - name: web
        containerPort: 80
    volumeMounts:
        - name: nfs
          mountPath: "/usr/share/nginx/html"
  volumes:
  - name: nfs
    persistentVolumeClaim:
      claimName: nfs-pvc

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

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

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

$ kubectl  create -f nfs-nginxpod.yaml
$ kubectl exec -it  web-front  -- /bin/bash
  root@web-front:/# curl localhost
  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 的自动配置程序

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 192.168.16.173 # 修改成自己的 IP
            - name: NFS_PATH
              value: /data/k8s
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.16.173 # 修改成自己的 IP
            path: /data/k8s

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

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

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
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"]

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

创建StorageClass

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

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

创建资源对象

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

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

创建nfs-pvc02.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc02
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs-data-db
  resources:
    requests:
      storage: 1Gi

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

$ kubectl  create  -f  nfs-pvc02.yaml 
$ kubectl  get pvc
  NAME        STATUS   VOLUME                                     CAPACITY     ACCESS MODES   STORAGECLASS   AGE
  nfs-pvc     Bound    nfs                                        5Gi        RWX            manual         7h49m
  nfs-pvc02   Bound    pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0   1Gi        RWO            nfs-data-db    7s
$ kubectl    get pv 
  NAME                                       CAPACITY   ACCESS MODES   RECLAIM  POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
  nfs                                        5Gi        RWX            Retain           Bound    default/nfs-pvc     manual                  8h
  pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0   1Gi        RWO            Delete           Bound    default/nfs-pvc02   nfs-data-db             17s
$  ls /data/k8s
  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】获取视频教程及更多资料:


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335