离线业务编排详解
在线业务和离线业务
在线业务
Deployment、StatefulSet以及 DaemonSet 这三个编排概念的共同之处是:它们主要编排的对象,都是"在线业务",即:Long Running Task(长作业)。比如常用的 Nginx、Tomcat,以及 MySQL 等等。这些应用一旦运行起来,除非出错或者停止,它的容器进程会一直保持在 Running 状态。
离线业务
也可以叫做Batch Job(计算业务)。这种业务在计算完成后就直接退出了,如果用 Deployment 来管理这种业务,会发现 Pod 会在计算结束后退出,然后被 Deployment Controller 不断地重启
Job解析
什么是Job?
一个用来描述离线业务的 API 对象
Job API 对象的定义
# vim job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: resouer/ubuntu-bc
command: ["sh", "-c", "echo 'scale=10000; 4*a(1)' | bc -l "]
restartPolicy: Never
backoffLimit: 4
Job 对象不要求定义一个 spec.selector 来描述要控制哪些 Pod
spec.template 字段为Pod 模板
运行程序
echo "scale=10000; 4*a(1)" | bc -l
bc 命令:
是 Linux 里的"计算器"
-l 表示:是要使用标准数学库
a(1): 是调用数学库中的 arctangent 函数,计算 atan(1)。这是什么意思呢?
数学知识回顾:
tan(π/4) = 1。所以,4*atan(1)正好就是π,也就是 3.1415926…。
这是一个计算π值的容器。通过 scale=10000,指定了输出的小数点后的位数是 10000。在我的计算机上,这个计算大概用时 1 分 54 秒。
创建Job
kubectl create -f job.yaml
查看 Job 对象# kubectl describe jobs/pi
Name: pi
Namespace: default
Selector: controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
Labels: controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
job-name=pi
Annotations: <none>
Parallelism: 1
Completions: 1
..
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
job-name=pi
Containers:
...
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: pi-rq5rl
为了避免不同 Job 对象所管理的 Pod 发生重合,Job 对象在创建后,它的 Pod 模板,被自动加上了一个 controller-uid=< 一个随机字符串 > 这样的 Label。而这个 Job 对象本身,则被自动加上了这个 Label 对应的 Selector,保证了 Job 与它所管理的 Pod 之间的匹配关系。这种自动生成的 Label 对用户并不友好,不太适合推广到 Deployment 等长作业编排对象上。
Pod 进入了 Running 状态说明它正在计算 Pi 的值
# kubectl get pods
NAME READY STATUS RESTARTS AGE
pi-rq5rl 1/1 Running 0 10s
几分钟后计算结束,这个 Pod 就会进入 Completed 状态# kubectl get pods
NAME READY STATUS RESTARTS AGE
pi-rq5rl 0/1 Completed 0 4m
离线计算的 Pod 永远都不应该被重启
实现方式是在 Pod 模板中定义 restartPolicy=Never
事实上restartPolicy 在 Job 对象里只允许被设置为 Never 和 OnFailure;而在 Deployment 对象里,restartPolicy 则只允许被设置为 Always。
查看 Pod 日志
# kubectl logs pi-rq5rl //可以看到计算得到的 Pi 值已经被打印了出来
3.141592653589793238462643383279...
离线作业失败处理方式
离线作业失败后 Job Controller 就会不断地尝试创建一个新 Pod,这个尝试肯定不能无限进行下去。所以,在 Job 对象的 spec.backoffLimit 字段里定义了重试次数为 4(即,backoffLimit=4,默认值是 6)
Job Controller 重新创建 Pod 的间隔是呈指数增加的,即下一次重新创建 Pod 的动作会分别发生在 10 s、20 s、40 s …后。
如果restartPolicy=OnFailure,离线作业失败后,Job Controller 就不会去尝试创建新的 Pod。但是,它会不断地尝试重启 Pod 里的容器。
# kubectl get pods
NAME READY STATUS RESTARTS AGE
pi-55h89 0/1 ContainerCreating 0 2s
pi-tqbcz 0/1 Error 0 5s
可以看到,这时候会不断地有新 Pod 被创建出来。
spec.activeDeadlineSeconds 字段:
当一个 Job 的 Pod 运行结束后,它会进入 Completed 状态。但是,如果这个 Pod 因为某种原因一直不肯结束呢?
在 Job 的 API 对象里,有一个 spec.activeDeadlineSeconds 字段可以设置最长运行时间,比如:
spec:
backoffLimit: 5
activeDeadlineSeconds: 100
一旦运行超过了 100 s,这个 Job 的所有 Pod 都会被终止。并且,你可以在 Pod 的状态里看到终止的原因是 reason: DeadlineExceeded。
以上,就是一个 Job API 对象最主要的概念和用法
并行作业
离线业务之所以被称为 Batch Job,是因为它们可以以"Batch",也就是并行的方式去运行。
负责并行控制的参数有两个:
spec.parallelism:
定义一个 Job 在任意时间最多可以启动多少个 Pod 同时运行;
spec.completions:
定义 Job 至少要完成的 Pod 数目,即 Job 的最小完成数。
在之前计算 Pi 值的 Job 里,添加这两个参数:
注意:本例只是为了演示 Job 的并行特性,实际用途不大。不过现实中确实存在很多需要并行处理的场景。比如批处理程序,每个副本(Pod)都会从任务池中读取任务并执行,副本越多,执行时间就越短,效率就越高。这种类似的场景都可以用 Job 来实现。
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
parallelism: 2
completions: 4
template:
spec:
containers:
- name: pi
image: resouer/ubuntu-bc
command: ["sh", "-c", "echo 'scale=5000; 4*a(1)' | bc -l "]
restartPolicy: Never
backoffLimit: 4
这样,我们就指定了这个 Job 最大的并行数是 2,而最小的完成数是 4。
创建这个 Job 对象
# kubectl create -f job.yaml
这个 Job 其实也维护了两个状态字段,即 DESIRED 和 SUCCESSFUL,如下所示:
# kubectl get job //注意,最新版本的子段已经不一样了 NAME DESIRED SUCCESSFUL AGE pi 4 0 3s
其中,DESIRED 的值,正是 completions 定义的最小完成数。
这个 Job 首先创建了两个并行运行的 Pod 来计算 Pi:
# kubectl get pods NAME READY STATUS RESTARTS AGE pi-5mt88 1/1 Running 0 6s pi-gmcq5 1/1 Running 0 6s
而在 40 s 后,这两个 Pod 相继完成计算。
这时可以看到,每当有一个 Pod 完成计算进入 Completed 状态时,就会有一个新的 Pod 被自动创建出来,并且快速地从 Pending 状态进入到 ContainerCreating 状态:
# kubectl get pods NAME READY STATUS RESTARTS AGE pi-gmcq5 0/1 Completed 0 40s pi-84ww8 0/1 Pending 0 0s pi-5mt88 0/1 Completed 0 41s pi-62rbt 0/1 Pending 0 0s
# kubectl get pods NAME READY STATUS RESTARTS AGE pi-gmcq5 0/1 Completed 0 40s pi-84ww8 0/1 ContainerCreating 0 0s pi-5mt88 0/1 Completed 0 41s pi-62rbt 0/1 ContainerCreating 0 0s
紧接着,Job Controller 第二次创建出来的两个并行的 Pod 也进入了 Running 状态:
# kubectl get pods NAME READY STATUS RESTARTS AGE pi-5mt88 0/1 Completed 0 54s pi-62rbt 1/1 Running 0 13s pi-84ww8 1/1 Running 0 14s pi-gmcq5 0/1 Completed 0 54s
最终,后面创建的这两个 Pod 也完成了计算,进入了 Completed 状态。
这时,由于所有的 Pod 均已经成功退出,这个 Job 也就执行完了,所以你会看到它的 SUCCESSFUL 字段的值变成了 4:
# kubectl get pods NAME READY STATUS RESTARTS AGE pi-5mt88 0/1 Completed 0 5m pi-62rbt 0/1 Completed 0 4m pi-84ww8 0/1 Completed 0 4m pi-gmcq5 0/1 Completed 0 5m
# kubectl get job NAME DESIRED SUCCESSFUL AGE pi 4 4 5m
Job Controller工作原理总结
Job Controller 控制的对象,直接就是 Pod。
Job Controller 在控制循环中进行的调谐(Reconcile)操作,是根据实际在 Running 状态 Pod 的数目、已经成功退出的 Pod 的数目,以及 parallelism、completions 参数的值共同计算出在这个周期里,应该创建或者删除的 Pod 数目,然后调用 Kubernetes API 来执行这个操作。
在上面计算 Pi 值的这个例子中,当 Job 一开始创建出来时,实际处于 Running 状态的 Pod 数目 =0,已经成功退出的 Pod 数目 =0,而用户定义的 completions,也就是最终用户需要的 Pod 数目 =4。
所以,在这个时刻,需要创建的 Pod 数目 = 最终需要的 Pod 数目 - 实际在 Running 状态 Pod 数目 - 已经成功退出的 Pod 数目 = 4 - 0 - 0= 4。也就是说,Job Controller 需要创建 4 个 Pod 来纠正这个不一致状态。
可是,又定义了这个 Job 的 parallelism=2 规定了每次并发创建的 Pod 个数不能超过 2 个。所以,Job Controller 会对前面的计算结果做一个修正,修正后的期望创建的 Pod 数目应该是:2 个。
这时候,Job Controller 就会并发地向 kube-apiserver 发起两个创建 Pod 的请求。
类似地,如果在这次调谐周期里,Job Controller 发现实际在 Running 状态的 Pod 数目,比 parallelism 还大,那么它就会删除一些 Pod,使两者相等。
综上所述,Job Controller 实际上控制了,作业执行的并行度,以及总共需要完成的任务数这两个重要参数。而在实际使用时,你需要根据作业的特性,来决定并行度(parallelism)和任务数(completions)的合理取值。
Job控制器CronJob
Job 对象:CronJob
CronJob 描述的是定时任务,CronJob 是一个 Job 对象的控制器(Controller)
CronJob的 API 对象,如下所示:
# vim ./cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: daocloud.io/library/busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
CronJob 与 Job 的关系,同 Deployment 与 Pod 的关系一样。
CronJob 是一个专门用来管理 Job 对象的控制器。它创建和删除 Job 的依据,是 schedule 字段定义的、一个标准的Unix Cron格式的表达式。
比如:
*/1 * * * * 分钟、小时、日、月、星期
这个 Cron 表达式里 _/1 中的 _ 表示从 0 开始,/ 表示"每",1 表示偏移量。所以,它的意思就是:从 0 开始,每 1 个时间单位执行一次。
本例表示从当前开始,每分钟执行一次
这里要执行的内容,就是 jobTemplate 定义的 Job 。
这个 CronJob 对象在创建 1 分钟后,就会有一个 Job 产生了,如下所示:
# kubectl create -f ./cronjob.yaml cronjob "hello" created
一分钟后 # kubectl get jobs NAME DESIRED SUCCESSFUL AGE hello-4111706356 1 1 2s 此时,CronJob 对象会记录下这次 Job 执行的时间:新版本中在describe里显示执行时间 # kubectl get cronjob hello NAME SCHEDULE SUSPEND ACTIVE LAST-SCHEDULE hello */1 * * * * False 0 Thu, 6 Sep 2018 14:34:00 -070
————————————————
版权声明:本文为CSDN博主「小李不怕黑」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Mwb_lhm/article/details/135118156# kubectl create -f ./cronjob.yaml cronjob "hello" created
一分钟后 # kubectl get jobs NAME DESIRED SUCCESSFUL AGE hello-4111706356 1 1 2s 此时,CronJob 对象会记录下这次 Job 执行的时间:新版本中在describe里显示执行时间 # kubectl get cronjob hello NAME SCHEDULE SUSPEND ACTIVE LAST-SCHEDULE hello */1 * * * * False 0 Thu, 6 Sep 2018 14:34:00 -070
spec.concurrencyPolicy 字段:
由于定时任务的特殊性,很可能某个 Job 还没有执行完,另外一个新 Job 就产生了。这时候,可以通过 spec.concurrencyPolicy 字段来定义具体的处理策略。
concurrencyPolicy=Allow
默认情况,表示这些 Job 可以同时存在;
concurrencyPolicy=Forbid
表示不会创建新的 Pod,该创建周期被跳过;
concurrencyPolicy=Replace
表示新产生的 Job 会替换旧的、没有执行完的 Job。
spec.startingDeadlineSeconds 字段:
如果某一次 Job 创建失败,这次创建就会被标记为"miss"。当在指定的时间窗口内,miss 的数目达到 100 时,那么 CronJob 会停止再创建这个 Job。
这个时间窗口,可以由 spec.startingDeadlineSeconds 字段指定。比如 startingDeadlineSeconds=200,意味着在过去 200 s 里,如果 miss 的数目达到了 100 次,那么这个 Job 就不会被创建执行了。
k8s之共享存储pv&pvc
存储资源管理
在基于k8s容器云平台上,对存储资源的使用需求通常包括以下几方面:
1.应用配置文件、密钥的管理; 2.应用的数据持久化存储; 3.在不同的应用间共享数据存储;
k8s的Volume抽象概念就是针对这些问题提供的解决方案,k8s的volume类型非常丰富,从临时目录、宿主机目录、ConfigMap、Secret、共享存储(PV和PVC)。
k8s支持Volume类型包括以下几类:
1.临时空目录(随着Pod的销毁而销毁)
emptyDir:2.配置类(将配置文件以Volume的形式挂载到容器内)
ConfigMap:将保存在ConfigMap资源对象中的配置文件信息挂载到容器内的某个目录下Secret:将保存在Secret资源对象中的密码密钥等信息挂载到容器内的某个文件中。
downwardAPI:将downwardAPI的数据以环境变量或文件的形式注入容器中。
gitErpo:将某Git代码库挂载到容器内的某个目录下
3.本地存储类
hostpath:将宿主机的目录或文件挂载到容器内进行使用。local:Kubernetes从v1.9版本引入,将本地存储以PV的形式提供给容器使用,并能够实现存储空间的管理。
4.共享存储
PV(Persistent Volume):将共享存储定义为一种“持久存储卷”,可以被多个容器应用共享使用。PVC(Persistent Volume Claim):用户对存储资源的一次“申请”,PVC申请的对象是PV,一旦申请成功,应用就能够像使用本地目录一样使用共享存储了。
共享存储主要用于多个应用都能够使用存储资源,例如NFS存储、光纤存储Glusterfs共享文件系统等,在k8s系统中通过PV/StorageClass和PVC来完成定义,并通过volumeMount挂载到容器的目录或文件进行使用。
ConfigMap、Secret、emptyDir、hostPath等属于临时性存储,当pod被调度到某个节点上时,它们随pod的创建而创建,临时占用节点存储资源,当pod离开节点时,存储资源被交还给节点,pod一旦离开这个节点,存储就失效,不具备持久化存储数据的能力。与此相反,持久化存储拥有独立的生命周期,具备持久化存储能力,其后端一般是独立的存储系统如NFS、iSCSI、cephfs、glusterfs等。
pv与pvc
Kubernetes中的node代表计算资源,而PersistentVolume(PV)则代表存储资源,它是对各种诸如NFS、iSCSI、云存储等各种存储后端所提供存储块的统一抽象从而提供网络存储,通过它屏蔽低层实现细节。与普通volume不同,PV拥有完全独立的生命周期。
因为PV表示的是集群能力,它是一种集群资源,所以用户(通常是开发人员)不能直接使用PV,就像不能直接使用内存一样,需要先向系统申请,而 PVC 就是请求存储资源的。
Kubernetes通过PersistentVolumeClaim(PVC)代理用户行为,用户通过对PVC的操作实现对PV申请、使用、释放等操作,PVC是用户层面的资源。PV 和 PVC 可以将 pod 和数据卷解耦,pod 不需要知道确切的文件系统或者支持它的持久化引擎。
Kubernetes的共享存储供应模式包括静态和动态两种模式
静态模式
静态PV由系统管理员负责创建、提供、维护,系统管理员为用户屏蔽真正提供存储的后端及其实现细节,普通用户作为消费者,只需通过PVC申请、使用此类资源。
动态模式:
集群管理员无须手工创建PV,而是通过对Storage Class的设置对后端存储进行描述,“storage class”可以理解成某种具体的后端存储,标记为某种“类型(Class)”,此时要求PVC对存储的类型进行声明,系统将自动完成PV的创建与PVC的绑定。如果用户的PVC中“storage class”的值为"",则表示不能为此PVC动态创建PV。PV与PVC的绑定
用户创建包含容量、访问模式等信息的PVC,向系统请求存储资源。系统查找已存在PV或者监控新创建PV,如果与PVC匹配则将两者绑定。如果PVC创建动态PV,则系统将一直将两者绑定。PV与PVC的绑定是一一对应关系,不能重复绑定。如果系统一直没有为PVC找到匹配PV,则PVC无限期维持在"unbound"状态,直到系统找到匹配PV。实际绑定的PV容量可能大于PVC中申请的容量。
持久卷pv的类型
#PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件 - GCEPersistentDisk - AWSElasticBlockStore - AzureFile - AzureDisk - FC (Fibre Channel) - Flexvolume - Flocker - NFS - iSCSI - RBD (Ceph Block Device) - CephFS - Cinder (OpenStack block storage) - Glusterfs - VsphereVolume - Quobyte Volumes - HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster) - Portworx Volumes - ScaleIO Volumes - StorageOS