从一个例子入手PV、PVC

Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 对象用于管理存储卷。

简单的说PersistentVolume (PV) 是集群中已由管理员配置的一段网络存储,是持久化存储数据卷;Persistent Volume Claim(PVC)描述的,则是 Pod 所希望使用的持久化存储的属性,比如,Volume 存储的大小、可读写权限等等。

上面的这段文字说明可能过于模糊,下面举个例子看看:

我们定义一个PVC,声明需要的Volume属性:

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

yaml文件中定义了一个1 GiB的PVC,Access Modes表示需要的volume存储类型,ReadWriteOnce表示只能在一个node节点上进行读写操作,其他的Access Modes详见:https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes。

然后再定义一个PV:

apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: 10.244.1.4
path: "/"

这个 PV 对象中会详细定义存储的类型是NFS,以及大小是1 GiB。

PVC和PV相当于“接口”和“实现”,所以我们需要将PVC和PV绑定起来才可以使用,而PVC和PV绑定的时候需要满足:

  1. PV 和 PVC 的 spec 字段要匹配,比如PV 的存储(storage)大小,就必须满足 PVC 的要求。
  2. PV 和 PVC 的 storageClassName 字段必须一样才能进行绑定。storageClassName表示的是StorageClass的name属性。

做好PVC的声明之后,并建立好PV,然后就可以使用这个PVC了:

apiVersion: v1
kind: Pod
metadata:
labels:
role: web-frontend
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

在Pod中只需要声明PVC的名字,等Pod创建后kubelet 就会把这个 PVC 所对应的 PV,也就是一个 NFS 类型的 Volume,挂载在这个 Pod 容器内的目录上。

PersistentVolumeController会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。所以如果出现没有PV可以和PVC绑定,那么Pod 的启动就会报错。

这个时候就需要用到StorageClass了,在上面我们说的PV和PVC绑定的过程称为Static Provisioning,需要手动的创建PV;StorageClass还提供了Dynamic Provisioning机制,可以根据模板创建PV。

StorageClass的Dynamic Provisioning

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

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

这样k8s就能够根据用户提交的 PVC,找到一个对应的 StorageClass ,然后调用该 StorageClass 声明的存储插件,创建出需要的 PV。

例如声明如下StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd

这里定义了名叫 block-service 的 StorageClass,provisioner 字段的值是:kubernetes.io/gce-pd,这是k8s内置的存储插件,type字段也是跟着provisioner定义的,官方默认支持 Dynamic Provisioning 的内置存储插件:https://kubernetes.io/docs/concepts/storage/storage-classes/。

然后就可以在PVC中声明storageClassName为block-service,当创建好PVC 对象之后,k8s就会调用相应的存储插件API创建一个PV对象。

如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim1
spec:
accessModes:
- ReadWriteOnce
storageClassName: block-service
resources:
requests:
storage: 30Gi

这种自动创建PV的机制就是Dynamic Provisioning,Kubernetes 就能够根据用户提交的 PVC,找到一个对应的 StorageClass ,然后会调用StorageClass 声明的存储插件,创建出需要的 PV。

需要注意的是,如果没有声明StorageClassName在PVC中,PVC 的 storageClassName 的值就是"",这也意味着它只能够跟 storageClassName 也是""的 PV 进行绑定。

PV和PVC的生命周期

PV和PVC之间的相互作用遵循这个生命周期:

Provisioning --->Binding --->Using --->Reclaiming

Provisioning

k8s提供了两种PV生成方式: statically or dynamically

statically:由管理员创建PV,它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费。

dynamically:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试为PVC动态配置卷。 此配置基于StorageClasses,PVC必须请求一个StorageClasses,并且管理员必须已创建并配置该类才能进行动态配置。

Binding

由用户创建好PersistentVolumeClaim 后,PersistentVolumeController会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。

Using

Pods声明并使用PVC作为volume后,集群会找到该PVC,如果该PVC已经绑定了PV,那么会将该volume挂载到Pod中。

Reclaiming

当用户已经不再使用该volume,可以将该PVC删除,以便让资源得以回收。相应的在PVC删除后,PV的回收策略可以是Retained, Recycled, or Deleted,这个策略可以在字段spec.persistentVolumeReclaimPolicy中设置。

  • Retain:这个策略允许手动回收资源,当PVC被删除后,PV仍然可以存在,管理员可以手动的执行删除PV,并且和PV绑定的存储资源也不会被删除,如果想要删除相应的存储资源的数据,需要手动删除对应存储资源的数据。
  • Delete:这个策略会在PVC被删除之后,连带将PV以及PV管理的存储资源也删除。
  • Recycle:相当于在volume中执行rm -rf /thevolume/*命令,以便让volume可以重复利用。

删除流程

一般的情况下,我们遵循这个删除流程:

  1. 删除使用这个 PV 的 Pod;
  2. 从宿主机移除本地磁盘(比如,umount 它);
  3. 删除 PVC;
  4. 删除 PV。

Local Persistent Volume实战

Local Persistent Volume适用于类似分布式数据存储比如 MongoDB、Cassandra等需要在多个不同节点上存储数据,并且对I/O 较为敏感的应用。但是相比于正常的 PV,一旦这些节点宕机且不能恢复时,Local Persistent Volume 的数据就可能丢失。

在我们的实验环境中,在宿主机上挂载几个 RAM Disk(内存盘)来模拟本地磁盘。例如:

我们在node1节点上挂载几个磁盘

$ mkdir /mnt/disks
$ for vol in vol1 vol2 vol3; do
mkdir /mnt/disks/$vol
mount -t tmpfs $vol /mnt/disks/$vol
done

然后创建相应的PV:

apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 512Mi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1

这个 PV 的定义里:local 字段指定了它是一个 Local Persistent Volume;而 path 字段,指定的正是这个 PV 对应的本地磁盘的路径,即:/mnt/disks/vol1。并且用nodeAffinity指定这个PV必须运行在node1节点上。

运行上面的PV:

$ kubectl create -f local-pv.yaml
persistentvolume/example-pv created $ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
example-pv 512Mi RWO Delete Available local-storage 16s

然后创建一个StorageClass 来描述这个 PV:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

这个 StorageClass叫local-storage,provisioner为no-provisioner表示不需要自动创建PV。

volumeBindingMode=WaitForFirstConsumer表示需要等到Pod运行之后才让PVC和PV绑定。因为在使用Local Persistent Volume的时候PV和对应的PVC必须要跟随Pod在同一node下面,否则会调度失败。

然后我们运行StorageClass:

$ kubectl create -f local-sc.yaml
storageclass.storage.k8s.io/local-storage created

再创建一个PVC:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: example-local-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 512Mi
storageClassName: local-storage

这里注意声明storageClassName需要是我们上面创建的StorageClass。

然后创建PVC:

$ kubectl create -f local-pvc.yaml
persistentvolumeclaim/example-local-claim created $ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Pending local-storage 7s

这个时候因为还没创建Pod,所以状态还是Pending。

创建一个pod:

kind: Pod
apiVersion: v1
metadata:
name: example-pv-pod
spec:
volumes:
- name: example-pv-storage
persistentVolumeClaim:
claimName: example-local-claim
containers:
- name: example-pv-container
image: nginx
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: example-pv-storage

然后我们创建pod后再看看PVC绑定状态:

$ kubectl create -f local-pod.yaml
pod/example-pv-pod created $ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Bound example-pv 512Mi RWO local-storage 6h

然后我们试着写入一个文件到/usr/share/nginx/html中:

$ kubectl exec -it example-pv-pod -- /bin/sh
# cd /usr/share/nginx/html
# touch test.txt # 在node1上
$ ls /mnt/disks/vol1
test.txt

4.深入k8s:容器持久化存储的更多相关文章

  1. Kubernetes 学习(十)Kubernetes 容器持久化存储

    0. 前言 最近在学习张磊老师的 深入剖析Kubernetes 系列课程,最近学到了 Kubernetes 容器持久化存储部分 现对这一部分的相关学习和体会做一下整理,内容参考 深入剖析Kuberne ...

  2. k8s 网络持久化存储之StorageClass(如何一步步实现动态持久化存储)

    StorageClass的作用: 创建pv时,先要创建各种固定大小的PV,而这些PV都是手动创建的,当业务量上来时,需要创建很多的PV,过程非常麻烦. 而且开发人员在申请PVC资源时,还不一定有匹配条 ...

  3. k8s的持久化存储PV&&PVC

    1.PV和PVC的引入 Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足. 拿前面 AWS EBS 的例子来说,要使用 Volume,Pod 必须事先知道如下信息: 当前 Volu ...

  4. k8s的持久化存储

    本例使用nfs 创建pv [root@k8s-master data]# vi pv.yaml apiVersion: v1kind: PersistentVolumemetadata: name: ...

  5. 通过Heketi管理GlusterFS为K8S集群提供持久化存储

    参考文档: Github project:https://github.com/heketi/heketi MANAGING VOLUMES USING HEKETI:https://access.r ...

  6. k8s集群,使用pvc方式实现数据持久化存储

    环境: 系统 华为openEulerOS(CentOS7) k8s版本 1.17.3 master 192.168.1.244 node1 192.168.1.245 介绍: 在Kubernetes中 ...

  7. 如何接入 K8s 持久化存储?K8s CSI 实现机制浅析

    作者 王成,腾讯云研发工程师,Kubernetes contributor,从事数据库产品容器化.资源管控等工作,关注 Kubernetes.Go.云原生领域. 概述 进入 K8s 的世界,会发现有很 ...

  8. 使用容器化块存储OpenEBS在K3s中实现持久化存储

    作者简介 Giridhara Prasad,Mayadata Inc.首席工程师.在软件测试自动化.混沌工程(chaos engineering)方面有丰富的经验.目前,他正在研究开源混沌工程项目Li ...

  9. docker容器的持久化存储:Volume

    独立于docker容器的持久化存储: 法(1):自动将服务器文件夹挂载到容器内部文件夹/usr/share/nginx/html,这样只修改服务器文件夹下的内容即可对应修改容器内部文件夹的内容 将服务 ...

随机推荐

  1. 阿里云上安装启动nginx 以及在个人电脑上通过公网ip访问遇到的问题

    1.安装依赖包 //一键安装上面四个依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel 2.下载并解压安装包 ...

  2. day73 bbs项目☞基本功能实现

    目录 一.登录功能 二.首页搭建 三.admin后台管理 四.图片防盗链 五.个人站点展示 一.登录功能 views.py 0难度,都是基本操作,要熟悉auth模块的使用 # 登录功能 def log ...

  3. MySQL分库分表的原则

    一.分表 当一个表的数据达到几千万条的时候,每一次查询都会花费更长的时间,如果这时候在使用链表查询,那么我想应该会实在那里,那么我们应该如何解决这个问题呢? 1.为什么要分表: 分表的目的就是为了解决 ...

  4. java 数据结构(六):数组与集合

    1. 集合与数组存储数据概述:集合.数组都是对多个数据进行存储操作的结构,简称Java容器.说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中) ...

  5. softmax、cross entropy和softmax loss学习笔记

    之前做手写数字识别时,接触到softmax网络,知道其是全连接层,但没有搞清楚它的实现方式,今天学习Alexnet网络,又接触到了softmax,果断仔细研究研究,有了softmax,损失函数自然不可 ...

  6. ADB-常见命令使用详解

    ADB命令使用详解 ADB是一个 客户端-服务器端 程序, 其中客户端是你用来操作的电脑, 服务器端是android设备. 1.连接android设置adb connect 设备名例如:adb con ...

  7. nexus 安装与启动(windows本版)

    1.下载 https://www.sonatype.com/download-oss-sonatype 本人云盘:https://pan.baidu.com/s/1_Qmhzij0TlOmTGT-eb ...

  8. cropper.js 二次开发:截图并下载图片

    cropper.js 是一个基于jquery的图片截取库. 参考:https://blog.csdn.net/weixin_38023551/article/details/78792400 我的代码 ...

  9. Java基础(二)流程语句与数组

    Java流程语句详解:https://www.cnblogs.com/jiajia-16/p/6008200.html Java数组详解:https://www.cnblogs.com/jiajia- ...

  10. 第四章 常用API(下)

    4.1.String类 描述:该类代表字符串 构造方法: 方法 描述 public String() 初始化构造一个空白字符串 public String(char[] value) 通过字符数组初始 ...