本文主要介绍k8s与LVM结合使用的场景,在原生的k8s中对于本地存储提供了hostPath与emptyDir两种volme,hostPath是直接将文件存储在本地主机上,存在的问题是无法进行quota管理,单个用户就可能将所有的磁盘打满;在kubernetes 1.10 local ephemeral storage成为beta版本,可以对emptyDir进行磁盘限制,但是这里的磁盘配额不仅包括用户写入epmtyDir的数据,也包括writable layer和logs,并且emptyDir会随着pod的删除而删除,无法自定义生命周期,如果应用程序需要在pod退出后保留这些数据,emptyDir就无法胜任了。鉴于k8s原生volume的以上特点无法满足复杂的业务需求,我们可以根据LVM提供一种本地存储的volume方案。本文只介绍LVM与k8s结合实现方式,具体LVM及k8s的知识请自行查阅相关资料。

Out-of-Tree Volume Plugins

k8s对于额外的存储提供良好的支持,参见 Out-of-Tree Volume Plugins, 有两种方式实现volume plugin: CSI和FlexVolume,第一种是现在官方推荐的方式,不过第二种官方还继续提供支持而且使用起来更加简单,官方提供了FelxVolme LVM shell版的简单实现,参见github地址,其实就是一个binary或脚本来提供特定的调用,对于LVM来说需要实现基本的mount和umount subcommand供kubelet回调。mount subcommand创建LVM,分为以下几个步骤:

  1. 调用lvcreate创建LVM设备,大小为用户在yaml文件中指定的磁盘大小,会在kubelet调用plugin的时候作为参数传递进来
  2. 调用mkfs格式化LVM为某种文件类型
  3. 创建挂载点目录,该参数一般都由plugin作为参数传递进来,一般就是$KUBELET_ROOT_DIR/pods/$POD_UID/volumes/...目录下
  4. mount LVM设备到挂载点上
  5. 汇报LVM VG的使用情况,因为后面调度pod的时候需要使用这些信息

上述是kubelet在回调plugin mount subcommand的操作,对于umount subcommand因为需要自定义删除策略,我们不能直接将LVM 删除掉,但挂载点目录必须删除掉,否则kubelet会认为该volume没有被正确移除,pod一直处于terminating无法删除,此时有种tricky的方式就是move挂载点目录,这样LVM正常mount到挂载点上,pods也可以正常被删除,至于后续的自定义删除策略就直接操作move之后的挂载点设备了。需要注意的是应用程序如果要查看退出pod的LVM中的数据必须去移动之后的挂载点目录查看。

LVM GC policy

前面了解了如何在k8s中提供LVM支持,umount的时候不删除LVM来为自定义删除策略提供支持,自定义删除策略就需要写一个旁路清理程序,可以是daemon或cron的形式运行,可以基于磁盘容量或pod退出时间进行GC。比较好的方式是不直接清理LVM, 先lvreduce对LVM进行缩容,将未使用的空间还给VG, 这样即提高磁盘使用率,又保留了用户数据,等到合适的时候再删除。注意此处缩容指的是LVM和文件系统两方面,且不能损坏用户的数据为前提。每次GC之后都需要及时上报VG信息,为后面调度提供信息。

schedule pod based on LVM

上面的实现为集群中的每个node提供了一种额外的资源(LVM)的支持, 既然是node级别的资源,就要考虑在调度pod的时候如何选择合适的node assign,需要自定义调度策略,上文中所有对LVM的操作中都提到了要及时上报VG信息,就是为了调度的时候使用这些信息。官方对于node resource提供了调度的支持,参见Advertise Extended Resources for a Node,但是这种方式只适合随着pod生命周期结束而结束的资源,例如emptyDir,memroy,cpu等。还有一种扩展是device plugin,可以考虑将LVM作为一种磁盘device使用,这种方式也不适合,因为对于device设备的申请是有粒度的,例如可以说申请一个磁盘设备,但不能说申请1G的空间。

不能通过上述两种声明node resource的方式利用内置的调度策略,我们只能自己实现调度策略了,k8s对于调度的扩展也有很好的支持,可以定义一个scheduler extender提供一个http webhook。要实现一个scheudler extender, 首先要了解kube-scheduler的调度过程,分为三步,首先是做筛选(predict)或称为filter,过滤掉不能调度到其上的node。其次是排序(priorities),为可以调度的node进行打分,决定最适合调度的node;最后是绑定(bind),绑定操作将该pod“分配”给某台node。scheduler-extender相当于上述步骤的“补充协议”, 可选的为上述步骤补充自己的调度逻辑, 当创建一个pod需要调度的时候,首先会执行内置的调度策略,然后将内置调度器的结果传递过来给你的extender server,调度逻辑完成之后将结果返回给kube-scheduler进行下一个过程。

除了kube-scheduler的调度步骤外,kube-scheduler为了提高调度性能有一个“资源占用”的概念: kube-scheduler会在本地有一个cache存放所有的node信息,该cache会与api-server进行同步,调度pod的信息直接从cache中取,当pod调度到某个node上后,会将cache中该node的资源进行“预先占用”,假定资源已经被pod消耗了,其实pod只是调度到了该node上面,还未真正启动起来,未真正占用资源,通过假定调度上去的pod已经占用了该资源,为后面调度的pod提供变化的信息,如果在指定的timeout内,pod还未启动则将预先占用的资源返回给该node,通过这种异步的最终一致性的方案能够有效提高pod的调度效率。此外在高可用的方案中需要配置多个kube-scheduler,因为每个kube-scheduler的cache并不同步,无法多个kube-scheduler同时对外提供服务,此时只能通过选主协议选择其中的一个进行处理请求。

明白了kube-scheduler的实现之后,来仿照kube-scheduler实现我们的scheduler extender, extender的httpserver位置通过kube-scheduler的--policy-config-file--policy-configmap来配置,前者指定一个config file,后者通过一个configmap来配置,笔者建议用configmap来管理,因为更新配置只需要改变一个地方对于多个kube-scheduler同时生效。并且可以单独定义上述三步骤中的其中某一步。

apiVersion: v1
kind: ConfigMap
metadata:
name: lvm-scheduler-extender
namespace: kube-system
data:
policy.cfg : |
{
"kind" : "Policy",
"apiVersion" : "v1",
"extenders" : [
{
"urlPrefix": "http://localhost:8384/lvm-scheduler",
"apiVersion": "v1alpha1",
"filterVerb": "predicates/lvm_resource",
"bindVerb": "bind",
"prioritizeVerb": "",
"weight": 1,
"enableHttps": false,
"nodeCacheCapable": false
}
],
"hardPodAffinitySymmetricWeight" : 10
}

上面的配置configmap指定了extender webhook的位置,并且定义了filter和bind这两个操作,之所以定义这两个操作是因为我们只关心哪些node可以调度上去,将LVM剩余空间不足以创建pod的node过滤掉,以及最后到底调度到了那台node上以便在scheduler extender的cache中将该node的资源进行占用。需要注意的是系统中只能有一个执行bind操作的binder, 如果extender中指定了binder,kube-scheduler内置的就不会生效了,需要通过k8s client-go来绑定:

clientset.CoreV1().Pods(podNamespace).Bind(&v1.Binding{
ObjectMeta: metav1.ObjectMeta{Namespace: podNamespace, Name: podName, UID: podUID},
Target: v1.ObjectReference{
Kind: "Node",
Name: nodeName,
}})

前面在介绍plugin和GC的时候每次操作LVM都需要及时将LVM信息上报来提供extender进行调度决策,那么该向哪里汇报这些VG信息?如何汇报? 这个问题根据不同的场景下有不同的解决方案,首先肯定不能直接向extender上报,因为高可用集群中往往不只有一个exteder示例,所以需要提供一个分离的统一后端存储,所有的extender直接从后端存储里取,可以存储在Mysql或redis中,比较tricky的方式是直接存储在k8s的etcd中,这些数据量比较小不会占用太多空间,也免了额外的操作数据库,但是直接操作etcd有点不妥,可以定义一种CRD资源,为每台node创建一个crd资源实体表示该node可用LVM空间的大小,这样就可以像其他资源一样方便查询,在LVM资源发生变化之后对该crd对象进行patch。在extender中watch该crd资源即可。此外需要提供一个cache进行资源的预先占用,同时提供选主协议的实现,以上都可以参考kube-scheduler中的实现。

summary

麻雀虽小,五脏俱全上述通过实现LVM这种node resource的支持,分别涉及到了storage plugin,scheduelr extender,CRD等,虽然内容较多但是实际编码并不多,感谢kubernetes提供了一种灵活的扩展,感兴趣的可以自己去实现一下。

kubernetes 与LVM的结合的更多相关文章

  1. Cgroup blkio简介和测试(使用fio测试)

    Cgroup blkio简介和测试(使用fio测试) 因需要对docker镜像内的进程对磁盘读写的速度进行限制,研究了下Cgroup blkio,并使用fio对其iops/bps限速进行测试. Cgr ...

  2. TopoLVM: 基于LVM的Kubernetes本地持久化方案,容量感知,动态创建PV,轻松使用本地磁盘

    正文 研发测试场景下,一般追求的是一键快速起环境,横向动态复制,一人一套,随起随用,用完即走.作为使用方,其不用关心实际的物理资源是怎样的,环境起在哪里,只要声明自己的使用需求即可.但作为方案构建者以 ...

  3. 在虚拟机环境(CentOS7系统)下将kubernetes中部署服务成功,但在虚拟机外部无法访问到服务

    在CentOS7环境下,kubernetes单机版环境,成功部署一个服务,在虚拟机中访问服务没问题,下面这样: curl http://172.27.73.26:8888/eureka-server/ ...

  4. 附009.Kubernetes永久存储之GlusterFS独立部署

    一 前期准备 1.1 基础知识 Heketi提供了一个RESTful管理界面,可以用来管理GlusterFS卷的生命周期.Heketi会动态在集群内选择bricks构建所需的volumes,从而确保数 ...

  5. 附010.Kubernetes永久存储之GlusterFS超融合部署

    一 前期准备 1.1 基础知识 在Kubernetes中,使用GlusterFS文件系统,操作步骤通常是: 创建brick-->创建volume-->创建PV-->创建PVC--&g ...

  6. kubernetes支持local volume

    目录 local volume 创建一个storage class 静态创建PV 使用local volume PV 动态创建PV local volume kubernetes从1.10版本开始支持 ...

  7. kubernetes存储之GlusterFS

    目录 1.glusterfs概述 1.1.glusterfs简介 1.2.glusterfs特点 1.3.glusterfs卷的模式 2.heketi概述 3.部署heketi+glusterfs 3 ...

  8. 二进制方式安装Kubernetes 1.14.2高可用详细步骤

    00.组件版本和配置策略 组件版本 Kubernetes 1.14.2 Docker 18.09.6-ce Etcd 3.3.13 Flanneld 0.11.0 插件: Coredns Dashbo ...

  9. Kubernetes学习笔记_尚硅谷

    https://www.bilibili.com/video/BV1w4411y7Go?p=1 一.K8s介绍 k8s是一个编排容器的工具,其实也是管理应用的全生命周期的一个工具,从创建应用,应用的部 ...

随机推荐

  1. SQL group by 分组后,同一组的排序后取第一条

    SELECT * FROM(                SELECT                     [SPID]                    ,[PH1]           ...

  2. C#使用Log4Net记录日志(转)

    出处:http://www.cnblogs.com/wangsaiming/archive/2013/01/11/2856253.html 第一步:下载Log4Net 下载地址:http://logg ...

  3. Gym 101201H Paint (离散化+DP)

    题意:给定 n 个区间,让你选出一些,使得每个选出区间不交叉,并且覆盖区间最大. 析:最容易想到的先是离散化,然后最先想到的就是 O(n^2)的复杂度,dp[i] = max(dp[j] + a[i] ...

  4. 深入jetty的使用详解

    简介: Jetty 是一个用 Java 实现.开源.基于标准的,并且具有丰富功能的 Http 服务器和 Web 容器,可以免费的用于商业行为.Jetty 这个项目成立于 1995 年,现在已经有非常多 ...

  5. handsontable-developer guide-data binding,data sources

    数据绑定: 1.表格中得数据是引用了数据源中的数据:表格中数据改变,数据源中得数据也改变:数据源中得数据改变,通过render方法,表格中的数据也改变: 2.如果想把数据源中的数据和表格中的数据分开: ...

  6. [label][responsive-web-design]网页响应测试各种尺寸的工具

    因为现在各种各样的尺寸上网设备 ,所以我们现在的网页设计都必须要兼容到各种尺寸的屏幕,必须测试各个 size下面的页面布局与排版. 一个开发人员是不可能拥有各种设备来进行测试,那么有没有什么便捷的工具 ...

  7. [label][javascript-Unit Test][JSLint]A Guide To JSLint Messages

    原文链接: http://www.jameswiseman.com/blog/2011/03/26/coding-convention-an-style-guide/ http://www.james ...

  8. C#基础入门 九

    C#基础入门 九 集合 对于很多应用程序,需要创建和管理相关对象组,有两种方式可以将对象分组,一是创建对象数组,如 object[] obj=new object[3]{1,2.33,"st ...

  9. urlrewrite重写url(转)

    环境: Maven 3.0.4 Urlrewrite 2.5.2 Myeclipse 8.6.1 借此机会顺便提一下 Maven Project 的创建,会了的朋友或还不想了解 Maven 的朋友,可 ...

  10. Hibernate实体类注解的问题

    刚刚和八千哥弄一个问题,这个很诡异的问题,困扰了我这么长时间.哎,说来惭愧. 用三大框架写毕设,结果今天获取前台数的时候,发现传值有个传不到. 我一开始用的是名为cows的数据,后来换了个数据库,加了 ...