Victor Longui
08/26/2025, 2:23 PMMarvin
08/26/2025, 2:23 PMMarvin
08/26/2025, 2:32 PMjob-name=<nome_do_job>
) para stream de logs, mas não encontrou nenhum. Isso pode acontecer quando:
- o Pod foi criado e terminou muito rápido e o GC do Kubernetes já limpou o Pod;
- o Job falhou antes mesmo de iniciar um Pod (ex.: erro de admission webhook, quota, spec inválida);
- RBAC do worker não permite listar/ler Pods/Logs (mais raro; normalmente veria erro 403).
- “Job reached backoff limit.”: o Job bateu no backoffLimit
do Kubernetes (número de tentativas de Pods falhados) e foi marcado como “Failed”.
Por que o Job fica 48h mas o Pod some
- O “Finished Job TTL” do Prefect mapeia para spec.ttlSecondsAfterFinished
no Job. Isso controla quando o Job será removido após terminar, mas não impede outros mecanismos do cluster de remover Pods terminados antes disso.
- Em clusters gerenciados como EKS, o kube-controller-manager aplica garbage collection de Pods terminados quando o número ultrapassa um limite interno. Nessa situação, Pods “Succeeded/Failed” antigos podem ser removidos rapidamente mesmo com o Job ainda presente. No EKS, você não consegue ajustar flags do control plane (como --terminated-pod-gc-threshold
).
Como reter melhor evidências/diagnóstico
1) Ajustes no template do Job (work pool)
- Garanta que o Job não seja apagado automaticamente (ou seja, TTL alto ou null
) e aumente a tolerância de falhas e o tempo de encerramento para dar tempo do log escoar:
{
"job": {
"spec": {
"ttlSecondsAfterFinished": null,
"backoffLimit": 6,
"template": {
"spec": {
"restartPolicy": "Never",
"terminationGracePeriodSeconds": 60,
"containers": [
{
"name": "prefect-job",
"env": [
{
"name": "PREFECT_LOGGING_LEVEL",
"value": "DEBUG"
}
]
}
]
}
}
}
}
}
- Aplique esse template como “base job template” no seu work pool de Kubernetes:
prefect work-pool create "meu-pool-k8s" --type kubernetes --base-job-template ./base-job-template.json --overwrite
Referência: Prefect 2.x – Kubernetes Jobs
Observações:
- ttlSecondsAfterFinished: null
evita a exclusão automática pelo TTL. Ainda assim, o GC do cluster pode deletar Pods terminados se o limiar for atingido.
- backoffLimit
maior ajuda a manter mais tentativas (mais Pods) para investigar o padrão de falha, mas também aumenta custo/ruído — use com parcimônia.
2) Garanta logs fora do ciclo de vida do Pod
Mesmo com TTL alto, o GC pode remover Pods. A maneira mais confiável de não perder logs é enviá-los para um backend de logging:
- Habilite coleta de logs para CloudWatch no EKS (fluent-bit/CloudWatch Logs).
- Alternativas: Datadog, ELK, OpenSearch, etc.
Isso garante que, mesmo que o Pod seja limpo, os logs permaneçam disponíveis.
3) Checagens rápidas de ambiente
- RBAC: o ServiceAccount do worker precisa de get/list/watch
em Pods e get
em pods/log
no namespace. Se houver restrições, o Prefect pode não conseguir localizar/streamar logs.
- K8s events: no momento da falha, rode:
kubectl describe job <nome>
kubectl get events -n <namespace> --sort-by='.lastTimestamp' | tail -n 100
Isso normalmente mostra razões como ImagePullBackOff, OOMKilled, admission denials, limites de quota, etc.
- Acompanhe em tempo real quando for reproduzir:
kubectl get pods -n <namespace> -l job-name=<nome_do_job> --watch
4) Sobre “manter o Pod vivo”
Tecnicamente não há como “manter vivo” um contêiner que já terminou com erro; o que dá para fazer é:
- Evitar que o objeto Pod seja deletado rapidamente (TTL alto no Job e evitar overload de Pods terminados no cluster).
- Persistir logs fora do Pod (mais recomendado em EKS).
Links úteisMarvin
08/26/2025, 2:32 PMkubectl describe job <nome>
logo após a falha para ver os events? Se puder compartilhar um trecho dos events, ajuda muito a identificar a causa raiz.Victor Longui
08/26/2025, 2:39 PMfrom_file
for creation.",
"secret_fields": [],
"block_type_slug": "kubernetes-cluster-config",
"block_schema_references": {}
}
},
"description": "Default variables for the Kubernetes worker.\n\nThe schema for this class is used to populate the variables
section of the default\nbase job template."
},
"job_configuration": {
"env": "{{ env }}",
"name": "{{ name }}",
"labels": "{{ labels }}",
"command": "{{ command }}",
"namespace": "{{ namespace }}",
"job_manifest": {
"kind": "Job",
"spec": {
"template": {
"spec": {
"volumes": [
{
"name": "volume-to-avoid-eviction",
"emptyDir": {}
}
],
"containers": [
{
"env": "{{ env }}",
"args": "{{ command }}",
"name": "prefect-job",
"image": "{{ image }}",
"resources": {
"limits": {
"cpu": "{{ cpu_limit }}",
"memory": "{{ memory_limit }}"
},
"requests": {
"cpu": "{{ cpu_request }}",
"memory": "{{ memory_request }}"
}
},
"volumeMounts": [
{
"name": "volume-to-avoid-eviction",
"mountPath": "/tmp"
}
],
"imagePullPolicy": "{{ image_pull_policy }}"
}
],
"completions": 1,
"parallelism": 1,
"restartPolicy": "Never",
"serviceAccountName": "{{ service_account_name }}"
}
},
"backoffLimit": 0,
"ttlSecondsAfterFinished": "{{ finished_job_ttl }}"
},
"metadata": {
"labels": "{{ labels }}",
"namespace": "{{ namespace }}",
"annotations": "{{ annotations }}",
"generateName": "{{ name }}-"
},
"apiVersion": "batch/v1"
},
"stream_output": "{{ stream_output }}",
"cluster_config": "{{ cluster_config }}",
"job_watch_timeout_seconds": "{{ job_watch_timeout_seconds }}",
"pod_watch_timeout_seconds": "{{ pod_watch_timeout_seconds }}"
}
}Marvin
08/26/2025, 2:39 PM