Docker & Kubernetes

Dominando Pods em Kubernetes: Ciclo de Vida, Init Containers e Sidecar Pattern em Projetos Reais

15 min de leitura

Dominando Pods em Kubernetes: Ciclo de Vida, Init Containers e Sidecar Pattern em Projetos Reais

Entendendo Pods: A Unidade Fundamental do Kubernetes Um Pod é a menor unidade deployável no Kubernetes, funcionando como um wrapper ao redor de um ou mais containers. Diferente de outras plataformas de orquestração, o Kubernetes não gerencia containers diretamente — ele gerencia Pods. Um Pod tipicamente contém um único container (a abordagem recomendada), mas pode hospedar múltiplos containers que precisam trabalhar em conjunto com acoplamento forte. A grande diferença de um Pod para um container em standalone é que todos os containers dentro de um Pod compartilham o mesmo namespace de rede. Isso significa que eles possuem o mesmo endereço IP, podem se comunicar via localhost e compartilham volumes. Essa característica é fundamental para entender padrões como Init Containers e Sidecars, que exploram justamente essa proximidade. Ciclo de Vida de um Pod Fases de Execução O ciclo de vida de um Pod passa por várias fases bem definidas. Entender essas fases é crucial para debugar problemas e implementar lógicas de

<h2>Entendendo Pods: A Unidade Fundamental do Kubernetes</h2>

<p>Um Pod é a menor unidade deployável no Kubernetes, funcionando como um wrapper ao redor de um ou mais containers. Diferente de outras plataformas de orquestração, o Kubernetes não gerencia containers diretamente — ele gerencia Pods. Um Pod tipicamente contém um único container (a abordagem recomendada), mas pode hospedar múltiplos containers que precisam trabalhar em conjunto com acoplamento forte.</p>

<p>A grande diferença de um Pod para um container em standalone é que todos os containers dentro de um Pod compartilham o mesmo namespace de rede. Isso significa que eles possuem o mesmo endereço IP, podem se comunicar via localhost e compartilham volumes. Essa característica é fundamental para entender padrões como Init Containers e Sidecars, que exploram justamente essa proximidade.</p>

<h2>Ciclo de Vida de um Pod</h2>

<h3>Fases de Execução</h3>

<p>O ciclo de vida de um Pod passa por várias fases bem definidas. Entender essas fases é crucial para debugar problemas e implementar lógicas de inicialização sofisticadas. As principais fases são: <strong>Pending</strong>, <strong>Running</strong>, <strong>Succeeded</strong>, <strong>Failed</strong> e <strong>Unknown</strong>.</p>

<p>A fase <strong>Pending</strong> ocorre quando o Pod foi aceito pelo cluster, mas ainda está sendo preparado — isso pode envolver o download da imagem, alocação de recursos ou execução de init containers. <strong>Running</strong> indica que pelo menos um container está em execução. <strong>Succeeded</strong> é quando todos os containers terminaram com sucesso (comum em jobs). <strong>Failed</strong> ocorre quando algum container falhou permanentemente. <strong>Unknown</strong> é um estado raro que indica perda de comunicação com o kubelet.</p>

<p>Além das fases, existos as <strong>condições</strong> (conditions), que fornecem informações mais granulares. A condição <code>PodScheduled</code> indica se o Pod foi associado a um nó. <code>Initialized</code> mostra se todos os init containers completaram. <code>Ready</code> significa que o Pod está pronto para receber tráfego. <code>ContainersReady</code> verifica se todos os containers estão prontos.</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: ciclo-vida-demo

namespace: default

spec:

containers:

  • name: app

image: nginx:1.21

lifecycle:

postStart:

exec:

command: [&quot;/bin/sh&quot;, &quot;-c&quot;, &quot;echo &#039;Container iniciado&#039; &gt; /tmp/startup.log&quot;]

preStop:

exec:

command: [&quot;/bin/sh&quot;, &quot;-c&quot;, &quot;sleep 15&quot;]

terminationGracePeriodSeconds: 30</code></pre>

<p>No exemplo acima, o <code>postStart</code> é executado imediatamente após o container ser criado, enquanto <code>preStop</code> é acionado quando o Pod recebe um sinal de término. O <code>terminationGracePeriodSeconds</code> define quanto tempo o Kubernetes espera antes de forçar o encerramento.</p>

<h3>Probes: Verificando a Saúde do Pod</h3>

<p>As probes são mecanismos de verificação que o Kubernetes usa para determinar a saúde de um container. Existem três tipos: <strong>liveness probe</strong>, <strong>readiness probe</strong> e <strong>startup probe</strong>.</p>

<p>A <strong>liveness probe</strong> verifica se o container está vivo e deve ser reiniciado se falhar. A <strong>readiness probe</strong> determina se o container está pronto para receber tráfego — um container pode estar vivo mas não pronto. A <strong>startup probe</strong> é usada para containers que levam tempo para inicializar, prevenindo que o Kubernetes reinicie a aplicação antes que ela esteja realmente pronta.</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: pod-com-probes

spec:

containers:

  • name: aplicacao

image: nginx:1.21

ports:

  • containerPort: 80

livenessProbe:

httpGet:

path: /health

port: 80

initialDelaySeconds: 10

periodSeconds: 5

timeoutSeconds: 2

failureThreshold: 3

readinessProbe:

httpGet:

path: /ready

port: 80

initialDelaySeconds: 5

periodSeconds: 3

timeoutSeconds: 1

successThreshold: 1

startupProbe:

httpGet:

path: /startup

port: 80

failureThreshold: 30

periodSeconds: 1</code></pre>

<h2>Init Containers: Preparação Antes da Execução Principal</h2>

<h3>Conceito e Comportamento</h3>

<p>Init containers são containers especiais que executam antes dos containers principais de um Pod. Eles são ideais para tarefas de inicialização como download de configurações, preparação de volumes, migrations de banco de dados ou espera por dependências externas. Um ponto crucial: todos os init containers <strong>devem</strong> ser completados com sucesso antes que o container principal inicie. Se um init container falhar, o Pod será reiniciado (exceto se a política de reinício proibir).</p>

<p>Os init containers compartilham o mesmo ciclo de vida que o Pod — eles têm acesso aos mesmos volumes e variáveis de ambiente. A execução é sequencial: um init container só inicia após o anterior terminar com sucesso.</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: pod-com-init-containers

spec:

initContainers:

  • name: aguardar-database

image: busybox:1.35

command: [&#039;sh&#039;, &#039;-c&#039;, &#039;until nc -z postgres-service 5432; do echo waiting for db; sleep 2; done;&#039;]

  • name: migration

image: postgres:14

command: [&#039;psql&#039;, &#039;-h&#039;, &#039;postgres-service&#039;, &#039;-U&#039;, &#039;usuario&#039;, &#039;-d&#039;, &#039;meudb&#039;, &#039;-c&#039;, &#039;CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(100));&#039;]

env:

  • name: PGPASSWORD

value: &quot;senha123&quot;

  • name: carregar-config

image: alpine:3.16

command: [&#039;sh&#039;, &#039;-c&#039;, &#039;wget -O /config/app.yaml http://config-service/app.yaml&#039;]

volumeMounts:

  • name: config-volume

mountPath: /config

containers:

  • name: aplicacao

image: minha-app:1.0

volumeMounts:

  • name: config-volume

mountPath: /etc/config

volumes:

  • name: config-volume

emptyDir: {}</code></pre>

<p>Neste exemplo, temos três init containers executando em sequência: primeiro aguardando a disponibilidade do PostgreSQL, depois executando uma migration, e finalmente baixando um arquivo de configuração. O container principal só inicia após todos esses passos serem concluídos com sucesso.</p>

<h3>Casos de Uso Práticos</h3>

<p>Init containers brilham em cenários onde você precisa garantir pré-condições antes da execução principal. Um exemplo real é quando sua aplicação depende de dados de um ConfigMap que precisa ser processado, ou quando você precisa verificar se serviços dependentes estão online. Outro caso comum é a inicialização de volumes compartilhados com dados vindos de um repositório ou API externa.</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: api-gateway

spec:

initContainers:

  • name: fetch-service-registry

image: curlimages/curl:7.85.0

command:

  • sh
  • -c

- |

curl -s http://consul:8500/v1/catalog/services &gt; /shared/services.json

echo &quot;Service registry loaded: $(cat /shared/services.json | wc -c) bytes&quot;

volumeMounts:

  • name: shared-data

mountPath: /shared

containers:

  • name: gateway

image: kong:3.0

volumeMounts:

  • name: shared-data

mountPath: /shared

readOnly: true

volumes:

  • name: shared-data

emptyDir: {}</code></pre>

<h2>Sidecar Pattern: Containers Auxiliares em Ação</h2>

<h3>O que é e Como Funciona</h3>

<p>O padrão Sidecar envolve um container auxiliar (sidecar) que roda ao lado do container principal, compartilhando o mesmo Pod. Diferente dos init containers que executam uma vez e finalizam, sidecars rodam continuamente durante toda a vida do Pod principal. Eles são usados para adicionar funcionalidades sem modificar a imagem principal: logging, monitoramento, proxy, cache, sincronização de configurações, entre outros.</p>

<p>A beleza do padrão Sidecar está na separação de responsabilidades — sua aplicação principal não precisa conhecer detalhes sobre logging centralizado, por exemplo. Um sidecar de logging fica encarregado disso, lendo os logs da aplicação e enviando para um serviço centralizado como ELK ou Datadog.</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: app-with-logging-sidecar

labels:

app: myapp

spec:

containers:

  • name: aplicacao

image: node:16-alpine

command: [&quot;node&quot;, &quot;app.js&quot;]

ports:

  • containerPort: 3000

volumeMounts:

  • name: shared-logs

mountPath: /var/log/app

env:

  • name: LOG_DIR

value: /var/log/app

  • name: log-forwarder

image: fluent/fluent-bit:2.0

volumeMounts:

  • name: shared-logs

mountPath: /var/log/app

readOnly: true

  • name: fluent-config

mountPath: /fluent-bit/etc

ports:

  • containerPort: 2020

volumes:

  • name: shared-logs

emptyDir: {}

  • name: fluent-config

configMap:

name: fluent-bit-config</code></pre>

<p>Neste exemplo, o container principal (Node.js) escreve logs em um diretório compartilhado (<code>/var/log/app</code>). O sidecar Fluent Bit monitora esse diretório e envia os logs para um serviço centralizado. Ambos compartilham o namespace de rede, então o sidecar pode se comunicar com sistemas externos usando localhost se necessário.</p>

<h3>Casos de Uso Reais</h3>

<p>Um caso muito comum é o <strong>proxy sidecar</strong> para service mesh (como Istio). Nesse padrão, um proxy Envoy roda como sidecar e intercepta todo o tráfego de rede, adicionando recursos como circuit breaking, retry automático, rate limiting e observabilidade — tudo transparentemente para a aplicação.</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: app-com-proxy-sidecar

annotations:

sidecar.istio.io/inject: &quot;true&quot;

spec:

containers:

  • name: aplicacao

image: python:3.9

command: [&quot;python&quot;, &quot;-m&quot;, &quot;http.server&quot;, &quot;8000&quot;]

ports:

  • containerPort: 8000
  • name: proxy-sidecar

image: envoyproxy/envoy:v1.24-latest

ports:

  • containerPort: 15001

name: envoy

protocol: TCP

volumeMounts:

  • name: proxy-config

mountPath: /etc/envoy

volumes:

  • name: proxy-config

configMap:

name: envoy-config</code></pre>

<p>Outro uso é o <strong>sidecar de sincronização de configuração</strong>. Imagine uma aplicação que precisa de atualizações de configuração sem restart — um sidecar pode monitorar um ConfigMap ou um serviço de configuração, e quando mudanças são detectadas, notificar a aplicação via signal ou API.</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: app-com-config-watcher

spec:

serviceAccountName: config-watcher

containers:

  • name: aplicacao

image: minha-app:latest

ports:

  • containerPort: 8080

volumeMounts:

  • name: config

mountPath: /etc/config

  • name: config-watcher

image: alpine:3.16

command:

  • sh
  • -c

- |

while true; do

wget -O /tmp/new-config.yaml http://config-service:9000/config.yaml

if ! diff -q /tmp/new-config.yaml /etc/config/app.yaml &gt; /dev/null 2&gt;&amp;1; then

cp /tmp/new-config.yaml /etc/config/app.yaml

kill -HUP 1 # Sinal para a aplicação recarregar config

fi

sleep 10

done

volumeMounts:

  • name: config

mountPath: /etc/config

volumes:

  • name: config

emptyDir: {}</code></pre>

<h3>Limitações e Boas Práticas</h3>

<p>Apesar da flexibilidade, o padrão Sidecar tem limitações. Cada sidecar consome recursos (CPU, memória) e adiciona overhead. Se você tem muitos Pods com sidecars pesados, seu cluster pode ficar ineficiente. Por isso, em cenários de grande escala, considera-se usar service mesh no nível de cluster em vez de adicionar sidecars em cada Pod.</p>

<p>Além disso, debugar problemas em um Pod com múltiplos containers fica mais complexo — você precisa verificar logs e status de múltiplos containers. Use nomes descritivos, configure probes adequadamente para cada sidecar e documente a responsabilidade de cada um.</p>

<h2>Integrando Init Containers e Sidecars em um Exemplo Completo</h2>

<h3>Arquitetura Prática</h3>

<p>Para solidificar o aprendizado, vamos combinar init containers e sidecars em um cenário real: uma aplicação Django com banco de dados, cache Redis, logging centralizado e um proxy de segurança.</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: django-app-production

labels:

app: django

version: v1

spec:

serviceAccountName: django-sa

Init Containers: Preparação

initContainers:

  • name: wait-for-postgres

image: busybox:1.35

command: [&#039;sh&#039;, &#039;-c&#039;, &#039;until nc -z postgres.default.svc.cluster.local 5432; do echo &quot;Aguardando PostgreSQL...&quot;; sleep 3; done&#039;]

  • name: wait-for-redis

image: busybox:1.35

command: [&#039;sh&#039;, &#039;-c&#039;, &#039;until nc -z redis.default.svc.cluster.local 6379; do echo &quot;Aguardando Redis...&quot;; sleep 3; done&#039;]

  • name: run-migrations

image: python:3.10

command: [&#039;sh&#039;, &#039;-c&#039;, &#039;python manage.py migrate --noinput&#039;]

env:

  • name: DATABASE_URL

valueFrom:

secretKeyRef:

name: django-secrets

key: database_url

  • name: DEBUG

value: &quot;false&quot;

Containers Principais e Sidecars

containers:

  • name: django-app

image: minha-django-app:1.2

ports:

  • name: http

containerPort: 8000

env:

  • name: DATABASE_URL

valueFrom:

secretKeyRef:

name: django-secrets

key: database_url

  • name: REDIS_URL

value: &quot;redis://127.0.0.1:6379&quot;

  • name: LOG_LEVEL

value: &quot;INFO&quot;

volumeMounts:

  • name: static-files

mountPath: /app/staticfiles

  • name: shared-logs

mountPath: /var/log/app

livenessProbe:

httpGet:

path: /health/live

port: 8000

initialDelaySeconds: 30

periodSeconds: 10

readinessProbe:

httpGet:

path: /health/ready

port: 8000

initialDelaySeconds: 10

periodSeconds: 5

  • name: redis-cache

image: redis:7-alpine

ports:

  • containerPort: 6379

command: [&quot;redis-server&quot;]

resources:

limits:

memory: &quot;256Mi&quot;

cpu: &quot;100m&quot;

  • name: log-forwarder

image: fluent/fluent-bit:2.0

volumeMounts:

  • name: shared-logs

mountPath: /var/log/app

readOnly: true

  • name: fluent-bit-config

mountPath: /fluent-bit/etc

env:

  • name: ELASTICSEARCH_HOST

value: &quot;elasticsearch.logging.svc.cluster.local&quot;

  • name: ELASTICSEARCH_PORT

value: &quot;9200&quot;

  • name: security-proxy

image: envoyproxy/envoy:v1.24-latest

ports:

  • containerPort: 9000

name: proxy

volumeMounts:

  • name: envoy-config

mountPath: /etc/envoy

volumes:

  • name: static-files

emptyDir: {}

  • name: shared-logs

emptyDir: {}

  • name: fluent-bit-config

configMap:

name: fluent-bit-config

  • name: envoy-config

configMap:

name: envoy-config

restartPolicy: Always

terminationGracePeriodSeconds: 30</code></pre>

<p>Nesta arquitetura:</p>

<ul>

<li><strong>Init Containers</strong>: Aguardam dependências externas (PostgreSQL e Redis) e executam migrations do banco de dados.</li>

<li><strong>Django App</strong>: Container principal que roda a aplicação.</li>

<li><strong>Redis Sidecar</strong>: Cache em memória compartilhado via localhost.</li>

<li><strong>Log Forwarder Sidecar</strong>: Coleta logs e envia para Elasticsearch.</li>

<li><strong>Security Proxy Sidecar</strong>: Controla acesso e adiciona camada de segurança.</li>

</ul>

<p>Todos os containers compartilham o mesmo namespace de rede, volumes e variáveis de ambiente, permitindo comunicação eficiente e reutilização de recursos.</p>

<h2>Conclusão</h2>

<p>O domínio de Pods, Init Containers e Sidecar Pattern é fundamental para trabalhar profissionalmente com Kubernetes. <strong>Primeiro ponto</strong>: entender que um Pod é uma abstração acima de containers que permite compartilhamento de rede, volumes e namespace — isso é a base para todo o resto. <strong>Segundo ponto</strong>: Init Containers são para tarefas de uma única execução que devem completar antes da aplicação principal iniciar, ideal para migrações, downloads de configuração e verificação de dependências. <strong>Terceiro ponto</strong>: Sidecars são containers que rodam continuamente ao lado da aplicação principal, perfeitos para logging, proxy, monitoramento e sincronização de configuração — combinando essas três técnicas você consegue construir sistemas robustos e escaláveis.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://kubernetes.io/docs/concepts/workloads/pods/" target="_blank" rel="noopener noreferrer">Documentação Oficial do Kubernetes - Pods</a></li>

<li><a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" target="_blank" rel="noopener noreferrer">Kubernetes Init Containers - Guia Oficial</a></li>

<li><a href="https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/" target="_blank" rel="noopener noreferrer">The Sidecar Pattern in Kubernetes</a></li>

<li><a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/" target="_blank" rel="noopener noreferrer">Kubernetes Pod Lifecycle Documentation</a></li>

<li><a href="https://istio.io/latest/docs/setup/additional-setup/sidecar-injection/" target="_blank" rel="noopener noreferrer">Istio Sidecar Injection and Service Mesh Patterns</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

Network Policies em Kubernetes: Isolamento de Rede entre Pods: Do Básico ao Avançado
Network Policies em Kubernetes: Isolamento de Rede entre Pods: Do Básico ao Avançado

Network Policies em Kubernetes: Isolamento de Rede entre Pods Network Policie...

O que Todo Dev Deve Saber sobre Dockerfile em Profundidade: Cada Instrução e seu Impacto no Build
O que Todo Dev Deve Saber sobre Dockerfile em Profundidade: Cada Instrução e seu Impacto no Build

Introdução: Por que entender Dockerfile é fundamental Um Dockerfile é um scri...

RBAC em Kubernetes: Roles, ClusterRoles, Bindings e ServiceAccounts na Prática
RBAC em Kubernetes: Roles, ClusterRoles, Bindings e ServiceAccounts na Prática

O que é RBAC e por que você precisa disso? RBAC (Role-Based Access Control) é...