kubernetes 与LVM的结合
本文主要介绍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,分为以下几个步骤:
- 调用
lvcreate创建LVM设备,大小为用户在yaml文件中指定的磁盘大小,会在kubelet调用plugin的时候作为参数传递进来 - 调用mkfs格式化LVM为某种文件类型
- 创建挂载点目录,该参数一般都由plugin作为参数传递进来,一般就是$KUBELET_ROOT_DIR/pods/$POD_UID/volumes/...目录下
- mount LVM设备到挂载点上
- 汇报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的结合的更多相关文章
- Cgroup blkio简介和测试(使用fio测试)
Cgroup blkio简介和测试(使用fio测试) 因需要对docker镜像内的进程对磁盘读写的速度进行限制,研究了下Cgroup blkio,并使用fio对其iops/bps限速进行测试. Cgr ...
- TopoLVM: 基于LVM的Kubernetes本地持久化方案,容量感知,动态创建PV,轻松使用本地磁盘
正文 研发测试场景下,一般追求的是一键快速起环境,横向动态复制,一人一套,随起随用,用完即走.作为使用方,其不用关心实际的物理资源是怎样的,环境起在哪里,只要声明自己的使用需求即可.但作为方案构建者以 ...
- 在虚拟机环境(CentOS7系统)下将kubernetes中部署服务成功,但在虚拟机外部无法访问到服务
在CentOS7环境下,kubernetes单机版环境,成功部署一个服务,在虚拟机中访问服务没问题,下面这样: curl http://172.27.73.26:8888/eureka-server/ ...
- 附009.Kubernetes永久存储之GlusterFS独立部署
一 前期准备 1.1 基础知识 Heketi提供了一个RESTful管理界面,可以用来管理GlusterFS卷的生命周期.Heketi会动态在集群内选择bricks构建所需的volumes,从而确保数 ...
- 附010.Kubernetes永久存储之GlusterFS超融合部署
一 前期准备 1.1 基础知识 在Kubernetes中,使用GlusterFS文件系统,操作步骤通常是: 创建brick-->创建volume-->创建PV-->创建PVC--&g ...
- kubernetes支持local volume
目录 local volume 创建一个storage class 静态创建PV 使用local volume PV 动态创建PV local volume kubernetes从1.10版本开始支持 ...
- kubernetes存储之GlusterFS
目录 1.glusterfs概述 1.1.glusterfs简介 1.2.glusterfs特点 1.3.glusterfs卷的模式 2.heketi概述 3.部署heketi+glusterfs 3 ...
- 二进制方式安装Kubernetes 1.14.2高可用详细步骤
00.组件版本和配置策略 组件版本 Kubernetes 1.14.2 Docker 18.09.6-ce Etcd 3.3.13 Flanneld 0.11.0 插件: Coredns Dashbo ...
- Kubernetes学习笔记_尚硅谷
https://www.bilibili.com/video/BV1w4411y7Go?p=1 一.K8s介绍 k8s是一个编排容器的工具,其实也是管理应用的全生命周期的一个工具,从创建应用,应用的部 ...
随机推荐
- Djanjo 的app 模板路径 静态文件 完整版登录 新手三件套 以及orm
一: django中app的概念: 一个项目可以包含多个应用(app,类似于模块,主页打开多个模块就是多个app) 创建了app,要在配置文件中注册 二:模板路径配置: 1 templates文件夹 ...
- java并发编程工具类辅助类:CountDownLatch、CyclicBarrier和 Semaphore
在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier和Semaphore,今天我们就来学习一下这三个辅助类的用法. 以下 ...
- Java Web系列:Spring Security 基础
Spring Security虽然比JAAS进步很大,但还是先天不足,达不到ASP.NET中的认证和授权的方便快捷.这里演示登录.注销.记住我的常规功能,认证上自定义提供程序避免对数据库的依赖,授权上 ...
- 由于没有公钥,无法验证下列签名 Ubuntu
问题:执行 apt-get update 时错误 W: GPG 错误:https://apt.dockerproject.org ubuntu-trusty InRelease: 由于没有公钥,无法验 ...
- 浅析C#中的IEquatable<T>接口
1.引言 首先我们先来看看IEquatable<T>接口的出现解决了什么问题. 我们知道,Object基类的Equals方法存在两个明显的问题.一是缺乏类型安全性,二是对于值类型而言需要装 ...
- DotNet三层架构
[项目结构]DAL 数据访问层.BLL 业务逻辑.UI 表现层 Model 数据.Common 常用工具[引用关系]DAL --> Model CommonCommon --> 谁都不引用 ...
- 创建可复用的自定义 ASP.NET MVC Helpers
通常,在ASP.NET MVC项目中App_Code目录下新建.cshtml编写类似下方的代码就能创建自定义的MVC Helper了, 假设文件名为StrHelper.cshtml,那么在别的视图中的 ...
- JSON is undefined. Infopath Form People Picker in SharePoint 2013
After some analysis, we found that, this is a known defect with the Microsoft and it is being fixed ...
- JPA之@GeneratedValue注解
JPA的@GeneratedValue注解,在JPA中,@GeneratedValue注解存在的意义主要就是为一个实体生成一个唯一标识的主键(JPA要求每一个实体Entity,必须有且只有一个主键), ...
- Java : java.util.ConcurrentModificationException
在删除 List 元素的时候,要用 Iterator,不要直接遍历 List,否则会出现 Fatal Exception: java.util.ConcurrentModificationExcept ...