zoukankan      html  css  js  c++  java
  • 【Kubernetes】离线业务:Job与CronJob

      Deployment、StatefulSet和DaemonSet这三个编排概念编排的对象主要都是在线业务(Long Running Task,这些应用一旦运行起来,除非出错或者停止,它的容器进程会一直保持在Running状态)。

      但是对于离线业务(Batch Job,计算业务)在计算完成后就直接退出了,如果依然使用Deployment来管理,就会发现Pod会在计算结束后退出,然后被Deployment Controller不断重启。

      在Kubernetes v1.4版本之后,设计出来一个用来描述离线业务的API对象:Job。

    举个Job的栗子

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: pi
    spec:
      template:
        spec:
          containers:
          - name: pi
            image: resouer/ubuntu-bc   #安装了ba明了的Ubuntu镜像
            command: ["sh", "-c", "echo 'scale=10000; 4*a(1)' | bc -l "] #bc命令是Linux里的计算器,; -l表示使用标准数学课; a(1)调用arctangent函数,scale指定小数点后的位数
          restartPolicy: Never
      backoffLimit: 4

      创建这个Pod

    $ kubectl create -f job.yaml
    $ 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模板被自动加上了一个Labels, controller-uid=<一个随机的字符串>。而Job对象本身,则被自动加上了这个Label对应的Selector,从而保证了Job与它所管理的Pod之间的匹配关系。

       Job Controller之所以要使用这种携带了UID的Label,就是为了避免不同Job对象管理的Pod重合

    ###  接下来可以看到这个Job创建的Pod进入了Running状态,这意味着它正在计算Pi的值
    
    $ kubectl get pods
    NAME                                READY     STATUS    RESTARTS   AGE
    pi-rq5rl                            1/1       Running   0          10s
    
    ###计算结束后,Pod会进入Completed状态,这也是需要在Pod模板中定义restartPolicy=Never的原因:离线计算的Pod永远不应该被重启
    
    $ kubectl get pods
    NAME                                READY     STATUS      RESTARTS   AGE
    pi-rq5rl                            0/1       Completed   0          4m

    ### 通过查看log可以看到计算的值

      $ kubectl logs pi-rq5rl
      3.141592653589793238462643383279...

      那如果这个离线作业失败了怎么办?因为这个例子中定义了restartPolicy=Never,那离线作业失败后Job Controller就会不断地尝试创建一个新Pod

      Job对象的spec.backoffLimit字段限制尝试的次数,在例子中定义为4,默认值为6。Job Controller重新创建Pod的间隔是呈倍数增加的,即下一次重新创建Pod的动作会分别发生在10s,20s,40s……

      那如果定义的restartPolicy=OnFailure呢?离线作业失败后,Job Controller就不会去尝试创建新的Pod,但是它会不断尝试重启Pod里的容器。

      那如果这个Pod一直不肯结束呢?spec.activeDeadlineSeconds字段可以设置最长运行时间

    spec:
     backoffLimit: 5
     activeDeadlineSeconds: 100   # 运行超过100s,这个Job的所有Pod都会被终止

      

     Job Controller 对并行作业的控制方法

      在Job对象中,负责并行控制的参数有两个:

    spec.parallelism:定义一个Job在任意时间最多可以启动多少个Pod同时运行

    spec.completions:定义Job至少要玩的Pod的数目

      再举个栗子

    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

      指定了最大并行数是2,最小完成数是4

    ### 创建Job对象
    $ kubectl create -f job.yaml
    
    
    ### Job维护两个字段,DESIRED即最小完成数
    $ kubectl get job
    NAME      DESIRED   SUCCESSFUL   AGE
    pi        4         0            3s
    
    ### 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
    
    ### 当一个Pod完成计算会进入Completed状态,就会有一个新的Pod被创建出来,并且快速地从Pending状态进入ContainerCreating状态,再到Running状态,最后完成
    $ 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
    
    $ 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
    
    $ 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
    
    ### 所有Pod均已成功退出,SUCCESSFUL字段值变成4
    $ kubectl get job
    NAME      DESIRED   SUCCESSFUL   AGE
    pi        4         4            5m

      通过上述DESIRED和SUCCESSFUL字段的关系,可以看出Job Controller控制的直接对象是Pod,Job Controller在控制循环中进行的调谐(Reconcile)操作,是根据实际在Running状态Pod数目、已经成功推出的Pod数目,以及parallelism、completions参数的值共同计算在这个周期里,应该创建或删除的Pod数目,然后调用Kubernetes API来执行这个操作 

    Job对象使用方法

    1、外部管理器 + Job模板

      把Job的YAML文件定义为一个模板,然后用一个外部工具控制这些模板来生成Job 。Job的定义方式如下所示:

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: process-item-$ITEM
      labels:
        jobgroup: jobexample
    spec:
      template:
        metadata:
          name: jobexample
          labels:
            jobgroup: jobexample
        spec:
          containers:
          - name: c
            image: busybox
            command: ["sh", "-c", "echo Processing item $ITEM && sleep 5"]  ### 定义了$ITEM变量
          restartPolicy: Never

      在控制这种Job时,只要注意如下两个方面即可

      • 创建Job时,替换掉$ITEM这样的变量
      • 所有来自同一个目标的Job,都有一个jobgroup: jobexample标签,也就是说这一组Job使用这样一个相同的标识
    ### 第一点可以通过shell把¥ITEM替换掉
    $ mkdir ./jobs
    $ for i in apple banana cherry
    do
      cat job-tmpl.yaml | sed "s/$ITEM/$i/" > ./jobs/job-$i.yaml
    done
    
    
    ### 创建Job
    $ kubectl create -f ./jobs
    $ kubectl get pods -l jobgroup=jobexample
    NAME                        READY     STATUS      RESTARTS   AGE
    process-item-apple-kixwv    0/1       Completed   0          4m
    process-item-banana-wrsf7   0/1       Completed   0          4m
    process-item-cherry-dnfu9   0/1       Completed   0          4m

      这种模式虽然看起很傻,但却很普遍,因为大多数用户在需要管理Batch Job时,都已经有自家的一套方案,需要做的往往就是集成工作。这时候Kubernetes对这些方案最有价值的就是Job这个API对象,因此只需要编写一个外部工具(如上面for循环)来管理这些Job即可。

      这种模式下使用Job对象,completions和parallelism这两个字段都应该使用默认值1,作业Pod的并行控制,应该完全交由外部工具来进行管理

     

    2、拥有固定任务数目的并行Job

       只关心最后是否有指定数目(spec.completions)个任务成功推出,不关心执行时的并行度多少。

      比如上面计算pi的例子,或者可以不指定parallelism

     3、指定并行度(parallelism),不设置固定的completions的值

       任务总数未知,需要决定什么时候启动Pod,什么时候Job才算执行完成。因此需要一个工作队列来负责任务分发,还需要能够判断工作队列已经为空(所有工作已经结束)

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: job-wq-2
    spec:
      parallelism: 2
      template:
        metadata:
          name: job-wq-2
        spec:
          containers:
          - name: c
            image: gcr.io/myproject/job-wq-2
            env:
            - name: BROKER_URL
              value: amqp://guest:guest@rabbitmq-service:5672  ##工作队列采用RabbitMQ
            - name: QUEUE
              value: job2
          restartPolicy: OnFailure

      这个Pod执行逻辑为

    /* job-wq-2 的伪代码 */
    for !queue.IsEmpty($BROKER_URL, $QUEUE) {
      task := queue.Pop()
      process(task)
    }
    print("Queue empty, exiting")
    exit

    CronJob

      CronJob描述的是定时任务,举个栗子

    apiVersion: batch/v1beta1
    kind: CronJob
    metadata:
      name: hello
    spec:
      schedule: "*/1 * * * *"
      jobTemplate:
        spec:
          template:
            spec:
              containers:
              - name: hello
                image: busybox
                args:
                - /bin/sh
                - -c
                - date; echo Hello from the Kubernetes cluster
              restartPolicy: OnFailure

      在这个YAML文件中,最重要的关键词就是jobTemplate,即一个Job对象的控制器(Controller)。如Deployment和Pod的关系一样,CronJob是一个专门用来管理Job对象的控制器。

      CronJob创建和删除Job的依据是schedule字段定义的、一个标准的Unix Cron格式的表达式

      如,“*/1****”,这个Cron表达式里*/1中的*表示从0开始,/表示每,1表示偏移量,它的意思就是从零开始,每1个时间单位执行一次

      那时间单位又是什么意思?Cron表达式中的五个部分分别代表:分钟、小时、日、月、星期。因此上面的意思就是每分钟执行一次。

    $ kubectl create -f ./cronjob.yaml
    cronjob "hello" created
    
    # 一分钟后
    $ kubectl get jobs
    NAME               DESIRED   SUCCESSFUL   AGE
    hello-4111706356   1         1         2s
    
    ##CronJob对象会记录下这次Job的执行时间
    $ kubectl get cronjob hello
    NAME      SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
    hello     */1 * * * *   False     0         Thu, 6 Sep 2018 14:34:00 -070

      另外由于定时任务的特殊性,很可能某个Job还没有执行完,另外一个新的Job就产生了,这时候可以通过spec.concurrencyPolicy字段来定义具体的处理策略

      • concurrencyPolicy=Allow:默认情况,Job可以同时存在;
      • concurrencyPolicy=Forbid:不会创建新的Jod,该创建周期被跳过
      • concurrencyPolicy=Replace:新产生的Job会替换旧的、没有执行完的Job

      如果一个Job创建失败,这次创建就会被标记为“miss”,当在指定的时间窗口内,miss的数目达到100时,那么CronJob会停止创建这个Job。这个时间窗口可以由spec.startingDeadlineSeconds字段指定。

      例如,startingDeadlineSeconds=200,意味着在过去的200s里,如果miss的数目到达了100次,那么这个Job就不会被创建执行了。

  • 相关阅读:
    游标
    mobaxterm安装与使用(root_35/37/36/121)
    美团笔试--修改矩阵
    美团笔试1--螺旋矩阵
    assert函数
    2019年头条笔试--围坐分糖果
    邻值查找——stl函数lower_bound和upper_bound的使用
    动态规划练习1--腾讯暑期实习正式批
    Windows 7下硬盘安装CentOS6.4的解决方法
    Sublime Text 3 的一些基本使用方法和配置
  • 原文地址:https://www.cnblogs.com/yuxiaoba/p/9797331.html
Copyright © 2011-2022 走看看