Pular para o conteúdo principal

Jobs e CronJobs

Uma aplicação que vive um curto período de tempo que se destina a uma tarefa e depois morrer é considerada um job.

Alguns exemplos.

  • Gerar um relatório
  • Processar um cálculo complexo ou uma imagem
  • Enviar emails

Se fossemos fazer isso com um docker poderíamos fazer uma operação matemática simples.

docker run --name soma ubuntu expr 3 + 2
5
docker ps -a | grep soma
d813cef7ec93 ubuntu "expr 3 + 2" About a minute ago Exited (0) About a minute ago soma

O container não está mais rodando, ele executou e saiu com o exit code 0, ou seja, fez o que tinha que fazer.

Esse é o lifecycle de muitos pods, mas podemos separar esse tipo de pod como Jobs.

Se fossemos fazer isso usando um pod o que aconteceria?

❯ kubectl run math-pod --image ubuntu --dry-run=client -o yaml --command expr 3 + 2 > math-pod.yaml

cat math-pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: math-pod
name: math-pod
spec:
containers:
- command:
- "expr"
- "3"
- "+"
- "2"
image: ubuntu
name: math-pod
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

# Ele executa mas fica restartando toda hora, pois o kubernetes fica tentando trazer ele novamente
❯ k get pods math-pod
NAME READY STATUS RESTARTS AGE
math-pod 0/1 Completed 3 (28s ago) 47s

O Kubernetes quer que suas aplicações vivam para sempre. O comportamento padrão de um pod é tentar reiniciar o pod para mantê-lo rodando caso ele esteja parado. Onde podemos confirmar isso.

kubectl get pod math-pod -o yaml | grep restartPolicy
restartPolicy: Always

Se tivéssemos definido para Never ou OnFailure isso não aconteceria.

Poderíamos ter vários containers processando em paralelo dentro de um pod, inclusive usar recursos como initContainers. Mas temos o ReplicaSet que faria isso também sem precisar definir vários containers em um pod no caso de querer processamento em paralelo.

Enquanto um ReplicaSet quer garantir um número de pods rodando, um Job quer garantir que vários pods completem uma tarefa.

Um Job também é um controller para pods assim como ReplicaSet, a diferença é que ele quer que termine. Então vamos definir um job.

apiVersion: batch/v1 # Atencao na API
kind: Job
metadata:
name: math-add
spec:
template:
spec:
containers:
- image: ubuntu
name: math-pod
resources: {}
command:
- "expr"
- "3"
- "+"
- "2"
dnsPolicy: ClusterFirst
restartPolicy: Never

Vamos testar.

❯ kubectl apply -f math-job.yaml

❯ k get jobs
NAME COMPLETIONS DURATION AGE
math-add 1/1 4s 46s

# Podemos observar que o pod não ficou reiniciando
❯ k get pods
NAME READY STATUS RESTARTS AGE
math-add-skttf 0/1 Completed 0 49s

❯ k logs pods/math-add-skttf
5

# Deletar o job também deleta o pod criado por ele
❯ k delete job math-add
job.batch "math-add" deleted

❯ k get pods
No resources found in default namespace.

Claro que essa é uma simulação simples que não é usada no mundo real. Geralmente um job irá persistir o dados em algum volume, ou enviar para algum lugar, ou enviar um email, etc.

Continuado com o exemplo podemos aumentar o numero de tarefas que queremos que execute. Ao invés de usar réplicas, usamos completions.

apiVersion: batch/v1
kind: Job
metadata:
name: math-add
spec:
completions: 3
template:
spec:
containers:
- image: ubuntu
name: math-pod
resources: {}
command:
- "expr"
- "3"
- "+"
- "2"
dnsPolicy: ClusterFirst
restartPolicy: Never

Se olhamos o que gerou, temos

❯ k apply -f math-job.yaml
job.batch/math-add created

❯ k get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
math-add-2gcbl 0/1 Completed 0 6s 10.244.2.12 kind-cluster-worker2 <none> <none>
math-add-fp6tc 0/1 Completed 0 15s 10.244.2.10 kind-cluster-worker2 <none> <none>
math-add-lfv5v 0/1 Completed 0 11s 10.244.2.11 kind-cluster-worker2 <none> <none>

❯ k get job math-add
NAME COMPLETIONS DURATION AGE
math-add 3/3 13s 82s

Se observarmos a coluna AGE podemos ver que esses jobs foram executados um depois do outro.

O job só termina quando ele tiver as 3 completions executadas, senão ele irá ficar criando novos pods.

Para que fossem executados em paralelo podemos definir o parallelism. Por padrão é 1, por isso roda um após o outro.

apiVersion: batch/v1
kind: Job
metadata:
name: math-add
spec:
completions: 9
parallelism: 3 # Rodará 3 por vez
spec:
containers:
- image: ubuntu
name: math-pod
resources: {}
command:
- "expr"
- "3"
- "+"
- "2"
dnsPolicy: ClusterFirst
restartPolicy: Never

Se quisermos que todos rodem juntos precisamos definir o completions e parallelism com os mesmos valores.

Também é possível definir o número de falhas, caso contrário o job ficará rodando até que consiga seu número de completions.

Jobs vs CronJobs

A diferença está em que o CronJob é o controller do Job. Com o CronJob podemos agendar os Jobs periodicamente. Se o CronJob é um controller do Job, pela nossa experiência ele precisa ter um template para um Job dentro dele, mas definindo algumas coisas extras como o schedule que será quando o Job irá executar.

apiVersion: batch/v1 # Atencao na api
kind: CronJob
metadata:
name: cj-math-add
spec:
schedule: "*/1 * * * *" # vai rodar a cada 1 minuto
jobTemplate: # Esse é o job
spec:
completions: 3 # Todos juntos
parallelism: 3
template:
spec:
containers:
- image: ubuntu
name: math-pod
resources: {}
command:
- "expr"
- "3"
- "+"
- "2"
dnsPolicy: ClusterFirst
restartPolicy: Never

Vamos criar um para observar

❯ k apply -f cronjob.yaml
cronjob.batch/cj-math-add created

❯ k get cronjobs
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
cj-math-add */1 * * * * False 0 <none> 27s

# Ainda não deu a hora
❯ k get jobs
No resources found in default namespace.

# Começou
❯ k get jobs
NAME COMPLETIONS DURATION AGE
cj-math-add-28579344 0/3 0s 0s

# Completou
❯ k get jobs
NAME COMPLETIONS DURATION AGE
cj-math-add-28579344 3/3 4s 6s

# Observe o AGE e veja que todos foram criados juntos por causa do paralelismo
❯ k get pods
NAME READY STATUS RESTARTS AGE
cj-math-add-28579344-cmtjz 0/1 Completed 0 17s
cj-math-add-28579344-m999t 0/1 Completed 0 17s
cj-math-add-28579344-s9kj6 0/1 Completed 0 17s

# dando mais um tempo vamos ver que rodou mais uma vez depois de um minuto
❯ k get jobs
NAME COMPLETIONS DURATION AGE
cj-math-add-28579344 3/3 4s 119s
cj-math-add-28579345 3/3 5s 59s

❯ k get pods
NAME READY STATUS RESTARTS AGE
cj-math-add-28579344-cmtjz 0/1 Completed 0 2m4s
cj-math-add-28579344-m999t 0/1 Completed 0 2m4s
cj-math-add-28579344-s9kj6 0/1 Completed 0 2m4s
cj-math-add-28579345-89dd6 0/1 Completed 0 64s
cj-math-add-28579345-jfpzm 0/1 Completed 0 64s
cj-math-add-28579345-lmh8j 0/1 Completed 0 64s
cj-math-add-28579346-htpgx 0/1 Completed 0 4s
cj-math-add-28579346-mndcv 0/1 Completed 0 4s
cj-math-add-28579346-qkq6v 0/1 Completed 0 4s

# Deletando o cronjob, ele deleta todos os jobs que deleta todos os pods
❯ k delete cronjobs.batch cj-math-add
cronjob.batch "cj-math-add" deleted

❯ k get pods
No resources found in default namespace.