首先咱们来看一下 Job 的需求来源。咱们知道 K8s 里面,最小的调度单元是 Pod,咱们能够直接经过 Pod 来运行任务进程。这样作将会产生如下几种问题:node
咱们来看一下 Kubernetes 的 Job 为咱们提供了什么功能:api
咱们根据一个实例来看一下Job是如何来完成下面的应用的。微信
上图是 Job 最简单的一个 yaml 格式,这里主要新引入了一个 kind 叫 Job,这个 Job 其实就是 job-controller 里面的一种类型。 而后 metadata 里面的 name 来指定这个 Job 的名称,下面 spec.template 里面其实就是 pod 的 spec。架构
这里面的内容都是同样的,惟一多了两个点:app
因此在 Job 里面,咱们主要重点关注的一个是 restartPolicy 重启策略和 backoffLimit 重试次数限制。less
Job 建立完成以后,咱们就能够经过 kubectl get jobs 这个命令,来查看当前 job 的运行状态。获得的值里面,基本就有 Job 的名称、当前完成了多少个 Pod,进行多长时间。dom
**AGE **的含义是指这个 Pod 从当前时间算起,减去它当时建立的时间。这个时长主要用来告诉你 Pod 的历史、Pod 距今建立了多长时间。**DURATION **主要来看咱们 Job 里面的实际业务到底运行了多长时间,当咱们的性能调优的时候,这个参数会很是的有用。**COMPLETIONS **主要来看咱们任务里面这个 Pod 一共有几个,而后它其中完成了多少个状态,会在这个字段里面作显示。 微服务
<br />下面咱们来看一下 Pod,其实 Job 最后的执行单元仍是 Pod。咱们刚才建立的 Job 会建立出来一个叫“pi”的一个 Pod,这个任务就是来计算这个圆周率,Pod 的名称会以“${job-name}-${random-suffix}”,咱们能够看一下下面 Pod 的 yaml 格式。性能
它比普通的 Pod 多了一个叫 ownerReferences,这个东西来声明此 pod 是归哪一个上一层 controller 来管理。能够看到这里的 ownerReferences 是归 batch/v1,也就是上一个 Job 来管理的。这里就声明了它的 controller 是谁,而后能够经过 pod 返查到它的控制器是谁,同时也能根据 Job 来查一下它下属有哪些 Pod。架构设计
咱们有时候有些需求:但愿 Job 运行的时候能够最大化的并行,并行出 n 个 Pod 去快速地执行。同时,因为咱们的节点数有限制,可能也不但愿同时并行的 Pod 数过多,有那么一个管道的概念,咱们能够但愿最大的并行度是多少,Job 控制器均可以帮咱们来作到。
这里主要看两个参数:一个是 completions,一个是 parallelism。
下面来看一下它的实际运行效果,上图就是当这个 Job 总体运行完毕以后能够看到的效果,首先看到 job 的名字,而后看到它一共建立出来了 8 个 pod,执行了 2 分 23 秒,这是建立的时间。
接着来看真正的 pods,pods 总共出来了 8 个 pod,每一个 pod 的状态都是完成的,而后来看一下它的 AGE,就是时间。从下往上看,能够看到分别有 73s、40s、110s 和 2m26s。每一组都有两个 pod 时间是相同的,即:时间段是 40s 的时候是最后一个建立、 2m26s 是第一个建立的。也就是说,老是两个 pod 同时建立出来,并行完毕、消失,而后再建立、再运行、再完毕。
好比说,刚刚咱们其实经过第二个参数来控制了当前 Job 并行执行的次数,这里就能够了解到这个缓冲器或者说管道队列大小的做用。
下面来介绍另一个 Job,叫作 CronJob,其实也能够叫定时运行 Job。CronJob 其实和 Job 大致是类似的,惟一的不一样点就是它能够设计一个时间。好比说能够定时在几点几分执行,特别适合晚上作一些清理任务,还有能够几分钟执行一次,几小时执行一次等等,这就叫定时任务。
定时任务和 Job 相比会多几个不一样的字段:
schedule:schedule 这个字段主要是设置时间格式,它的时间格式和 Linux 的 crontime 是同样的,因此直接根据 Linux 的 crontime 书写格式来书写就能够了。举个例子: */1 指每分钟去执行一下 Job,这个 Job 须要作的事情就是打印出大约时间,而后打印出“Hello from the kubernetes cluster” 这一句话;
**startingDeadlineSeconds:**即:每次运行 Job 的时候,它最长能够等多长时间,有时这个 Job 可能运行很长时间也不会启动。因此这时,若是超过较长时间的话,CronJob 就会中止这个 Job;
concurrencyPolicy:就是说是否容许并行运行。所谓的并行运行就是,好比说我每分钟执行一次,可是这个 Job 可能运行的时间特别长,假如两分钟才能运行成功,也就是第二个 Job 要到时间须要去运行的时候,上一个 Job 还没完成。若是这个 policy 设置为 true 的话,那么无论你前面的 Job 是否运行完成,每分钟都会去执行;若是是 false,它就会等上一个 Job 运行完成以后才会运行下一个;
**JobsHistoryLimit:**这个就是每一次 CronJob 运行完以后,它都会遗留上一个 Job 的运行历史、查看时间。固然这个额不能是无限的,因此须要设置一下历史存留数,通常能够设置默认 10 个或 100 个均可以,这主要取决于每一个人集群不一样,而后根据每一个人的集群数来肯定这个时间。
下面看一下具体如何使用 Job。
首先看一下 job.yaml。这是一个很是简单的计算 pi 的一个任务。使用 kubectl creat-f job.yaml,这样 job 就能提交成功了。来看一下 kubectl.get.jobs,能够看到这个 job 正在运行;get pods 能够看到这个 pod 应该是运行完成了,那么接下来 logs 一下这个 job 以及 pod。能够看到下图里面打印出来了圆周率。
下面再来看第二个例子:
这个例子就是指刚才的并行运行 Job 建立以后,能够看到有第二个并行的 Job。
如今已经有两个 Pod 正在 running,能够看到它大概执行了快到 30s。
30s 以后它应该会起第二个。
第一批的 pod 已经执行完毕,第二批的 pod 正在 running,每批次分别是两个Pod。也就是说后面每隔 40s 左右,就会有两个 pod 在并行执行,它一共会执行 4 批,共 8 个 pod,等到全部的 pod 执行完毕,就是刚才所说的并行执行的缓冲队列功能。
过一段时间再看这个 pods,能够发现第二批已经执行结束,接下来开始建立第三批······
下面来看第三个例子 —— CronJob。 CronJob 是每分钟执行一次,每次一个 job。
以下图 CronJob 已经建立了,能够经过 get cronjob 来看到当前有一个 CronJob,这个时候再来看 jobs,因为它是每分钟执行一次,因此得稍微等一下。
同时能够看到,上一个 job 还在运行,它的时间是 2m12s 左右,它的完成度是 7/八、6/8,刚刚看到 7/8 到 8/8,也就是说咱们上一个任务执行了最后一步,并且每次都是两个两个地去运行。每次两个运行的 job 都会让咱们在运行一些大型工做流或者工做任务的时候感到特别的方便。
上图中能够看到忽然出现了一个 job,“hello-xxxx”这个 job 就是刚才所说的 CronJob。它距离刚才 CronJob 提交已通过去 1 分钟了,这样就会自动建立出来一个 job,若是不去干扰它的话,它之后大概每一分钟都会建立出来这么一个 job,除非等咱们何时指定它不能够再运行的时候它才会中止建立。
在这里 CronJob 其实主要是用来运做一些清理任务或者说执行一些定时任务。好比说 Jenkins 构建等方面的一些任务,会特别有效。
咱们来看一下 job 的架构设计。Job Controller 其实仍是主要去建立相对应的 pod,而后 Job Controller 会去跟踪 Job 的状态,及时地根据咱们提交的一些配置重试或者继续建立。同时咱们刚刚也提到,每一个 pod 会有它对应的 label,来跟踪它所属的 Job Controller,而且还去配置并行的建立, 并行或者串行地去建立 pod。
上图是一个 Job 控制器的主要流程。全部的 job 都是一个 controller,它会 watch 这个 API Server,咱们每次提交一个 Job 的 yaml 都会通过 api-server 传到 ETCD 里面去,而后 Job Controller 会注册几个 Handler,每当有添加、更新、删除等操做的时候,它会经过一个内存级的消息队列,发到 controller 里面。
经过 Job Controller 检查当前是否有运行的 pod,若是没有的话,经过 Scale up 把这个 pod 建立出来;若是有的话,或者若是大于这个数,对它进行 Scale down,若是这时 pod 发生了变化,须要及时 Update 它的状态。
同时要去检查它是不是并行的 job,或者是串行的 job,根据设置的配置并行度、串行度,及时地把 pod 的数量给建立出来。最后,它会把 job 的整个的状态更新到 API Server 里面去,这样咱们就能看到呈现出来的最终效果了。
下面介绍第二个控制器:**DaemonSet。**一样的问题:若是咱们没有 DaemonSet 会怎么样?下面有几个需求:
DaemonSet 也是 Kubernetes 提供的一个 default controller,它实际是作一个守护进程的控制器,它能帮咱们作到如下几件事情:
下面举个例子来看一下,DaemonSet.yaml 会稍微长一些。
首先是 kind:DaemonSet。若是以前学过 deployment,其实咱们再看这个 yaml 会比较简单。例如它会有 matchLabel,经过 matchLabel 去管理对应所属的 pod,这个 pod.label 也要和这个 DaemonSet.controller.label 想匹配,它才能去根据 label.selector 去找到对应的管理 Pod。下面 spec.container 里面的东西都是一致的。<br /> <br />这里用 fluentd 来作例子。DaemonSet 最经常使用的点在于如下几点内容:
首先是存储,GlusterFS 或者 Ceph 之类的东西,须要每台节点上都运行一个相似于 Agent 的东西,DaemonSet 就能很好地知足这个诉求;
另外,对于日志收集,好比说 logstash 或者 fluentd,这些都是一样的需求,须要每台节点都运行一个 Agent,这样的话,咱们能够很容易搜集到它的状态,把各个节点里面的信息及时地汇报到上面;
还有一个就是,须要每一个节点去运行一些监控的事情,也须要每一个节点去运行一样的事情,好比说 Promethues 这些东西,也须要 DaemonSet 的支持。
建立完 DaemonSet 以后,咱们可使用 kubectl get DaemonSet(DaemonSet 缩写为 ds)。能够看到 DaemonSet 返回值和 deployment 特别像,即它当前一共有正在运行的几个,而后咱们须要几个,READY 了几个。固然这里面,READY 都是只有 Pod,因此它最后建立出来全部的都是 pod。
这里有几个参数,分别是:须要的 pod 个数、当前已经建立的 pod 个数、就绪的个数,以及全部可用的、经过健康检查的 pod;还有 NODE SELECTOR,由于 NODE SELECTOR 在 DaemonSet 里面很是有用。有时候咱们可能但愿只有部分节点去运行这个 pod 而不是全部的节点,因此有些节点上被打了标的话,DaemonSet 就只运行在这些节点上。好比,我只但愿 master 节点运行某些 pod,或者只但愿 Worker 节点运行某些 pod,就可使用这个 NODE SELECTOR。
其实 DaemonSet 和 deployment 特别像,它也有两种更新策略:一个是 RollingUpdate,另外一个是 OnDelete。
RollingUpdate 其实比较好理解,就是会一个一个的更新。先更新第一个 pod,而后老的 pod 被移除,经过健康检查以后再去见第二个 pod,这样对于业务上来讲会比较平滑地升级,不会中断;
OnDelete 其实也是一个很好的更新策略,就是模板更新以后,pod 不会有任何变化,须要咱们手动控制。咱们去删除某一个节点对应的 pod,它就会重建,不删除的话它就不会重建,这样的话对于一些咱们须要手动控制的特殊需求也会有特别好的做用。
下面举一个例子。好比说咱们去改了些 DaemonSet 的镜像,而后看到了它的状态,它就会去一个一个地更新。
上图这个就是刚才 DaemonSet 的 yaml,会比刚才会多一些, 咱们作一些资源的限制,这个都不影响。
下面咱们建立一下 DaemonSet ,而后再看一下它的状态。下图就是咱们刚才看到的 DaemonSet 在 ready 里打出来的状态。
从下图中能够看到,一共有 4 个 pod 被建立出来。为何是 4 个 pod呢?由于只有 4 个节点,因此每一个节点上都会运行一个对应的 pod。
这时,咱们来更新 DaemonSet, 执行完了kubectl apply -f 后,它的 DaemonSet 就已经更新了。接下来咱们去查看 DaemonSet 的更新状态。
上图中能够看到:DaemonSet 默认这个是 RollingUpdate 的,咱们看到是 0-4,如今是 1-4,也就是说它在更新第一个,第一个更新完成会去更新第二个,第二个更新完,就更新第三个······这个就是 RollingUpdate。RollingUpdate 能够作到全自动化的更新,不用有人值守,而是一个一个地去自动更新,更新的过程也比较平滑,这样能够有利于咱们在现场发布或者作一些其余操做。<br />
上图结尾处能够看到,整个的 DaemonSet 已经 RollingUpdate 完毕。
接下来看一下 DaemonSet 架构设计。DaemonSet 仍是一个 controller,它最后真正的业务单元也是 Pod,DaemonSet 其实和 Job controller 特别类似,它也是经过 controller 去 watch API Server 的状态,而后及时地添加 pod。惟一不一样的是,它会监控节点的状态,节点新加入或者消失的时候会在节点上建立对应的 pod,而后同时根据你配置的一些 affinity 或者 label 去选择对应的节点。
最后咱们来看一下 DaemonSet 的控制器,DaemonSet 其实和 Job controller 作的差很少:二者都须要根据 watch 这个 API Server 的状态。如今 DaemonSet 和 Job controller 惟一的不一样点在于,DaemonsetSet Controller须要去 watch node 的状态,但其实这个 node 的状态仍是经过 API Server 传递到 ETCD 上。
当有 node 状态节点发生变化时,它会经过一个内存消息队列发进来,而后DaemonSet controller 会去 watch 这个状态,看一下各个节点上是都有对应的 Pod,若是没有的话就去建立。固然它会去作一个对比,若是有的话,它会比较一下版本,而后加上刚才提到的是否去作 RollingUpdate?若是没有的话就会从新建立,Ondelete 删除 pod 的时候也会去作 check 它作一遍检查,是否去更新,或者去建立对应的 pod。
固然最后的时候,若是所有更新完了以后,它会把整个 DaemonSet 的状态去更新到 API Server 上,完成最后所有的更新
“阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,作最懂云原生开发者的技术公众号。”