Nesta aula, vamos mergulhar nos conceitos essenciais de Pods e workloads no Kubernetes. Compreender esses elementos é fundamental para orquestrar aplicações containerizadas de forma eficiente, garantindo alta disponibilidade, escalabilidade e resiliência. Você aprenderá não apenas a definição teórica, mas também como aplicar esses conceitos na prática, usando comandos e manifestos YAML.

O Kubernetes é uma plataforma de orquestração de contêineres que abstrai a complexidade de gerenciar aplicações distribuídas. No centro dessa abstração estão os Pods, a menor unidade de computação que o Kubernetes pode criar e gerenciar. A partir deles, construímos workloads como Deployments, que garantem a execução contínua e a evolução controlada das aplicações. Vamos explorar cada um desses componentes, suas interações e o ciclo de vida que os governa.

Pods

Um Pod é a menor unidade de implantação no Kubernetes. Ele representa um processo único ou um grupo de processos que compartilham o mesmo contexto de execução, incluindo rede e armazenamento. Cada Pod possui seu próprio endereço IP e um conjunto de portas, e os contêineres dentro de um Pod compartilham esses recursos, podendo se comunicar entre si via localhost. Essa arquitetura é ideal para casos em que múltiplos contêineres precisam operar de forma coesa, como um contêiner de aplicação e um sidecar para coleta de logs.

Os Pods são efêmeros por natureza: eles são criados, executam e podem ser destruídos a qualquer momento. Quando um Pod é substituído (por exemplo, após uma falha ou atualização), ele é recriado com um novo IP, o que torna o endereço IP não confiável para comunicação direta. Para resolver isso, utilizamos Services, que fornecem um endpoint estável. Apesar disso, entender como criar e gerenciar Pods diretamente é essencial para dominar o Kubernetes.

Criando um Pod

Podemos criar um Pod de forma imperativa com o comando kubectl run, mas a prática recomendada é usar manifestos YAML, que são declarativos e versionáveis. Veja um exemplo de manifesto para um Pod simples:

apiVersion: v1
kind: Pod
metadata:
  name: meu-pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80

Para aplicar esse manifesto, salve-o em um arquivo (por exemplo, pod.yaml) e execute:

kubectl apply -f pod.yaml

Para verificar se o Pod está em execução, use kubectl get pods. Você verá o nome do Pod, o status (Running, Pending, etc.) e outras informações. Para obter detalhes mais completos, utilize kubectl describe pod meu-pod, que exibe eventos, condições e configurações.

É importante notar que criar Pods diretamente não é a melhor prática para produção. Em vez disso, recomendamos usar workloads como Deployments, que fornecem gerenciamento de ciclo de vida, escalabilidade e atualizações contínuas.

ReplicaSets

Um ReplicaSet é um controlador que garante que um número especificado de réplicas de um Pod esteja sempre em execução. Ele monitora o cluster e, se algum Pod falhar ou for excluído, o ReplicaSet cria um novo para manter o número desejado. Isso garante a disponibilidade da aplicação, mesmo diante de falhas.

O ReplicaSet utiliza seletores de rótulos (labels) para identificar os Pods que deve gerenciar. No manifesto, definimos o seletor e o template do Pod. O template é um modelo de Pod que será usado para criar novas réplicas. Aqui está um exemplo de manifesto de ReplicaSet:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: meu-replicaset
spec:
  replicas: 3
  selector:
    matchLabels:
      app: meu-app
  template:
    metadata:
      labels:
        app: meu-app
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

Para criar o ReplicaSet, execute kubectl apply -f replicaset.yaml. Depois, verifique com kubectl get replicasets e kubectl get pods para ver as réplicas em execução. Se você deletar um dos Pods, o ReplicaSet criará um novo automaticamente.

Apesar de os ReplicaSets serem úteis, eles não suportam atualizações contínuas (rolling updates) nem rollbacks. Para isso, usamos Deployments, que gerenciam ReplicaSets internamente e oferecem uma abstração de mais alto nível.

Deployments

Um Deployment é um controlador que gerencia ReplicaSets e fornece recursos de atualização declarativa, escalabilidade e rollback. Ele é o workload mais comum para aplicações stateless. Com um Deployment, você define o estado desejado da aplicação (imagem, réplicas, etc.) e o Kubernetes trabalha para alcançar esse estado, realizando atualizações de forma controlada.

O Deployment cria um ReplicaSet para cada versão da aplicação. Quando você atualiza a imagem ou a configuração, o Deployment cria um novo ReplicaSet e gradualmente escala o novo enquanto reduz o antigo, garantindo que a aplicação permaneça disponível durante o processo. Se algo der errado, você pode reverter para uma versão anterior com um simples comando.

Vamos criar um Deployment de exemplo:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: meu-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: meu-app
  template:
    metadata:
      labels:
        app: meu-app
    spec:
      containers:
      - name: nginx
        image: nginx:1.20
        ports:
        - containerPort: 80

Aplique com kubectl apply -f deployment.yaml. Para ver o status, use kubectl get deployments, kubectl get replicasets e kubectl get pods. O Deployment garante que o número especificado de réplicas esteja sempre em execução.

Uma das grandes vantagens do Deployment é a facilidade de escalar. Você pode alterar o número de réplicas com kubectl scale deployment meu-deployment --replicas=5 ou editando o manifesto e aplicando novamente. Além disso, atualizações são simples: mude a imagem no manifesto e aplique, ou use kubectl set image deployment/meu-deployment nginx=nginx:1.21. O Kubernetes fará uma atualização contínua, garantindo que não haja downtime.

Se necessário, você pode reverter uma atualização com kubectl rollout undo deployment/meu-deployment. Para ver o histórico de versões, use kubectl rollout history deployment/meu-deployment.

Ciclo de vida

O ciclo de vida de um Pod é composto por várias fases: Pending, Running, Succeeded, Failed, e Unknown. Entender essas fases é crucial para diagnosticar problemas e gerenciar aplicações.

Pending: o Pod foi aceito pelo cluster, mas ainda não está em execução. Isso pode ocorrer porque o nó ainda está inicializando ou porque não há recursos suficientes. O scheduler está tentando alocar o Pod a um nó.

Running: o Pod foi alocado a um nó e todos os contêineres foram criados. Pelo menos um contêiner está em execução ou está em processo de inicialização.

Succeeded: todos os contêineres terminaram com sucesso e não serão reiniciados. Isso é comum em jobs ou pods de tarefa única.

Failed: pelo menos um contêiner terminou com falha (código de saída diferente de zero). O Pod pode ser reiniciado dependendo da política de reinicialização.

Unknown: o estado do Pod não pode ser obtido, geralmente devido a uma falha de comunicação com o nó.

Além das fases, os Pods têm condições que fornecem mais detalhes: PodScheduled, Initialized, ContainersReady e Ready. Essas condições são booleanas e ajudam a entender o progresso e a prontidão do Pod.

O Kubernetes também define estados para os contêineres: Waiting, Running e Terminated. O estado Waiting indica que o contêiner está aguardando para ser iniciado (por exemplo, baixando a imagem). Running significa que o contêiner está em execução. Terminated indica que o contêiner terminou, seja com sucesso ou falha.

Para inspecionar o ciclo de vida de um Pod, use kubectl describe pod, que mostra eventos como criação, agendamento, pull de imagem e início dos contêineres. Esses eventos são inestimáveis para depuração.

Além disso, você pode definir probes (liveness, readiness e startup) para controlar quando um contêiner é considerado saudável e pronto para receber tráfego. Essas sondas são configuradas no manifesto do Pod e são essenciais para garantir que apenas Pods saudáveis recebam tráfego.

Boas práticas e observações finais

Ao trabalhar com Pods e workloads, é importante seguir algumas boas práticas:

  • Prefira usar Deployments em vez de Pods ou ReplicaSets diretamente, a menos que você tenha um caso específico que exija controle fino.
  • Use labels e seletores de forma consistente para organizar e selecionar recursos.
  • Defina recursos (CPU e memória) para cada contêiner, evitando que um Pod consuma todos os recursos do nó.
  • Utilize health checks (liveness e readiness probes) para que o Kubernetes possa gerenciar a disponibilidade da aplicação.
  • Mantenha os manifestos versionados e use ambientes separados para desenvolvimento, staging e produção.

Com esse conhecimento, você está pronto para criar e gerenciar aplicações no Kubernetes de forma eficiente e robusta.

Referências

Exercícios

  1. Crie um Pod chamado nginx-pod com a imagem nginx:latest e exponha a porta 80. Use um manifesto YAML e aplique-o. Verifique se o Pod está em execução.

    ✓ Resposta: Crie um arquivo pod.yaml com o conteúdo:
    apiVersion: v1
    kind: Pod
    metadata:
      name: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
    
    Aplique com kubectl apply -f pod.yaml e verifique com kubectl get pods.
  2. Dado o ReplicaSet abaixo, quantas réplicas ele manterá? E se você deletar um Pod, o que acontece?

    apiVersion: apps/v1
    kind: ReplicaSet
    metadata:
      name: rs-exemplo
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: meu-app
      template:
        metadata:
          labels:
            app: meu-app
        spec:
          containers:
          - name: nginx
            image: nginx:latest
    

    ✓ Resposta: O ReplicaSet manterá 3 réplicas. Se você deletar um Pod, o ReplicaSet criará um novo Pod automaticamente para manter o número desejado de 3.
  3. Explique a diferença entre ReplicaSet e Deployment. Quando você usaria um em vez do outro?

    ✓ Resposta: Um ReplicaSet garante que um número específico de réplicas de um Pod esteja em execução, mas não oferece suporte a atualizações contínuas ou rollbacks. Um Deployment gerencia ReplicaSets e fornece atualizações declarativas, escalabilidade e rollback. Você deve usar Deployment para a maioria das aplicações, pois é mais alto nível e fornece mais funcionalidades. ReplicaSet é usado internamente pelo Deployment, mas pode ser usado diretamente em cenários específicos onde você precisa de controle fino sobre o número de réplicas sem atualizações.
  4. Descreva as fases do ciclo de vida de um Pod e o que cada uma significa.

    ✓ Resposta: As fases são: Pending (Pod aceito, mas ainda não em execução), Running (Pod alocado a um nó e contêineres em execução), Succeeded (todos os contêineres terminaram com sucesso), Failed (pelo menos um contêiner terminou com falha) e Unknown (estado não pode ser obtido).
  5. Como você faria uma atualização contínua em um Deployment? Mostre o comando ou a edição de manifesto.

    ✓ Resposta: Para fazer uma atualização contínua, você pode editar o manifesto do Deployment alterando a imagem da aplicação e aplicar novamente com kubectl apply -f deployment.yaml. Alternativamente, use o comando kubectl set image deployment/meu-deployment nginx=nginx:1.21 --record. O Kubernetes fará um rolling update, criando um novo ReplicaSet e gradualmente substituindo os Pods antigos.