Introdução
Os Deployments mantêm os serviços em execução contínua. Já as tarefas em lote têm uma condição de sucesso diferente: executar até a conclusão, registrar o resultado e parar. No Kubernetes, o trabalho executado uma única vez é modelado com Jobs, enquanto os agendamentos recorrentes usam CronJobs.
Você executará um Job, inspecionará seu Pod e seus logs, adicionará paralelismo controlado, agendará um CronJob e aprenderá como a suspensão e os limites de histórico tornam as tarefas recorrentes mais fáceis de administrar.
Pense neste laboratório como uma comparação entre condições de sucesso. Um Deployment é bem-sucedido ao manter réplicas disponíveis. Um Job é bem-sucedido quando o número exigido de Pods termina com o código de saída 0. Um CronJob é bem-sucedido ao criar Jobs nos horários previstos. Manter essas três camadas separadas tornará os arquivos YAML muito mais fáceis de interpretar.
Executar um Job único
Nesta etapa, você criará um Job cujo Pod imprime um relatório e termina com sucesso. Crie o manifesto usando um documento here; o EOF entre aspas impede que o shell expanda caracteres dentro do YAML.
Antes de inserir o YAML, observe sua estrutura: kind: Job seleciona o controlador, spec.template descreve o Pod que será criado, e o Pod contém um contêiner de curta duração. Diferentemente de um template de Deployment, um Pod de Job deve usar restartPolicy: Never ou OnFailure, pois a expectativa é que uma tarefa concluída seja encerrada.
cd /home/labex/project/batch-workloads
cat <<'EOF' > report-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: report-job
namespace: practice-batch
spec:
backoffLimit: 2
template:
metadata:
labels:
app: report-job
spec:
restartPolicy: Never
containers:
- name: reporter
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "echo report-complete"]
EOF
Primeiro, peça ao Kubernetes para validar o arquivo localmente. --dry-run=client verifica a estrutura do objeto sem criar nada, um hábito útil sempre que você escreve um novo manifesto:
kubectl apply --dry-run=client -f report-job.yaml
Se o resultado for job.batch/report-job created (dry run), o arquivo é estruturalmente válido. Aplique-o de verdade e aguarde a condição Complete do Job, em vez de estimar quanto tempo ele levará:
kubectl apply -f report-job.yaml
kubectl wait --for=condition=Complete job/report-job -n practice-batch --timeout=60s
restartPolicy: Never permite que o controlador do Job decida se outro Pod é necessário. backoffLimit: 2 limita as tentativas após falhas. Leia a saída do Pod concluído por meio do controlador:
kubectl logs job/report-job -n practice-batch
Você deverá ver report-complete. Ao informar job/report-job, o kubectl encontra o Pod do Job automaticamente, portanto não é necessário copiar o nome gerado.
Os três comandos produzem um ciclo de vida curto e fácil de ler:
job.batch/report-job created
job.batch/report-job condition met
report-complete
condition met significa que o controlador do Job registrou a conclusão; a linha seguinte contém a saída da aplicação. Em um cluster local recém-iniciado, até mesmo este pequeno Job pode levar alguns segundos enquanto a rede é inicializada. Aguardar uma condição torna o fluxo confiável sem precisar estimar uma duração para sleep.
Rastrear a propriedade e a conclusão de um Job
Nesta etapa, você comparará um Job concluído com um Deployment em execução contínua. Um Job concluído mantém seu Pod para que os logs e o status de encerramento continuem disponíveis para inspeção.
Comece pela visualização tabular comum. O Job deverá mostrar uma conclusão bem-sucedida, enquanto o Pod deverá ter a fase Completed ou Succeeded, dependendo da coluna exibida:
kubectl get jobs,pods -n practice-batch
kubectl get pods -n practice-batch -l job-name=report-job -o custom-columns='POD:.metadata.name,PHASE:.status.phase,OWNER:.metadata.ownerReferences[0].name,EXIT_CODE:.status.containerStatuses[0].state.terminated.exitCode'
kubectl describe job report-job -n practice-batch
A fase do Pod deverá ser Succeeded, seu proprietário deverá ser report-job e o código de saída deverá ser 0. Um Job é concluído quando o número exigido de Pods termina com sucesso; ele não reinicia um contêiner bem-sucedido apenas para mantê-lo em execução.
A consulta com colunas personalizadas transforma quatro campos aninhados em uma pequena tabela de evidências. Isso é mais útil do que ler todo o YAML do Pod quando a pergunta específica é: “quem é o proprietário deste Pod e o processo foi concluído com sucesso?”
O resultado testado foi semelhante a este:
POD PHASE OWNER EXIT_CODE
report-job-cd54c Succeeded report-job 0
O sufixo gerado será diferente. Leia a linha horizontalmente: OWNER=report-job conecta o Pod ao seu controlador, enquanto PHASE=Succeeded e EXIT_CODE=0 comprovam que o processo foi concluído com sucesso.
Executar várias conclusões em paralelo
Nesta etapa, você criará um Job que exige quatro Pods concluídos com sucesso, permitindo que dois sejam executados simultaneamente.
Antes de escrever o manifesto, é importante entender dois números independentes:
completions: 4significa que são necessárias quatro unidades de trabalho concluídas com sucesso no total.parallelism: 2significa que o Kubernetes pode executar no máximo duas dessas unidades ao mesmo tempo.
completionMode: Indexed atribui índices de 0 a 3 às unidades de trabalho. Cada Pod recebe seu próprio índice por meio da variável de ambiente JOB_COMPLETION_INDEX, permitindo que programas reais de processamento em lote dividam um conjunto de dados sem que todos os Pods processem a mesma partição.
cat <<'EOF' > indexed-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: indexed-job
namespace: practice-batch
spec:
completionMode: Indexed
completions: 4
parallelism: 2
backoffLimitPerIndex: 1
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "echo worker-index=${JOB_COMPLETION_INDEX}; sleep 2"]
EOF
Aplique o manifesto e aguarde a conclusão dos quatro índices:
O comando final usa -L para adicionar o valor do rótulo batch.kubernetes.io/job-completion-index como uma coluna extra na tabela. Assim, você pode ver a qual índice de trabalho cada Pod gerado pertence sem abrir seu YAML completo.
kubectl apply -f indexed-job.yaml
kubectl wait --for=condition=Complete job/indexed-job -n practice-batch --timeout=90s
kubectl get pods -n practice-batch -l job-name=indexed-job -L batch.kubernetes.io/job-completion-index
completions representa a quantidade total de unidades de trabalho concluídas com sucesso; parallelism representa o número máximo de Pods simultâneos. A conclusão indexada fornece a cada Pod um índice estável por meio de JOB_COMPLETION_INDEX.
A tabela final deverá conter quatro Pods concluídos com rótulos de índice diferentes. Eles podem não aparecer em ordem numérica, pois a execução é concorrente e, portanto, sua ordem não é determinística.
Por exemplo:
NAME STATUS JOB-COMPLETION-INDEX
indexed-job-0-gw5wq Completed 0
indexed-job-1-vw4qk Completed 1
indexed-job-2-mpvnr Completed 2
indexed-job-3-8z5j8 Completed 3
Ver quatro linhas não significa que quatro Pods foram executados simultaneamente. Os quatro índices comprovam a quantidade total de trabalho, enquanto parallelism: 2 limitou quantos Pods o Kubernetes poderia executar de cada vez.
Agendar um CronJob
Nesta etapa, você criará um CronJob que inicia um Job a cada minuto. A sintaxe do cron possui cinco campos: minuto, hora, dia do mês, mês e dia da semana.
O agendamento */1 * * * * significa “a cada minuto”. Ele foi escolhido de propósito para que você possa observá-lo em pouco tempo durante o laboratório. Em produção, os agendamentos normalmente são menos frequentes. O manifesto também contém um jobTemplate aninhado, pois um CronJob cria Jobs, e cada Job cria Pods.
cat <<'EOF' > heartbeat-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: heartbeat
namespace: practice-batch
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: heartbeat
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "date -u; echo heartbeat-complete"]
EOF
Aplique o agendamento e inspecione seu resumo:
kubectl apply -f heartbeat-cronjob.yaml
kubectl get cronjob heartbeat -n practice-batch
concurrencyPolicy: Forbid impede que uma nova execução se sobreponha a uma execução anterior ainda não concluída. Crie imediatamente um Job a partir do template do CronJob para inspecioná-lo sem esperar até o próximo minuto:
kubectl create job --from=cronjob/heartbeat heartbeat-manual -n practice-batch
kubectl wait --for=condition=Complete job/heartbeat-manual -n practice-batch --timeout=60s
kubectl logs job/heartbeat-manual -n practice-batch
O log contém um registro de data e hora em UTC seguido por heartbeat-complete. O Job criado manualmente usa o mesmo template, mas não altera o agendamento futuro do CronJob.
A saída típica é:
job.batch/heartbeat-manual condition met
Thu Aug 13 07:48:29 UTC 2026
heartbeat-complete
O registro de data e hora será diferente no seu ambiente. Observe que o contêiner usa UTC porque seu comando é date -u; isso evita confundir o agendamento do cluster com o fuso horário local do terminal.
Suspender e retomar tarefas agendadas
Nesta etapa, você pausará o CronJob sem excluir sua definição. A suspensão é útil durante manutenções ou na resposta a incidentes.
kubectl patch altera apenas o campo especificado no conteúdo JSON. Aqui, ele define spec.suspend como true; o objeto CronJob, seu agendamento e os Jobs já existentes permanecem intactos.
kubectl patch cronjob heartbeat -n practice-batch --type=merge -p '{"spec":{"suspend":true}}'
kubectl get cronjob heartbeat -n practice-batch -o custom-columns='NAME:.metadata.name,SCHEDULE:.spec.schedule,SUSPEND:.spec.suspend,LAST_SCHEDULE:.status.lastScheduleTime'
Os Jobs existentes continuam em execução; a suspensão apenas impede a criação de novos Jobs agendados. Retome o CronJob depois da inspeção:
kubectl patch cronjob heartbeat -n practice-batch --type=merge -p '{"spec":{"suspend":false}}'
kubectl get jobs -n practice-batch --sort-by=.metadata.creationTimestamp
Agora você consegue distinguir as três camadas: o CronJob define um agendamento, cada execução agendada cria um Job e cada Job cria um ou mais Pods.
Como o exercício inteiro pode terminar antes da próxima virada de minuto, LAST_SCHEDULE ainda pode aparecer como <none>. Isso não é um erro: heartbeat-manual foi criado sob demanda com --from, e não pelo agendador do CronJob. O valor final importante é SUSPEND=false, que permite novamente futuras execuções agendadas.
Resumo
Você usou Jobs para tarefas finitas, conclusões indexadas para tarefas paralelas e CronJobs para agendamentos recorrentes. Inspecionou a propriedade do controlador, os logs, o estado de conclusão, a política de concorrência, os limites de histórico e a suspensão. Essas diferenças no ciclo de vida são fundamentais para escolher o controlador de carga de trabalho correto no Kubernetes.


