Kubernetes básico: Pod, Deployment e Service

1. Visão geral

Kubernetes tem dezenas de tipos de recursos, mas 80% do trabalho diário gira em torno de três conceitos: Pod, Deployment, Service. Este artigo o ajuda a entender a natureza de cada recurso, como eles se conectam e como escrever um conjunto de manifestos YAML para implantar uma aplicação que realmente funcione no cluster.

Depois de ler, você será capaz de fazer deploy de uma aplicação Spring Boot no Kubernetes, escalá-la em múltiplas réplicas e expô-la para fora através de um Service - tudo apenas usando kubectl e alguns arquivos YAML.

Nota: Este artigo assume que você já tem um cluster (Minikube, Kind, ou na nuvem) e kubectl já está apontando para o contexto correto.

2. Pod - A menor unidade de implantação

2.1. O que é um Pod

Um Pod é um grupo de um ou mais containers que compartilham o namespace de rede (mesmo IP, mesmo espaço de portas) e volumes. Na prática, a maioria dos Pods contém apenas um container de aplicação.

apiVersion: v1
kind: Pod
metadata:
  name: order-service-pod
  labels:
    app: order-service
spec:
  containers:
    - name: order-service
      image: nonladev/order-service:1.0
      ports:
        - containerPort: 8080
      resources:
        requests:
          cpu: "250m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"

Explicação:

  • metadata.labels anexa o rótulo app: order-service - é assim que o Kubernetes “identifica” Pods; Service e Deployment usam este rótulo para selecionar os objetos associados.
  • resources.requests são os recursos mínimos que o scheduler precisa para colocar o Pod em um nó; limits é o limite máximo - exceder isso causará throttle (CPU) ou encerramento forçado (memória - OOMKilled).
  • containerPort: 8080 é apenas uma declaração informativa, não abre automaticamente a porta para fora.

Aplicar e verificar:

kubectl apply -f order-service-pod.yaml
kubectl get pods
NAME                 READY   STATUS    RESTARTS   AGE
order-service-pod    1/1     Running   0          5s

2.2. Por que não criar Pods diretamente

Pods criados “nus” como acima têm um grande problema: se o Nó que contém o Pod morrer ou o Pod falhar, ninguém o recria automaticamente. Pod não tem mecanismo de self-healing ou scaling.

Nota: Em ambientes de produção, quase nunca crie Pods diretamente. Sempre use Deployment (ou StatefulSet, Job) para gerenciar Pods.

3. Deployment - Gerenciamento do ciclo de vida do Pod

3.1. Declarando um Deployment

Deployment cria e gerencia um conjunto de Pods através de ReplicaSet, garantindo que sempre há o número correto de réplicas rodando, e suporta rolling update sem downtime.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10

Explicação:

  • replicas: 3 instrui o Deployment a manter sempre 3 Pods rodando simultaneamente - se um Pod morrer, o Deployment cria automaticamente um novo para substituí-lo.
  • selector.matchLabels deve corresponder a template.metadata.labels. Esta é a ligação obrigatória entre o Deployment e os Pods que ele gerencia.
  • readinessProbe determina se o Pod deve receber tráfego (usado pelo Service); livenessProbe determina se o Pod precisa ser reiniciado. Confundir essas duas probes é um erro muito comum para quem está começando com Kubernetes.

Aplicar e observar:

kubectl apply -f order-service-deployment.yaml
kubectl get deployments
kubectl get pods -l app=order-service
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
order-service   3/3     3            3           12s

NAME                             READY   STATUS    RESTARTS   AGE
order-service-7d4f9c8b6d-2kx9p   1/1     Running   0          12s
order-service-7d4f9c8b6d-7h2mn   1/1     Running   0          12s
order-service-7d4f9c8b6d-xq1lw   1/1     Running   0          12s

3.2. Scaling e rolling update

Escale aumentando/diminuindo o número de réplicas sem precisar editar o YAML:

kubectl scale deployment order-service --replicas=5

Rolling update quando muda a imagem - o Deployment substitui automaticamente Pods antigos por novos seguindo uma estratégia de atualização gradual, sem downtime:

kubectl set image deployment/order-service order-service=nonladev/order-service:1.1
kubectl rollout status deployment/order-service
Waiting for deployment "order-service" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "order-service" successfully rolled out

Se a nova versão tem um bug, faça rollback para a versão anterior imediatamente:

kubectl rollout undo deployment/order-service

3.3. Configurando a estratégia de rolling update

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1

maxUnavailable: 1 permite que no máximo 1 Pod fique “indisponível” durante a atualização; maxSurge: 1 permite criar no máximo 1 Pod adicional além do número de réplicas declaradas. Com replicas: 3, o cluster terá entre 2-4 Pods durante a transição, mas nunca menos de 2.

4. Service - Expor Pods de forma estável

4.1. Por que precisamos de um Service

Pods têm seu próprio IP, mas este IP muda toda vez que o Pod é recriado (crash, rolling update, scaling). Service fornece um endereço estável (ClusterIP + nome DNS) na frente do grupo de Pods, balanceando automaticamente o tráfego com base no seletor de rótulo - não importa quantas vezes os Pods abaixo mudem.

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP

Explicação:

  • selector.app: order-service corresponde ao rótulo nos Pods (colocado pelo Deployment em template.metadata.labels) - o Service encontra e monitora automaticamente esses Pods através de um objeto intermediário chamado Endpoints.
  • port: 80 é a porta que o Service escuta dentro do cluster; targetPort: 8080 é a porta que o container realmente expõe. Esses dois números não precisam ser idênticos.
  • type: ClusterIP (padrão) é acessível apenas de dentro do cluster - adequado para serviços internos entre microsserviços.

Verificar Service e Endpoints:

kubectl apply -f order-service-service.yaml
kubectl get svc order-service
kubectl get endpoints order-service
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
order-service   ClusterIP   10.96.142.201   <none>        80/TCP    4s

NAME            ENDPOINTS                                   AGE
order-service   10.244.1.5:8080,10.244.1.6:8080,+1 more     4s

4.2. Os tipos de Service

Type Escopo de acesso Quando usar
ClusterIP Apenas dentro do cluster Comunicação entre microsserviços internos
NodePort Abre uma porta fixa (30000-32767) em todos os Nós Teste rápido, ambiente de desenvolvimento/on-premise
LoadBalancer Fornece um IP público através do provedor de nuvem Expor o serviço para a Internet na nuvem

Exemplo de exposição para teste rápido no Minikube:

apiVersion: v1
kind: Service
metadata:
  name: order-service-external
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080
  type: NodePort
minikube service order-service-external --url

Nota: Usar NodePort em produção é um anti-pattern - faixa de porta limitada, sem terminação TLS ou roteamento baseado em saúde como Ingress. Para produção na nuvem, priorize LoadBalancer ou Ingress Controller.

5. Combinando os três em uma aplicação real

Todo o pipeline: cliente chama o Service → o Service balanceia a carga para os Pods gerenciados pelo Deployment.

Cliente
  │
  ▼
Service (order-service, ClusterIP 10.96.142.201:80)
  │  (seletor de rótulo: app=order-service)
  ▼
┌─────────────┬─────────────┬─────────────┐
│  Pod #1     │  Pod #2     │  Pod #3     │
│  :8080      │  :8080      │  :8080      │
└─────────────┴─────────────┴─────────────┘
       ▲ gerenciado por
  Deployment (order-service, replicas: 3)

Para facilitar a implantação de uma vez, você pode combinar tanto Deployment quanto Service em um único arquivo, separados por ---:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
kubectl apply -f order-service-all.yaml

6. Escrever teste para o endpoint de readiness antes do deploy

Antes de a probe do Kubernetes confiar em /actuator/health/readiness, você deve garantir que este endpoint retorne o estado correto no código do Spring Boot.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.health.readiness-state.enabled=true
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderServiceHealthTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void readinessProbe_shouldReturnUp() {
        ResponseEntity<String> response =
                restTemplate.getForEntity("/actuator/health/readiness", String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).contains("\"status\":\"UP\"");
    }
}

Este teste chama TestRestTemplate no endpoint de readiness real, confirmando HTTP 200 e que o body contém UP. Este é exatamente o endpoint que readinessProbe no Deployment chamará periodicamente - se o teste falhar aqui, o Pod também nunca ficará “Ready” no cluster.

7. Erros comuns

  • Seletor não corresponde ao rótulo: Service ou Deployment não encontra nenhum Pod (Endpoints vazio) porque selector e labels foram escritos incorretamente ou diferem. Sempre use kubectl get endpoints <svc-name> para confirmar.
  • Falta de readinessProbe: Service ainda roteia tráfego para Pods que não completaram a inicialização da aplicação, causando erros 502/503 logo após o deploy.
  • Resource requests/limits ausentes: O Pod pode ser colocado incorretamente no nó pelo scheduler, ou ser OOMKilled inesperadamente quando o tráfego aumenta.
  • Usar Pod simples em produção: sem self-healing, perder um Pod é perder permanentemente, precisa ser recriado manualmente.
  • Tag de imagem latest: rolling update não percebe mudanças, Kubernetes pensa que a imagem não mudou e não faz pull novamente. Sempre use uma tag de versão específica.

8. Conclusão

Pod é a unidade de execução de container mais pequena, Deployment gerencia o ciclo de vida e scaling do conjunto de Pods, enquanto Service fornece um endereço estável para acessá-los mesmo que os Pods abaixo mudem continuamente. Dominar esses três pilares é a base para você avançar para Ingress, ConfigMap/Secret, ou Helm charts depois. O próximo passo deve ser: fazer deploy de uma aplicação real em Spring Boot, observar o rolling update, e tentar matar um Pod manualmente para ver como o Deployment se recupera.

kubernetesdevopscontainerorchestration