O Kubernetes CronJob no Node.js cria Jobs em horários recorrentes usando uma expressão cron. Ele é adequado para relatórios, limpeza de dados, reconciliação, envio de lembretes, backups auxiliares e outras tarefas periódicas que terminam depois de executar.
Um CronJob não garante execução exatamente uma vez. A documentação do Kubernetes alerta que, em determinadas circunstâncias, duas execuções podem ser criadas ou uma execução pode ser perdida. Por isso, o comando Node.js precisa ser idempotente, registrar progresso e tolerar reinícios.
Neste guia, você aprenderá a definir schedule e timezone, escolher concurrencyPolicy, configurar deadlines, retries, recursos, segurança, histórico, suspensão, observabilidade e execução manual.
O que é um CronJob?
A documentação oficial de CronJob no Kubernetes explica que o recurso cria objetos Job em uma programação recorrente. Cada Job cria um ou mais Pods que executam até concluir ou falhar.
O CronJob controla quando criar o Job. O Job controla retries e conclusão. O Pod executa o processo Node.js.
Estrutura básica
apiVersion: batch/v1
kind: CronJob
metadata:
name: orders-reconciliation
namespace: production
spec:
schedule: "0 */2 * * *"
timeZone: "America/Sao_Paulo"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 900
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
backoffLimit: 3
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: Never
containers:
- name: reconciliation
image: ghcr.io/acme/orders-api@sha256:abc123...
command:
- node
- dist/jobs/reconcile-orders.jsEsse exemplo executa a cada duas horas no fuso de São Paulo e impede sobreposição.
Sintaxe cron
# ┌──────── minuto
# │ ┌────── hora
# │ │ ┌──── dia do mês
# │ │ │ ┌── mês
# │ │ │ │ ┌ dia da semana
# │ │ │ │ │
# * * * * *Exemplos:
0 3 * * * # todos os dias às 03:00
0 8 * * 1-5 # dias úteis às 08:00
*/15 * * * * # a cada 15 minutos
0 0 1 * * # primeiro dia do mêsValide a expressão em testes e documentação interna. Uma expressão incorreta pode executar mais vezes que o esperado.
Timezone
timeZone: "America/Sao_Paulo"Use spec.timeZone em vez de colocar TZ ou CRON_TZ dentro da expressão. O nome deve ser válido na base IANA.
Horário de verão e mudanças regulatórias podem alterar a relação com UTC. Para processamento financeiro, registre o instante UTC e o período lógico processado.
UTC ou fuso local?
Use UTC quando o processo não depende de calendário local. Use um fuso explícito para fechamento diário, relatórios comerciais e rotinas relacionadas à data local.
Nunca dependa silenciosamente do timezone do controller manager.
concurrencyPolicy Allow
concurrencyPolicy: AllowÉ o padrão. Uma nova execução começa mesmo que a anterior continue ativa. Use quando os Jobs são independentes e o sistema suporta paralelismo.
concurrencyPolicy Forbid
concurrencyPolicy: ForbidA próxima execução é ignorada enquanto a anterior estiver ativa. É adequada para reconciliação, limpeza e relatórios que não devem sobrepor.
Uma execução ignorada conta como missed schedule. Se o Job demora mais que o intervalo, várias execuções podem ser puladas.
concurrencyPolicy Replace
concurrencyPolicy: ReplaceO Kubernetes encerra o Job atual e inicia o novo. Use somente quando interromper a execução antiga é seguro. O processo Node.js precisa tratar SIGTERM e deixar estado consistente.
Idempotência
Mesmo com Forbid, duplicatas continuam possíveis em condições de controle. Use uma chave lógica:
const executionKey = `orders-reconciliation:${periodStart.toISOString()}`;
const result = await pool.query(`
INSERT INTO scheduled_executions(execution_key, started_at)
VALUES ($1, now())
ON CONFLICT DO NOTHING
RETURNING execution_key
`, [executionKey]);
if (result.rowCount === 0) {
console.log('Período já processado');
process.exit(0);
}Consulte Idempotência em APIs Node.js.
Lock transacional
Uma alternativa é usar advisory lock:
const { rows } = await pool.query(
'SELECT pg_try_advisory_lock($1) AS acquired',
[lockId]
);
if (!rows[0].acquired) {
process.exit(0);
}Libere o lock no finally. Locks de sessão exigem conexão dedicada e cuidado com PgBouncer transaction mode.
startingDeadlineSeconds
startingDeadlineSeconds: 900Permite iniciar até quinze minutos depois do horário planejado. Se o controller esteve indisponível por mais tempo, a execução é pulada.
Escolha o prazo conforme utilidade. Um relatório diário pode aceitar horas de atraso; uma rotina a cada cinco minutos talvez não deva recuperar uma execução antiga.
Valores muito baixos
O controller verifica schedules periodicamente. A documentação alerta que valores menores que aproximadamente dez segundos podem impedir a criação. Não use deadline extremamente curto.
Suspendendo
suspend: trueIsso impede novas execuções, mas não interrompe Jobs já iniciados. Ao reativar, schedules perdidos podem ser criados imediatamente quando não existe deadline.
Antes de remover a suspensão, avalie startingDeadlineSeconds.
Job template
O jobTemplate aceita a especificação de um Job. A documentação de Jobs no Kubernetes detalha completions, parallelism, backoff e pod failure policy.
restartPolicy
restartPolicy: NeverCom Never, uma falha cria novo Pod conforme backoffLimit. Com OnFailure, o container pode reiniciar no mesmo Pod. Never costuma facilitar diagnóstico e logs por tentativa.
backoffLimit
backoffLimit: 3O Job falha após exceder retries. O código precisa classificar erros:
- timeout e 503 podem ser temporários;
- dados inválidos são permanentes;
- credencial ausente exige intervenção;
- conflito de idempotência pode ser sucesso lógico.
activeDeadlineSeconds
activeDeadlineSeconds: 1800Encerra o Job depois de trinta minutos, incluindo retries. Defina um limite para impedir execuções presas.
Timeouts no código
const signal = AbortSignal.timeout(10_000);
const response = await fetch(url, { signal });O prazo total do Job não substitui timeouts por operação. Banco, HTTP e filas precisam de limites menores.
Recursos
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1
memory: 512MiRequests permitem scheduling previsível. Limits evitam que uma rotina pesada afete outros workloads. Analise OOMKilled e throttling.
Imagem imutável
image: ghcr.io/acme/orders-api@sha256:abc123...Não use latest. Um CronJob pode executar dias depois do deploy, e a tag móvel dificultaria saber qual código rodou.
Veja GitHub Container Registry no Node.js.
Comando separado
Organize cada job como uma entrada clara:
src/jobs/reconcile-orders.ts
src/jobs/delete-expired-sessions.ts
src/jobs/generate-daily-report.tsEvite iniciar o servidor HTTP e depois chamar uma função interna. O processo deve executar a tarefa e sair com código adequado.
Códigos de saída
async function main() {
await runJob();
}
main()
.then(() => process.exitCode = 0)
.catch(error => {
console.error(error);
process.exitCode = 1;
});Não chame process.exit() antes de logs e buffers terminarem, salvo caso controlado.
ConfigMap
envFrom:
- configMapRef:
name: orders-jobs-config
- secretRef:
name: orders-jobs-secretsUse configuração externa e secrets protegidos. Consulte ConfigMaps e Secrets no Node.js.
ServiceAccount
serviceAccountName: orders-reconciliation
automountServiceAccountToken: falseDesative o token se o Job não chama a API do Kubernetes. Quando precisa, conceda RBAC mínimo.
Security context
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: reconciliation
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]Monte emptyDir somente nos caminhos graváveis necessários.
Histórico
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5Manter falhas ajuda no diagnóstico. Muitos Jobs antigos aumentam objetos e ruído. Combine com TTL.
TTL após conclusão
ttlSecondsAfterFinished: 86400O Job e seus Pods são removidos após um dia. Logs importantes devem estar em um sistema central antes da limpeza.
Executando manualmente
kubectl create job \
--from=cronjob/orders-reconciliation \
orders-reconciliation-manual-$(date +%s) \
-n productionA execução manual usa o template atual. Ela não altera o schedule. Registre motivo e operador.
Inspecionando
kubectl get cronjobs -n production
kubectl describe cronjob orders-reconciliation -n production
kubectl get jobs -n production \
-l batch.kubernetes.io/cronjob-name=orders-reconciliation
kubectl logs job/JOB_NAME -n productionAnnotation de horário agendado
Versões modernas adicionam ao Job uma annotation com o timestamp originalmente agendado. Use-a para calcular atraso e o período lógico, quando disponível.
Não use a hora de início como período
Se o Job das 03:00 começa às 03:12, ele ainda pode precisar processar o dia anterior. Derive o período do schedule ou de parâmetros explícitos, não apenas de new Date().
Processamento em páginas
let cursor = null;
do {
const page = await loadPage(cursor, 500);
await processPage(page.items);
cursor = page.nextCursor;
} while (cursor);Use cursor estável e checkpoint quando a tarefa pode exceder o prazo. O reprocessamento precisa ser seguro.
Jobs muito longos
Se a rotina dura horas, considere:
- dividir em jobs menores;
- usar fila;
- usar Indexed Job;
- usar workflow durável;
- salvar checkpoint;
- reavaliar a frequência.
Fila em vez de CronJob
O CronJob pode descobrir trabalho e publicar itens em uma fila. Workers processam em paralelo, com retries e DLQ. Isso evita um único Pod gigantesco.
Leader election
O CronJob controller já decide a criação do Job. Não é necessário leader election dentro do processo apenas por haver várias réplicas, pois o Job normalmente cria um Pod. Em Jobs paralelos, coordene as unidades de trabalho.
Graceful shutdown
Com Replace, drain ou deadline, o Pod recebe SIGTERM. O processo deve parar de iniciar novos lotes, salvar checkpoint e fechar conexões.
Consulte Graceful Shutdown no Node.js.
Observabilidade
Monitore:
- último schedule e último sucesso;
- Jobs ativos;
- falhas consecutivas;
- atraso entre schedule e início;
- duração;
- itens processados e rejeitados;
- execuções puladas por concorrência;
- OOMKilled e evictions;
- Jobs presos.
Alertas
Um alerta útil verifica ausência de sucesso dentro de uma janela:
time() - kube_job_status_completion_time{job_name=~"orders-reconciliation-.*"}
> 3 * 60 * 60A expressão real depende das métricas e labels. Evite alertar apenas por um Job falho quando uma tentativa posterior concluiu.
Testes
Teste a função do job fora do Kubernetes com banco e serviços reais em containers. Depois teste:
- duas execuções simultâneas;
- SIGTERM no meio de uma página;
- timeout externo;
- reexecução do mesmo período;
- schedule perdido;
- mudança de timezone;
- falha permanente.
Erros comuns
- Allow em job não idempotente: execuções se sobrepõem.
- Sem deadline: schedules antigos iniciam tarde demais.
- Replace sem shutdown: estado fica parcialmente gravado.
- Timezone implícito: tarefa roda em horário inesperado.
- Latest: a mesma definição executa código diferente.
- Sem timeout: Job permanece ativo para sempre.
- Histórico ilimitado: objetos e logs acumulam.
- Confiar em execução única: duplicatas causam efeitos repetidos.
Conclusão
O Kubernetes CronJob no Node.js agenda Jobs recorrentes com cron e timezone explícito. Concurrency policy, deadline, backoff e limites controlam como execuções atrasadas, concorrentes e falhas são tratadas.
Escreva tarefas idempotentes, use imagem imutável e defina recursos e timeouts. Centralize logs, monitore último sucesso e teste reexecução. Assim, rotinas periódicas deixam de depender de um cron local e passam a ter estado, histórico e controle operacional no cluster.




