Skip to main content

Jobs y CronJobs

Una aplicación que vive un corto período de tiempo que se destina a una tarea y después morir es considerada un job.

Algunos ejemplos.

  • Generar un reporte
  • Procesar un cálculo complejo o una imagen
  • Enviar emails

Si fuésemos hacer eso con un docker podríamos hacer una operación matemática simple.

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

El contenedor no está más ejecutando, él ejecutó y salió con el exit code 0, o sea, hizo lo que tenía que hacer.

Ese es el lifecycle de muchos pods, pero podemos separar ese tipo de pod como Jobs.

Si fuésemos hacer eso usando un pod ¿qué acontecería?

❯ 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: {}

# Él ejecuta pero queda restartando toda hora, pues el kubernetes queda intentando traer él nuevamente
❯ k get pods math-pod
NAME READY STATUS RESTARTS AGE
math-pod 0/1 Completed 3 (28s ago) 47s

El Kubernetes quiere que sus aplicaciones vivan para siempre. El comportamiento padrón de un pod es intentar reiniciar el pod para mantenerlo ejecutando caso él esté parado. Donde podemos confirmar eso.

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

Si tuviésemos definido para Never o OnFailure eso no acontecería.

Podríamos tener varios contenedores procesando en paralelo dentro de un pod, inclusive usar recursos como initContainers. Pero tenemos el ReplicaSet que haría eso también sin precisar definir varios contenedores en un pod en el caso de querer procesamiento en paralelo.

Mientras un ReplicaSet quiere garantizar un número de pods ejecutando, un Job quiere garantizar que varios pods completen una tarea.

Un Job también es un controller para pods así como ReplicaSet, la diferencia es que él quiere que termine. Entonces vamos a definir un job.

apiVersion: batch/v1 # Atención en la 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 a testar.

❯ kubectl apply -f math-job.yaml

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

# Podemos observar que el pod no quedó 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 el job también deleta el pod creado por él
❯ k delete job math-add
job.batch "math-add" deleted

❯ k get pods
No resources found in default namespace.

Claro que esa es una simulación simple que no es usada en el mundo real. Generalmente un job irá persistir los datos en algún volumen, o enviar para algún lugar, o enviar un email, etc.

Continuado con el ejemplo podemos aumentar el numero de tareas que queremos que ejecute. Al revé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

Si miramos lo que generó, tenemos

❯ 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

Si observamos la columna AGE podemos ver que esos jobs fueron ejecutados uno después del otro.

El job solo termina cuando él tuviera las 3 completions ejecutadas, sino él irá quedar creando nuevos pods.

Para que fuesen ejecutados en paralelo podemos definir el parallelism. Por padrón es 1, por eso ejecuta uno después del otro.

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

Si queremos que todos ejecuten juntos precisamos definir el completions y parallelism con los mismos valores.

También es posible definir el número de fallas, caso contrario el job quedará ejecutando hasta que consiga su número de completions.

Jobs vs CronJobs

La diferencia está en que el CronJob es el controller del Job. Con el CronJob podemos agendar los Jobs periódicamente. Si el CronJob es un controller del Job, por nuestra experiencia él precisa tener un template para un Job dentro de él, pero definiendo algunas cosas extras como el schedule que será cuando el Job irá ejecutar.

apiVersion: batch/v1 # Atención en la api
kind: CronJob
metadata:
name: cj-math-add
spec:
schedule: "*/1 * * * *" # va a ejecutar a cada 1 minuto
jobTemplate: # Ese es el 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 a crear uno 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

# Todavía no dio la hora
❯ k get jobs
No resources found in default namespace.

# Comenzó
❯ k get jobs
NAME COMPLETIONS DURATION AGE
cj-math-add-28579344 0/3 0s 0s

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

# Observe el AGE y vea que todos fueron creados juntos por causa del 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 más un tiempo vamos a ver que ejecutó más una vez después de un 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 el cronjob, él deleta todos los jobs que deleta todos los pods
❯ k delete cronjobs.batch cj-math-add
cronjob.batch "cj-math-add" deleted

❯ k get pods
No resources found in default namespace.