Edited at

CronJob(ScheduledJob)

More than 1 year has passed since last update.

このエントリーは Kubernetes Advent Calendar 2016の4日目の記事です。

CronJobs について、キャッチアップしたことを書いて行きたいと思います。

from. http://kubernetes.io/docs/user-guide/cron-jobs/


CronJob(ScheduledJob) とは?

時間ベースのジョブを管理するもの。

所謂Cronみたいに特定の一回だったり、繰り返し実行することができるものです。


ユースケース


  • メール送信

  • バッチ処理

  • 定期的に解析

  • 夜間にバックアップ処理


Kubernetes のバージョンによって名前が違う


  • 1.4 Scheduledjob

  • 1.5 CronJob


利用するための前提条件


You need a working Kubernetes cluster at version >= 1.4 (for ScheduledJob), >= 1.5 (for CronJobs), with batch/v2alpha1 API turned on by passing --runtime-config=batch/v2alpha1 while bringing up the API server (see Turn on or off an API version for your cluster for more). You cannot use Cron Jobs on a hosted Kubernetes provider that has disabled alpha resources.


バージョンで名前が違うのは前述とおりです。

ここでは更にkubernetes の API Server を立ち上げる際に --runtime-config=batch/v2alpha1 を付けることが必要です。


  • 例: launch-k8s-on-aws.sh

#!/bin/bash

# Turned on v2aplpha1

export KUBE_RUNTIME_CONFIG="batch/v2alpha1=true"

# Target aws config

export KUBERNETES_PROVIDER=aws
export AWS_DEFAULT_PROFILE=default

export KUBE_AWS_ZONE=ap-northeast-1c
export AWS_S3_REGION=ap-northeast-1

export NODE_SIZE=c4.large
export MASTER_SIZE=c4.large
export AWS_S3_BUCKET=kubernetes-145

# Kubernetes config

export NUM_NODES=5
export KUBE_AWS_INSTANCE_PREFIX=k8s-145

# Disk size

export MASTER_DISK_SIZE=50
export MASTER_ROOT_DISK_SIZE=50
export NODE_ROOT_DISK_SIZE=50

# Download Kubernetes and automatically set up a default cluster

wget -q -O - https://get.k8s.io | bash


実際に manifest file を書いて実行してみよう


  • hello world 的な物

apiVersion: batch/v2alpha1

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


  • cronjob 作成

$ kubectl create -f examples/cronjob.yaml 

cronjob "hello" created


  • cronjob 確認

$ kubectl get cronjob hello

NAME SCHEDULE SUSPEND ACTIVE LAST-SCHEDULE
hello */1 * * * * False 0 Mon, 29 Aug 2016 14:34:00 -0700


  • job 確認

$ kubectl get job

NAME DESIRED SUCCESSFUL AGE
hello-1705740100 1 1 32d



  • kubectl describe job するとさらに情報がわかる。

$ kubectl describe job hello-1705740100 

Name: hello-1705740100
Namespace: default
Image(s): busybox
Selector: controller-uid=93a273e1-9fdf-11e6-b40b-06daaf3c2469
Parallelism: 1
Completions: 1
Start Time: Tue, 01 Nov 2016 12:02:08 +0900
Labels: <none>
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
No volumes.
No events.



  • Selector がわかるので、 Pod がわかる

$ kubectl get po -l controller-uid=93a273e1-9fdf-11e6-b40b-06daaf3c2469 --show-all

NAME READY STATUS RESTARTS AGE
job-task-latest-1553958718-mg5r8 0/1 Error 0 45s
job-task-latest-1553958718-r65iv 0/1 Error 0 41s


  • cronjob 削除

$ kubectl delete cronjob hello

cronjob "hello" deleted


  • job 削除

$ kubectl get jobs

NAME DESIRED SUCCESSFUL AGE
hello-1201907962 1 1 11m
hello-1202039034 1 1 8m
...

$ kubectl delete jobs hello-1201907962 hello-1202039034 ...
job "hello-1201907962" deleted
job "hello-1202039034" deleted
...

ref. https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/scheduledjob.md


オプション


Cronjob



  • .spec.schedule: cron format でスケジュールを指定する


  • .spec.jobTemplate: Job を定義する


  • .spec.startingDeadlineSeconds: CronJob で何かしら実行されなかった時にいつまで実行可能か期限を決める。


  • .spec.concurrencyPolicy: 並列して実行するポリシーを決めるもの。前の job と被って実行されてもいいのか気を付ける。



    • Allow(default): 並列して実行を許可


    • Forbid: 前回がまだ終了していない場合、次回の実行をスキップする


    • Replace: 現在実行中のジョブをキャンセルし、新しいジョブに置き換えます




  • .spec.suspend: trueに設定すると、次回以降に中断され実行されない。既に開始されているものはそのまま実行される。


job



  • .spec.jobTemplate.spec.activeDeadlineSeconds: 失敗する際にデッドラインを決める(デフォルトは無期限だから失敗するコンテナを書くと、ずっとrestartされる)


job の考え方

ref. Handling Pod and Container Failures


A Container in a Pod may fail for a number of reasons, such as because the process in it exited with a non-zero exit code, or the Container was killed for exceeding a memory limit, etc. If this happens, and the .spec.template.containers[].restartPolicy = "OnFailure", then the Pod stays on the node, but the Container is re-run. Therefore, your program needs to handle the case when it is restarted locally, or else specify .spec.template.containers[].restartPolicy = "Never". See pods-states for more information on restartPolicy.

An entire Pod can also fail, for a number of reasons, such as when the pod is kicked off the node (node is upgraded, rebooted, deleted, etc.), or if a container of the Pod fails and the .spec.template.containers[].restartPolicy = "Never". When a Pod fails, then the Job controller starts a new Pod. Therefore, your program needs to handle the case when it is restarted in a new pod. In particular, it needs to handle temporary files, locks, incomplete output and the like caused by previous runs.

Note that even if you specify .spec.parallelism = 1 and .spec.completions = 1 and .spec.template.containers[].restartPolicy = "Never", the same program may sometimes be started twice.

If you do specify .spec.parallelism and .spec.completions both greater than 1, then there may be multiple pods running at once. Therefore, your pods must also be tolerant of concurrency.



  • 基本的に、Jobは何回実行しても結果が同じになることを期待している設計になっている。


  • .spec.template.containers[].restartPolicy = "Never" はあくまでも Pod 内の話であり、Job 事態が完了していないのであれば、新しく Pod を作ってプログラムを実行する


  • .spec.parallelism = 1.spec.completions = 1.spec.template.containers[].restartPolicy = "Never" を指定しても、同じプログラムが2回起動される可能性がある


こんな使い方が出来る


kubectl をクラスタ内部で実行

例えば kubectl set コマンドを使ってコンテナのイメージを替える

apiVersion: batch/v2alpha1

kind: CronJob
metadata:
name: replace-task-latest
labels:
name: app-server-scheduledjob
role: kubectl
namespace: app-server
spec:
schedule: "0 2,6,10,14,18,22 * * *" # every 4 hours (shifted by 2 hours)
startingDeadlineSeconds: 30
concurrencyPolicy: "Forbid"
suspend: false
jobTemplate:
metadata:
name: app-server-scheduledjob
labels:
name: app-server-scheduledjob
role: kubectl
spec:
template:
spec:
containers:
- name: app-server-scheduledjob
image: koudaiii/kubectl
command:
- "sh"
- "-c"
- >
echo "kubectl set image deployment/app-server app-server=app-server:latest --namespace=app-server --record";
kubectl set image deployment/app-server app-server=app-server:latest --namespace=app-server --record
restartPolicy: Never


manifest file を更新する


  • kubernetes/app-server.yaml

apiVersion: batch/v1

kind: Job
metadata:
name: pi
spec:
template:
metadata:
name: pi
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never


  • 書いた manifest file を configmap に置く

kubectl create configmap app-server --from-file=kubernetes/app-server.yaml


  • 定期的に実行させる

apiVersion: batch/v2alpha1

kind: CronJob
metadata:
name: job-task-latest
labels:
name: app-server-scheduledjob
role: kubectl
spec:
schedule: "*/5 * * * *"
startingDeadlineSeconds: 30
concurrencyPolicy: "Forbid"
suspend: false
jobTemplate:
metadata:
name: app-server-scheduledjob
labels:
name: app-server-scheduledjob
role: kubectl
spec:
template:
spec:
containers:
- name: app-server-scheduledjob
image: koudaiii/kubectl
command: ["kubectl","replace","-f","tmp/app-server.yaml"]
volumeMounts:
- name: deployment
mountPath: /tmp
readOnly: true
restartPolicy: Never
volumes:
- name: deployment
configMap:
name: app-server


気をつける点

ここからは、実際に使ってみてハマったものをまとめました。


実行はあくまでも UTC?

NAME                                 SCHEDULE     SUSPEND   ACTIVE    LAST-SCHEDULE

people--ios-event-appsflyer-screen 0 16 * * * False 0 Sat, 03 Dec 2016 01:00:00 +0900

LASTをみると日本時間で表示されるが Scheduler が実行されるのはUTCになってる。

もしかすると、 get.k8s.io で作る場合はUTCなのかもしれない。get.k8s.ioで特に指定するところが GitHub の kubernetes/cluster/* からはなく、ここは自分で Cluster を作ってみないとわからない感じです。


二回目の kubectl apply で失敗する

# scheduledjob.yaml

# From http://kubernetes.io/docs/user-guide/scheduled-jobs/#creating-a-scheduled-job
apiVersion: batch/v2alpha1
kind: ScheduledJob
metadata:
name: hello
spec:
schedule: 0/1 * * * ?
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure


  • 1回目は成功

$ kubectl apply -f scheduledjob.yaml 

scheduledjob "hello" created


  • 2回目に失敗する。

$ kubectl apply -f scheduledjob.yaml

error: error when applying patch:

to:
&{0xc8200bd740 0xc8202f8ee0 sandbox hello manifests.yaml XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Travis CI 経由で Job をまとめて kubectl apply -f ./dir/ とかやろうとした際にこの不具合にあたった。


Job が already exists で時々実行されない

Error creating job: jobs.batch "es-cleanup-1629652801" already exists

getTimeHash の実装が原因で hourly とか設定すると時々名前が被ってJobが失敗する

getTimeHash(scheduledTime)

Date
Epoch
Hash
Hits

Nov 14 00:00:00
1479081600
4175643772
1

Nov 14 00:05:00
1479081900
4209853567
1

Nov 14 00:10:00
1479082200
4141499513
1

Nov 14 00:15:00
1479082500
4175709308
1

Nov 14 00:20:00
1479082800
4209919103
1

Nov 14 00:25:00
1479083100
4244128898
1

Nov 14 00:30:00
1479083400
4072621171
1

Nov 14 00:35:00
1479083700
4106830966
1

Nov 14 00:40:00
1479084000
4141040761
1

Nov 14 00:45:00
1479084300
4072686707
1

Nov 14 00:50:00
1479084600
4106896502
1

Nov 14 00:55:00
1479084900
4141106297
1

Nov 14 01:00:00
1479085200
4072752243
1

Nov 14 01:05:00
1479085500
4106962038
1

Nov 14 01:10:00
1479085800
4141171833
1

Nov 14 01:15:00
1479086100
4175381628
1

Nov 14 01:20:00
1479086400
4107027574
1

Nov 14 01:25:00
1479086700
4141237369
1

Nov 14 01:30:00
1479087000
4175447164
1

Nov 14 01:35:00
1479087300
4107093110
1

Nov 14 01:40:00
1479087600
4141302905
1

Nov 14 01:45:00
1479087900
4175512700
1

Nov 14 01:50:00
1479088200
4107158646
1

Nov 14 01:55:00
1479088500
4141368441
1

Nov 14 02:00:00
1479088800
4175578236
1

Nov 14 02:05:00
1479089100
4209788031
1

Nov 14 02:10:00
1479089400
4141433977
1

Nov 14 02:15:00
1479089700
4175643772
2

Nov 14 02:20:00
1479090000
4209853567
2

Nov 14 02:25:00
1479090300
4141499513
2

    // We want job names for a given nominal start time to have a deterministic name to avoid the same job being created twice

name := fmt.Sprintf("%s-%d", sj.Name, getTimeHash(scheduledTime))

Unix Epoch Time を返すように変更されている。次のリリースで治りそう。


まとめ

実際に使ってみて、クラスタリング上で job が実行されるメリットは大きいです。

スケジュールを実行するため専用のサーバーを作ったりしていると思いますが、その必要がなくなりより効率的に全体のリソースを使えるようになるからです。

アプリケーションエンジニアの技術選定の幅がより広がるので、Cronjobは今後も追っていきたいと思います。