使用 K8s 进行作业调度实战分享
最近在公司的数据同步项目(以下简称 ZDTP)中,需要使用到分布式调度数据同步执行单元,目前使用的方案是将数据同步执行单元打包成镜像,使用 K8s 进行调度。
在 ZDTP 中,数据同步的动作可抽象成一个执行单元(以下称为 worker),类似于线程执行单元 Runnable ,Runnable 放入一个队列中等待线程的调度执行,执行完 Runnable 即完成了它的使命。当用户在 ZDTP 控制台中创建同步任务并启动任务时,会根据同步任务的配置,产生若干个用于该任务的 worker,假设这些 worker 都在本地执行,可以将其包装成一个 Runnable,然后创建一个线程执行,如下图表示:
但是在单机模式下,就会遇到性能瓶颈,此时就需要分布式调度,将 worker 调度到其他机器执行:
问题是我们如何将 worker 更好地调度到其它机器中执行呢?
Worker 部署方式调研
1、基于虚拟机部署 Worker
Worker 在提前创建好的虚拟机中运行, 任务启动时需要根据当前 Worker 负载情况进行选择空闲的 Worker,相当于 Worker 是以 Agent 的形式运行,如下图表示:
伴随而来的缺点主要有以下几点:
- Worker Agent 数量相对固定,虚拟机创建成本高,扩/缩容麻烦;
- 任务运行情况依赖 zk 监听机制,如果某个任务在运行中挂掉了,需要自行实现故障转移与自动重启机制,增加开发周期;
- Worker Agent 负载获取逻辑需要项目实现,精确获取负载信息实现难度大,增加开发周期。
2、基于 K8s 部署 Worker
将 Worker 打包成 Docker 镜像,使用 K8s 对 worker 容器进行调度作业,并且一个 Worker 只运行一个任务,如下图表示:
使用 K8s 的优点如下:
- 使用 K8s 集群调度的 Worker 容器具备故障恢复功能,只要将 Pod 的重启策略设置为 restartPolicy=Always,无论 Worker 容器在运行过程中发生什么异常,K8s 都会自动尝试重启 Worker 容器,大大减少了运维成本,提高了数据同步的高可用性;
- 自动实现负载,比如当某个节点负载高,就会将 Worker 容器调度到负载低的节点上,更重要的是,某个节点宕机,其上的工作负载会被 K8s 自动将其转移到其它节点上面;
- Worker 的生命周期完全交由 K8s 集群管理,只需调用相关接口即可清楚 Worker 运行情况,大大减少开发周期。
K8s 容器调度方案调研
K8s 集群的调度对象是 Pod,调度方式有多种,这里主要介绍以下几种方式:
1、Deployment(全自动调度)
在讲 Deployment 前,先来说下 Replica Set,它是 K8s 一个非常重要的概念,它是在 Pod 这个抽象上更为上层的一个抽象,一般大家用 Deployment 这个抽象来做应用的真正的管理,而 Pod 是组成 Deployment 最小的单元。它可以定义某种 Pod(比如包装了 ZDTP Worker 容器的 Pod)在任意时刻都保持符合 Replica Set 设定的预期值, 比如 Replica Set 可预期设定 Pod 副本数,当 k8s 集群定期巡检发现某种 Pod 的副本数少于 Replica Set 设定的预期值,它就会按照 Replica Set 设定的 Pod 模版创建 Pod 实例,使得 Pod 的数量维持在预期值,也是通过 Replica Set 的特性,实现了集群的高可用性,同时减少了运维成本。
Deployment 内部使用了 Replica Set 来实现,他们之间高度相似,也可以将 Deployment 看作是 Replica Set 的升级版本。
2、Job(批处理调度)
我们可以通过 k8s Job 资源对象定义并启动一个批处理任务,并行或者串行处理一批工作项(Work item),处理完成后任务就结束。
1)Job Template Expansion 模式
根据 ZDTP Worker 运行方式,我们可以使用一个 Job 对像对应一个 Worker,有多少个 worker 就创建多少个 Job,除非 Pod 异常,才会重启该 Pod,正常执行完后 Job 就退出,如下图表示:
2)Queue with Pod Per Work Item 模式
这种模式将客户端生成的 worker 存放在一个队列中,然后只会创建一个 job 去消费队列中的 worker item,通过设置 parallelism 参数可以同时启动多少个 worker Pod 同时处理 worker,值得一体的是,这种模式下的 Worker 处理程序逻辑只会从队列拉取 worker 处理,处理完就立即退出,completions 参数用于控制正常退出的 Pod 数量,当退出的 Pod 数量达到了 completions 后,Job 结束,即 completions 参数可以控制 Job 的处理 Worker 的数量。如下图所示:
3)Queue with Variable Pod Count 模式
这种调度模式看起来跟 Queue with Pod Per Work Item 模式差不多,其实不然,Queue with Variable Pod Count 模式的 Job 只要有一个 Pod 正常退出,即说明 Job 已经处理完数据,处于终止状态了,因为它的每个 Pod 都有查询队列是否还有 worker 的逻辑,一旦发现队列中没有了 worker,Pod 正常退出,因此 Queue with Variable Pod Count 模式 completions 参数只能设置 1, parallelism 参数可以同时启动多少个 worker Pod 同时处理 worker。
这种模式也要求队列能够让 Pod 感知是否还存在 worker,像 RocketMQ/Kafka 之类的消息中间件并不能做到,只会让客户端一直等待,因此这种模式不能选用 RocketMQ/Kafka,可以选择数据库或者 Redis 来实现。如下图所示:
当然如果后面还有定时执行 Worker 的需求,使用 K8s 的 cronjob(定时任务调度)是一个非常好的选择。
3、Pod(默认调度)
直接通过 kind=pod 的方式启动容器,这种方式不能设置容器的运行实例数,即 replicas = 1,通常生产应用集群都不会通过这个方式启动容器,因为这种方式启动容器不具备 Pod 自动扩缩容的特性。
值得一提的是,即使你的 Pod 副本只有 1 个,官方也推荐使用 Replica Set 的方式进行部署。
Pod 重启策略分析
Pod 的重启策略包括 Always、onFailure、Never:
- Always:当容器失效时,k8s 自动重启该容器;
- onFailure:当容器终止运行时并且退出码不为 0 时,k8s 自动重启该容器;
- Never:不论容器运行状态如何,k8s 都不会重启该容器
Deployment/Replica Set 必须设置为 Always(因为它们都需要保持 Pod 期待的副本数),而 Job 只能设置为 onFailure 和 Never,以确保容器执行完成后不再重启,直接 Pod 启动容器以上三个重启策略都可以设置。
这里需要说明一点,如果使用 Job,情况可能稍微复杂些:
1)Pod 重启策略 RestartPolicy=Never
假设 Job 调度过程中 Pod 发生非正常退出,尽管此时容器不再重启,由于 Job 需要至少一个 Pod 执行完成(即 completions 最少等于 1),Job 才算完成。因此,虽然非正常退出的 Pod 不再重启,但 Job 会尝试重新启动一个 Pod 执行,直到 Pod 正常完成的数量为 completions。
$ kubectl get pod --namespace zdtp-namespace
NAME READY STATUS RESTARTS AGE
zdtp-worker-hc6ld 0/1 ContainerCannotRun 0 64s
zdtp-worker-hfblk 0/1 ContainerCannotRun 0 60s
zdtp-worker-t9f6v 0/1 ContainerCreating 0 11s
zdtp-worker-v2g7s 0/1 ContainerCannotRun 0 31s
2)Pod 重启策略 RestartPolicy=onFailure
当 RestartPolicy=onFailure,Pod 发生非正常退出时,Pod 会尝试重启,直到该 Pod 正常执行完成,此时 Job 就不会重新启动一个 Pod 执行了,如下:
$ kubectl get pod --namespace zdtp-namespace
NAME READY STATUS RESTARTS AGE
zdtp-worker-5tbxw 0/1 CrashLoopBackOff 5 67s
如何选择 K8s 调度策略?
以上内容把 K8s 的调度方案与 Pod 的重启策略都研究了一番后,接下来就需要针对项目的调度需求选择合适的调度方式。
1、增量同步 Worker
增量同步 Worker 会一直同步下去,中途不停止,这意味着 Pod 的重启策略必须为 RestartPolicy=Always,那么这种方式只能选择 Deployment 调度或者直接创建 Pod 部署,但建议使用 Deployment,官方已经说明了即使 Pod 副本为 1,依然建议使用 Deployment 进行部署。
2、 全量同步 Worker
全量同步 Worker 在数据同步完就退出,看起来 Job 调度或者直接创建 Pod 部署都可以满足,但现阶段由于全量同步暂时没有记录同步进度,因此要求中途发生任何错误容器退出后都不能自动重启,目前的做法是当 Worker 执行过程中发生非正常退出时,需要用户自行删除已同步的资源,再手动启动 Worker 再次进行全量同步。
因此,Job 目前还还不适合调度 Worker Pod,全量同步 Worker 现阶段只适合直接使用 Pod 进行部署,且需要设置 Pod 重启策略 RestartPolicy=Never。
作者简介
作者张乘辉,擅长消息中间件技能,负责公司百万 TPS 级别 Kafka 集群的维护,作者维护的公号「后端进阶」不定期分享 Kafka、RocketMQ 系列不讲概念直接真刀真枪的实战总结以及细节上的源码分析;同时作者也是阿里开源分布式事务框架 Seata Contributor,因此也会分享关于 Seata 的相关知识;当然公号也会分享 WEB 相关知识比如 Spring 全家桶等。内容不一定面面俱到,但一定让你感受到作者对于技术的追求是认真的!
公众号:后端进阶
GitHub:https://github.com/objcoding/
使用 K8s 进行作业调度实战分享的更多相关文章
- .NET Core中的验证组件FluentValidation的实战分享
今天有人问我能不能出一篇FluentValidation的教程,刚好今天在实现我们的.NET Core实战项目之CMS的修改密码部分的功能中有用到FluentValidation,所以就以修改用户密码 ...
- nRF24L01+组网方式及防撞(防冲突)机制的实战分享
利用多个nRF24L01+模块组网通信的实现方式 这里讨论的组网方式,不包含使用6个通道实现的多对1通信方式,因其只限于6个发送端,局限性很大,可以附加其他技术实现更好的组网,暂时这里不讨论.这里分享 ...
- Dubbo入门到精通学习笔记(十一):Dubbo服务启动依赖检查、Dubbo负载均衡策略、Dubbo线程模型(结合Linux线程数限制配置的实战分享)
文章目录 Dubbo服务启动依赖检查 Dubbo负载均衡策略 Dubbo线程模型(结合Linux线程数限制配置的实战分享) 实战经验分享( ** 属用性能调优**): Dubbo服务启动依赖检查 Du ...
- 如何在github中插入图片,链接,图片链接(给图片加上链接),文字+图片链接,的实战分享!
如何在github中插入图片,链接,图片链接(给图片加上链接),文字+图片链接,的实战分享! markdown 1.文字链接: [link-Text](link-URL) [home](https:/ ...
- nRF24L01+不能接收或接收偶尔异常等问题实战分享
nRF24L01+接收异常问题综述 在调试nRF24L01+无线收发模块的时候,最具标志性的环节就是在接收端可以收到数据.在实际应用调试中,会出现很多意想不到的情况,造成nRF24L01+模块接收端无 ...
- K8S的StorageClass实战(NFS)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- K8S(16)集成实战-使用spinnaker进行自动化部署
K8s集成实战-使用spinnaker进行自动化部署 1 spinnaker概述和选型 1.1 概述 1.1.1 主要功能 Spinnaker是一个开源的多云持续交付平台,提供快速.可靠.稳定的软件变 ...
- K8S(15)监控实战-ELK收集K8S内应用日志
K8S监控实战-ELK收集K8S内应用日志 目录 K8S监控实战-ELK收集K8S内应用日志 1 收集K8S日志方案 1.1 传统ELk模型缺点: 1.2 K8s容器日志收集模型 2 制作tomcat ...
- K8S(14)监控实战-grafana出图_alert告警
k8s监控实战-grafana出图_alert告警 目录 k8s监控实战-grafana出图_alert告警 1 使用炫酷的grafana出图 1.1 部署grafana 1.1.1 准备镜像 1.1 ...
随机推荐
- 5.10 省选模拟赛 拍卖 博弈 dp
LINK:拍卖 比赛的时候 前面时间浪费的有点多 写这道题的时候 没剩多少时间了. 随便设了一个状态 就开始做了. 果然需要认真的思考.其实 从我的状态的状态转移中可以看出所有的结论. 这里 就不再赘 ...
- Linux搭建Gitlab(Docker版)
1.拉取gitlab的docker镜像 #这里使用gitlab的社区版 docker pull gitlab/gitlab-ce 2.启动gitlab容器实例 docker run -d -p 44 ...
- 笨办法学习python3练习代码:argv参数变量与文件操作
ex15.py 完成ex15.py需要在ex15.py同文件夹目录下面准备一个txt文件(ex15_sample.txt) 执行ex15.py 如: python ex15.py e ...
- 认识IPv4分组
强化一下记忆:以免忘记. 图就不放了. 首部20B (4B的整数倍) 的固定部分12个域,的确很麻烦的:IPv6才8个域,首部长度8B的整数倍 20B分5行吧,每行4B,即32位.第一行,第二行,第三 ...
- 【HEOI2015】公约数数列 题解(分块)
前言:毒瘤数据结构题,半个下午都在搞它了…… --------------------------- 题目链接 题目大意:给定一个长度为$n$的序列,有两种操作:1.把$a_x$的值改成$y$.2.求 ...
- 【六省联考2017】组合数问题 题解(矩阵快速幂优化DP)
题目链接 题目大意:求$(\sum\limits_{i=0}^n C_{nk}^{ik+r})\ mod \ p$的值. --------------------- 讲真,一开始看到这个题我都没往DP ...
- SpringBoot常用配置,引入外部配置文件信息,热加载
SpringBoot的配置文件格式 yml规范 SpringBoot的配置文件支持properties和yml,甚至还支持json. 更推荐使用yml文件格式: yml文件,会根据换行和缩进帮助咱们管 ...
- Java—面向对象、类与对象、封装
理解什么是面向过程.面向对象 面向过程与面向对象都是我们编程中,编写程序的一种思维方式. 面向过程的程序设计方式,是遇到一件事时,思考“我该怎么做”,然后一步步实现的过程. 面向对象的程序设计方式,是 ...
- 2020-04-13:怎么在日志里排查错误,该用哪些Linux命令
能通过less命令打开文件,通过Shift+G到达文件底部,再通过?+关键字的方式来根据关键来搜索信息. 能通过grep的方式查关键字,具体用法是, grep 关键字 文件名,如果要两次在结果里查找的 ...
- C#LeetCode刷题之#521-最长特殊序列 Ⅰ(Longest Uncommon Subsequence I)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3949 访问. 给定两个字符串,你需要从这两个字符串中找出最长的特 ...