Docker & Kubernetes

Como Usar DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote em Produção

13 min de leitura

Como Usar DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote em Produção

DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote 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 cada nó do cluster (como coleta de logs ou monitoramento), ou então precisa executar tarefas pontuais que terminam (como backup ou processamento em lote). Para esses casos, Kubernetes oferece duas primitivas poderosas: DaemonSets e Jobs. Neste artigo, vou te ensinar não apenas o "como fazer", mas o quando e por quê usar cada um deles. DaemonSets: Agentes que Rodam em Todo Lugar O Conceito Fundamental Um DaemonSet garante que uma cópia específica de um Pod seja executada em cada nó 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

<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 &quot;como fazer&quot;, 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 &quot;eu quero 3 réplicas espalhadas pelo cluster&quot;, enquanto um DaemonSet diz &quot;eu quero uma réplica em cada nó&quot;. 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: &quot;true&quot;

containers:

  • name: gpu-monitor

image: nvidia/cuda:11.0-base

command: [&quot;nvidia-smi&quot;]</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: &quot;postgres-svc&quot;

  • name: PGUSER

value: &quot;postgres&quot;

  • name: PGPASSWORD

valueFrom:

secretKeyRef:

name: postgres-secret

key: password

command: [&quot;sh&quot;, &quot;-c&quot;]

args:

- |

pg_dump --host=$PGHOST --username=$PGUSER --format=custom mydb &gt; /backup/mydb-$(date +%s).dump

if [ $? -eq 0 ]; then

echo &quot;Backup concluído com sucesso&quot;

exit 0

else

echo &quot;Backup falhou&quot;

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: &quot;0 2 *&quot; # 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: [&quot;python&quot;]

args:

- |

import os

import glob

from datetime import datetime, timedelta

cleanup_dir = &quot;/data/uploads&quot;

max_age_days = 30

cutoff_date = datetime.now() - timedelta(days=max_age_days)

for filepath in glob.glob(f&quot;{cleanup_dir}/*&quot;):

file_mtime = datetime.fromtimestamp(os.path.getmtime(filepath))

if file_mtime &lt; cutoff_date:

os.remove(filepath)

print(f&quot;Removido: {filepath}&quot;)

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: &quot;rabbitmq-svc&quot;

command: [&quot;python&quot;, &quot;/app/process.py&quot;]</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>&quot;Este processo deveria estar rodando o tempo todo, ou é uma tarefa que completa?&quot;</strong></p>

<p>Se a resposta é &quot;o tempo todo, em cada lugar&quot;, use <strong>DaemonSet</strong>. Se é &quot;execute uma tarefa e deixe ela terminar&quot;, use <strong>Job</strong>. Se é &quot;execute uma tarefa regularmente&quot;, 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=~&quot;.*&quot;} &gt; 0

for: 5m

annotations:

summary: &quot;Job {{ $labels.job_name }} falhou&quot;

  • alert: DaemonSetMisscheduled

expr: kube_daemonset_status_misscheduled &gt; 0

annotations:

summary: &quot;DaemonSet {{ $labels.daemonset }} com Pods não agendados&quot;</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 &quot;em todo lugar&quot;.</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 &quot;Failed&quot; 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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

O que Todo Dev Deve Saber sobre kubectl em Profundidade: Comandos, Contexts e Kubeconfig
O que Todo Dev Deve Saber sobre kubectl em Profundidade: Comandos, Contexts e Kubeconfig

Introdução: O que é kubectl e Por Que Importa O kubectl é a ferramenta de lin...

Boas Práticas de ArgoCD: GitOps Contínuo, App of Apps e Sync Policies para Times Ágeis
Boas Práticas de ArgoCD: GitOps Contínuo, App of Apps e Sync Policies para Times Ágeis

GitOps e ArgoCD: Fundamentos GitOps é um paradigma operacional onde o Git se...

O que Todo Dev Deve Saber sobre Kubernetes Events e Auditoria: Rastreando Mudanças no Cluster
O que Todo Dev Deve Saber sobre Kubernetes Events e Auditoria: Rastreando Mudanças no Cluster

Introdução: Por que Rastrear Mudanças no Kubernetes? No dia a dia de um clust...