<h2>DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote</h2>
<p>Quando você começa a trabalhar com Kubernetes em ambientes de produção, logo percebe que nem toda aplicação funciona como um deployment tradicional. Existem cenários onde você precisa que um processo execute em <strong>cada nó do cluster</strong> (como coleta de logs ou monitoramento), ou então precisa executar <strong>tarefas pontuais que terminam</strong> (como backup ou processamento em lote). Para esses casos, Kubernetes oferece duas primitivas poderosas: <strong>DaemonSets</strong> e <strong>Jobs</strong>. Neste artigo, vou te ensinar não apenas o "como fazer", mas o <strong>quando e por quê</strong> usar cada um deles.</p>
<h2>DaemonSets: Agentes que Rodam em Todo Lugar</h2>
<h3>O Conceito Fundamental</h3>
<p>Um DaemonSet garante que uma cópia específica de um Pod seja executada em <strong>cada nó</strong> do seu cluster Kubernetes. Imagine que você precisa coletar logs de CPU, memória ou eventos de cada máquina. Se você tivesse apenas um Pod fazendo isso, você perderia informações dos outros nós. Um DaemonSet resolve esse problema de forma elegante: automaticamente, quando um novo nó entra no cluster, um Pod é criado nele. Quando um nó é removido, o Pod associado também é limpo.</p>
<p>A principal diferença entre um DaemonSet e um Deployment está justamente na <strong>semântica</strong>: um Deployment diz "eu quero 3 réplicas espalhadas pelo cluster", enquanto um DaemonSet diz "eu quero uma réplica em cada nó". DaemonSets são ideais para ferramentas de infraestrutura, não para aplicações de negócio convencionais.</p>
<h3>Caso de Uso Real</h3>
<p>Considere um cenário comum: você precisa de um agente de monitoramento (como o Prometheus Node Exporter ou Datadog Agent) em cada máquina do seu cluster. Manualmente manter isso seria um pesadelo em um cluster que cresce constantemente. Um DaemonSet faz isso automaticamente.</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
tolerations:
Este tolerationAllowsSchedulingTaintsTolerateAllNodesDefault faz o DaemonSet
rodar até em nós que têm taints (como nós master, se configurado assim)
- effect: NoSchedule
operator: Exists
containers:
- name: node-exporter
image: prom/node-exporter:latest
ports:
- containerPort: 9100
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
- name: sys
mountPath: /host/sys
readOnly: true
volumes:
- name: proc
hostPath:
path: /proc
- name: sys
hostPath:
path: /sys</code></pre>
<p>Neste exemplo, o DaemonSet garante que o Node Exporter rode em <strong>cada nó</strong>. Repare nas duas configurações importantes: <strong>tolerations</strong> (para permitir que rode em nós com taints especiais) e <strong>hostPath volumes</strong> (para acessar informações do nó hospedeiro).</p>
<h3>Seletores e Node Affinity</h3>
<p>Às vezes você não quer que um DaemonSet rode em <strong>todos</strong> os nós. Por exemplo, você pode ter nós GPU que precisam de um agente especial, mas os nós comuns não. Para isso, usamos <strong>nodeSelector</strong> ou <strong>affinity</strong>:</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: DaemonSet
metadata:
name: gpu-monitor
spec:
selector:
matchLabels:
app: gpu-monitor
template:
metadata:
labels:
app: gpu-monitor
spec:
nodeSelector:
gpu: "true"
containers:
- name: gpu-monitor
image: nvidia/cuda:11.0-base
command: ["nvidia-smi"]</code></pre>
<p>Aqui, o DaemonSet só será criado em nós que têm o label <code>gpu=true</code>. Se você adicionar esse label a um nó, automaticamente um Pod será criado lá. Se remover, o Pod será removido.</p>
<h2>Jobs: Tarefas que Precisam Terminar</h2>
<h3>Entendendo a Natureza do Job</h3>
<p>Um Job em Kubernetes representa uma <strong>tarefa com início e fim</strong>. Ao contrário de um Pod em um Deployment (que é restartado indefinidamente), um Job assume que seu trabalho será concluído e o Pod pode terminar com sucesso. Um Job é responsável por garantir que o trabalho seja feito: se um Pod falha, o Job cria outro. Se o Pod termina com sucesso, o Job marca a tarefa como completa.</p>
<p>Jobs são perfeitos para: backup de banco de dados, processamento em lote de arquivos, geração de relatórios, limpeza de dados ou qualquer tarefa que tenha um começo e um fim definidos.</p>
<h3>Um Job Simples</h3>
<pre><code class="language-yaml">apiVersion: batch/v1
kind: Job
metadata:
name: backup-database
spec:
backoffLimit: 3 # Tenta até 3 vezes se falhar
completions: 1 # Número de Pods que precisam terminar com sucesso
parallelism: 1 # Número máximo de Pods rodando simultaneamente
ttlSecondsAfterFinished: 3600 # Delete o Job 1 hora após terminar
template:
spec:
restartPolicy: Never # Não reinicia Pods com falha (deixa o Job fazer retry)
containers:
- name: backup
image: postgres:13
env:
- name: PGHOST
value: "postgres-svc"
- name: PGUSER
value: "postgres"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
command: ["sh", "-c"]
args:
- |
pg_dump --host=$PGHOST --username=$PGUSER --format=custom mydb > /backup/mydb-$(date +%s).dump
if [ $? -eq 0 ]; then
echo "Backup concluído com sucesso"
exit 0
else
echo "Backup falhou"
exit 1
fi
volumeMounts:
- name: backup-storage
mountPath: /backup
volumes:
- name: backup-storage
persistentVolumeClaim:
claimName: backup-pvc</code></pre>
<p>Neste exemplo, o Job tenta executar um backup de banco de dados. Se falhar, vai tentar até 3 vezes (<code>backoffLimit: 3</code>). Após terminar com sucesso, o Job é automaticamente deletado após 1 hora (<code>ttlSecondsAfterFinished</code>). A chave aqui é que o Pod <strong>não é reiniciado</strong> indefinidamente — o Job controla os retry.</p>
<h3>CronJobs: Jobs Agendados</h3>
<p>Às vezes você precisa que um Job execute periodicamente. Para isso existe o <strong>CronJob</strong>, que é essencialmente um agendador de Jobs:</p>
<pre><code class="language-yaml">apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-cleanup
spec:
schedule: "0 2 *" # Roda diariamente às 2 da manhã (UTC)
jobTemplate:
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: Never
containers:
- name: cleanup
image: python:3.9-slim
command: ["python"]
args:
- |
import os
import glob
from datetime import datetime, timedelta
cleanup_dir = "/data/uploads"
max_age_days = 30
cutoff_date = datetime.now() - timedelta(days=max_age_days)
for filepath in glob.glob(f"{cleanup_dir}/*"):
file_mtime = datetime.fromtimestamp(os.path.getmtime(filepath))
if file_mtime < cutoff_date:
os.remove(filepath)
print(f"Removido: {filepath}")
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1</code></pre>
<p>O CronJob aqui executa diariamente e remove arquivos com mais de 30 dias. Repare que <code>schedule</code> usa a sintaxe padrão de cron do Unix. O <code>successfulJobsHistoryLimit</code> mantém os últimos 3 Jobs bem-sucedidos para auditoria, e <code>failedJobsHistoryLimit</code> mantém apenas o último Job que falhou.</p>
<h3>Processamento em Paralelo com Jobs</h3>
<p>Há casos onde você precisa processar muitos itens em paralelo. Um Job pode fazer isso com <code>completions</code> e <code>parallelism</code>:</p>
<pre><code class="language-yaml">apiVersion: batch/v1
kind: Job
metadata:
name: image-processor
spec:
completions: 100 # 100 items para processar
parallelism: 10 # Processa 10 em paralelo
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: processor
image: image-processor:latest
env:
- name: QUEUE_HOST
value: "rabbitmq-svc"
command: ["python", "/app/process.py"]</code></pre>
<p>Esse Job vai criar 100 Pods no total, mas apenas 10 vão rodar simultaneamente. Conforme um Pod termina, outro é iniciado até que todos os 100 completem. É ideal para workloads que podem ser paralelizados.</p>
<h2>Diferenças Críticas e Como Escolher</h2>
<h3>Comparação Direta</h3>
<div class="table-wrap"><table><thead><tr><th>Aspecto</th><th>DaemonSet</th><th>Job</th></tr></thead><tbody><tr><td><strong>Propósito</strong></td><td>Agente que roda continuamente em cada nó</td><td>Tarefa que executa e termina</td></tr><tr><td><strong>Ciclo de vida</strong></td><td>Permanente (enquanto houver nós)</td><td>Temporário (até completar)</td></tr><tr><td><strong>Replicas</strong></td><td>Uma por nó</td><td>Configurável (completions)</td></tr><tr><td><strong>Restart</strong></td><td>Sim, se o Pod falhar</td><td>Sim, até backoffLimit</td></tr><tr><td><strong>Melhor para</strong></td><td>Monitoramento, logs, rede</td><td>Batch, backup, limpeza</td></tr></tbody></table></div>
<h3>Heurística de Decisão</h3>
<p>Faça-se essa pergunta: <strong>"Este processo deveria estar rodando o tempo todo, ou é uma tarefa que completa?"</strong></p>
<p>Se a resposta é "o tempo todo, em cada lugar", use <strong>DaemonSet</strong>. Se é "execute uma tarefa e deixe ela terminar", use <strong>Job</strong>. Se é "execute uma tarefa regularmente", use <strong>CronJob</strong>.</p>
<h2>Monitoramento e Observabilidade</h2>
<p>Ambos os recursos podem ser monitorados através da API do Kubernetes:</p>
<pre><code class="language-bash"># Ver DaemonSets
kubectl get daemonsets -A
kubectl describe daemonset node-exporter -n monitoring
Ver Jobs
kubectl get jobs -A
kubectl describe job backup-database
kubectl logs job/backup-database
Ver status de um CronJob
kubectl get cronjobs
kubectl get jobs --selector=cronjob-name=daily-cleanup</code></pre>
<p>Para monitoramento em Prometheus, você pode criar um ServiceMonitor que scrape a métrica <code>kube_job_status_failed</code> e <code>kube_daemonset_status_misscheduled</code> para detectar problemas.</p>
<pre><code class="language-yaml">apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: kubernetes-jobs-alerts
spec:
groups:
- name: kubernetes.jobs
rules:
- alert: JobFailed
expr: kube_job_status_failed{job=~".*"} > 0
for: 5m
annotations:
summary: "Job {{ $labels.job_name }} falhou"
- alert: DaemonSetMisscheduled
expr: kube_daemonset_status_misscheduled > 0
annotations:
summary: "DaemonSet {{ $labels.daemonset }} com Pods não agendados"</code></pre>
<h2>Conclusão</h2>
<p>Os três aprendizados principais que você deve carregar daqui:</p>
<ol>
<li><strong>DaemonSets são para infraestrutura contínua</strong>: quando você precisa de um agente rodando em cada máquina do cluster, DaemonSets eliminam a complexidade de gerenciar Pods manualmente. Use para monitoramento, coleta de logs, e qualquer ferramenta de sistema que precise estar "em todo lugar".</li>
</ol>
<ol>
<li><strong>Jobs são para tarefas discretas</strong>: a semântica fundamental é diferente — um Job não quer que você gerencie réplicas, quer que você defina quantas vezes a tarefa precisa completar com sucesso. Combine com CronJobs para agendamento e paralelismo para processamento em lote eficiente.</li>
</ol>
<ol>
<li><strong>Observabilidade é essencial</strong>: tanto DaemonSets quanto Jobs precisam de monitoramento ativo. Um DaemonSet com Pods não agendados é um sinal de alerta. Um Job que fica em estado "Failed" indefinidamente é um problema real que você só descobre com alertas.</li>
</ol>
<h2>Referências</h2>
<ul>
<li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/" target="_blank" rel="noopener noreferrer">Kubernetes DaemonSet Documentation</a></li>
<li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/" target="_blank" rel="noopener noreferrer">Kubernetes Jobs Documentation</a></li>
<li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/" target="_blank" rel="noopener noreferrer">Kubernetes CronJob Documentation</a></li>
<li><a href="https://github.com/prometheus-community/kube-state-metrics" target="_blank" rel="noopener noreferrer">Prometheus Kubernetes Metrics</a></li>
<li><a href="https://www.manning.com/books/kubernetes-in-action" target="_blank" rel="noopener noreferrer">Kubernetes in Action by Marko Lukša</a></li>
</ul>
<p><!-- FIM --></p>