DevOps Admin

Kubernetes Fundamentos: Arquitetura, Componentes e kubectl na Prática: Do Básico ao Avançado Já leu

Entendendo Kubernetes: O Orquestrador de Contêineres Kubernetes é uma plataforma de código aberto que automatiza a implantação, o dimensionamento e a gestão de aplicações em contêineres. Seu nome vem do grego antigo, significando "timoneiro" ou "capitão do navio" — uma metáfora perfeita para seu papel em guiar contêineres por um ambiente distribuído. Diferentemente de simplesmente rodar Docker localmente, Kubernetes resolve problemas reais que enfrentamos em produção: como garantir alta disponibilidade, distribuir carga entre múltiplos servidores, fazer rollback automático de falhas e escalar aplicações conforme a demanda. A adoção de Kubernetes é hoje um padrão na indústria porque resolve o problema central da computação moderna: gerenciar centenas ou milhares de contêineres espalhados em múltiplas máquinas simultaneamente. Sem Kubernetes, você precisaria de scripts manuais, monitoramento constante e decisões humanas para colocar contêineres online, detectar falhas e realocá-los. Kubernetes faz isso automaticamente, declarativamente — você descreve o estado desejado e a plataforma trabalha para mantê-lo. Arquitetura do Kubernetes: A Visão Geral A Estrutura

Entendendo Kubernetes: O Orquestrador de Contêineres

Kubernetes é uma plataforma de código aberto que automatiza a implantação, o dimensionamento e a gestão de aplicações em contêineres. Seu nome vem do grego antigo, significando "timoneiro" ou "capitão do navio" — uma metáfora perfeita para seu papel em guiar contêineres por um ambiente distribuído. Diferentemente de simplesmente rodar Docker localmente, Kubernetes resolve problemas reais que enfrentamos em produção: como garantir alta disponibilidade, distribuir carga entre múltiplos servidores, fazer rollback automático de falhas e escalar aplicações conforme a demanda.

A adoção de Kubernetes é hoje um padrão na indústria porque resolve o problema central da computação moderna: gerenciar centenas ou milhares de contêineres espalhados em múltiplas máquinas simultaneamente. Sem Kubernetes, você precisaria de scripts manuais, monitoramento constante e decisões humanas para colocar contêineres online, detectar falhas e realocá-los. Kubernetes faz isso automaticamente, declarativamente — você descreve o estado desejado e a plataforma trabalha para mantê-lo.

Arquitetura do Kubernetes: A Visão Geral

A Estrutura Mestre-Nó (Control Plane e Workers)

Kubernetes funciona com uma arquitetura de cluster baseada em dois tipos principais de máquinas: o Control Plane (antigo "master") e os Nós Worker. O Control Plane é o "cérebro" do cluster — ele toma todas as decisões sobre o que rodar, onde rodar e como gerenciar a aplicação. Os Nós Worker são as "mãos" — máquinas que realmente executam os contêineres, sob as ordens do Control Plane.

Essa separação é fundamental para escalabilidade. Um Control Plane pode gerenciar dezenas ou centenas de Nós Worker. Enquanto o Control Plane é responsável por manter o estado desejado através de loops de reconciliação, os Worker Nodes são stateless — podem ser adicionados ou removidos sem prejudicar o cluster, desde que haja replicação adequada das aplicações.

Componentes do Control Plane

O Control Plane é composto por quatro componentes principais que trabalham em harmonia:

kube-apiserver é o ponto de entrada para toda comunicação com o cluster. Ele expõe a API REST do Kubernetes através da qual você submete manifestos YAML, consulta o status de recursos e modifica configurações. Toda requisição passa por aqui, incluindo autenticação e autorização. É stateless, o que significa você pode ter múltiplas instâncias rodando atrás de um load balancer para alta disponibilidade.

etcd é um banco de dados de chave-valor distribuído que armazena o estado completo do cluster. Cada objeto criado em Kubernetes (Pods, Services, ConfigMaps, etc.) é persistido aqui. É crítico fazer backup regularmente do etcd, pois perder seu conteúdo significa perder toda a configuração do cluster. Kubernetes usa etcd como fonte única de verdade.

kube-scheduler é responsável por decidir em qual Nó Worker um novo Pod será executado. Ele analisa os requisitos do Pod (limites de CPU, memória, afinidade de nó), o estado dos Nós disponíveis e aplica algoritmos de agendamento para fazer a melhor escolha. Se nenhum Nó atender os requisitos, o Pod fica em estado "Pending" até que um Nó adequado fique disponível.

kube-controller-manager executa múltiplos controladores que funcionam em loops contínuos verificando o estado desejado versus o estado atual. O ReplicationController, por exemplo, garante que o número correto de replicas de um Pod esteja rodando. Se um Pod falhar, o controlador cria um novo. Se houver Pods extras, ele remove os excedentes.

Componentes dos Nós Worker

Cada Nó Worker executa dois componentes essenciais para se comunicar com o Control Plane e rodar containers:

kubelet é um agente que roda em cada Nó Worker. Ele recebe instruções do Control Plane via API e garante que os contêineres especificados estejam realmente rodando no Nó. O kubelet monitora continuamente a saúde dos Pods — se um contêiner falhar, ele tenta reiniciá-lo conforme a política de restart definida.

kube-proxy mantém regras de rede no Nó para que os Pods possam se comunicar entre si e com o mundo externo. Ele implementa o modelo de serviço do Kubernetes, garantindo que requisições chegem ao Pod correto através de abstrações como Services.

Componentes Fundamentais: Recursos do Kubernetes

Pod: A Unidade Fundamental

Um Pod é a menor unidade que você cria em Kubernetes. Não é um contêiner — é um wrapper ao redor de um ou mais contêineres que compartilham espaço de rede. Na maioria dos casos, um Pod contém apenas um contêiner, mas a possibilidade de múltiplos contêineres no mesmo Pod existe para casos especiais onde você precisa de "sidecars" — contêineres auxiliares que enriquecem a aplicação principal.

O que torna um Pod especial é que seus contêineres compartilham um namespace de rede. Isso significa que eles têm o mesmo IP, podem se comunicar via localhost e compartilham volumes. Se você tem dois contêineres em um Pod e um deles faz um bind na porta 8080, o outro não pode fazer o mesmo. Pods são efêmeros — quando terminam, não há persistência automática. Para aplicações stateless, isso é perfeito. Para dados que precisam sobreviver, você usa Volumes.

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: web
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Este manifesto cria um Pod simples rodando Nginx. O campo resources é crítico — requests diz ao scheduler quanto de CPU e memória o Pod necessita, enquanto limits previne que um Pod consumir mais que o permitido e seja evicted (removido) de um Nó quando há pressão de recursos.

Deployment: Gerenciando Múltiplas Replicas

Um Deployment é o tipo de objeto que você usa na maioria dos casos em produção. Ele gerencia uma ou mais replicas de Pods e fornece atualização declarativa. Em vez de criar Pods manualmente, você define um Deployment, especifica quantas replicas deseja e o Deployment garante que esse número esteja sempre rodando.

Deployments oferecem estratégias de atualização poderosas. A estratégia padrão é RollingUpdate, que gradualmente substitui Pods antigos por novos, garantindo que a aplicação nunca fique completamente offline durante uma atualização. Você também pode usar Recreate, que mata todos os Pods de uma vez e depois cria novos — mais rápido mas com downtime.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

Este Deployment cria 3 replicas de Nginx. O campo selector.matchLabels conecta o Deployment aos Pods — ele gerencia qualquer Pod com label app: web. As probes (livenessProbe e readinessProbe) são críticas: livenessProbe reinicia o Pod se ele não responder, readinessProbe remove o Pod do tráfego se não estiver pronto. Com maxSurge: 1 e maxUnavailable: 0, durante uma atualização, Kubernetes cria um novo Pod antes de matar um antigo, mantendo sempre 3 ou mais Pods disponíveis.

Service: Expondo Pods para Comunicação

Um Service é uma abstração que fornece um ponto de acesso estável para um conjunto de Pods. Pods têm IPs dinâmicos — quando um Pod é recriado, seu IP muda. Um Service fornece um nome DNS fixo (por exemplo, nginx-service.default.svc.cluster.local) e um IP virtual que permanece constante, roteando requisições para os Pods corretos automaticamente.

Existem três tipos principais de Service: ClusterIP (padrão, acesso apenas dentro do cluster), NodePort (expõe em uma porta em cada Nó do cluster) e LoadBalancer (provisiona um load balancer externo, funciona bem em clouds públicas).

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Este Service roteia tráfego na porta 80 para qualquer Pod com label app: web. Dentro do cluster, qualquer Pod pode acessar este serviço fazendo requisições para nginx-service.default.svc.cluster.local:80. O Service atua como um load balancer interno, distribuindo requisições entre todas as replicas disponíveis.

ConfigMap e Secret: Gerenciando Configuração

ConfigMap armazena dados de configuração em pares chave-valor, enquanto Secret faz o mesmo mas com dados sensíveis (senhas, tokens, chaves). A diferença principal é que Secrets são codificados em base64 no etcd (não realmente criptografados por padrão — use encryption at rest em produção) e há convenção de que dados sensíveis vão lá.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  DATABASE_HOST: "postgres.default.svc.cluster.local"
  DATABASE_PORT: "5432"
---
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
type: Opaque
data:
  DATABASE_PASSWORD: cG9zdGdyZXM=  # base64 encoded "postgres"
  API_KEY: c2VjcmV0LWtleQ==  # base64 encoded "secret-key"

Você injeta esses valores em Pods de duas formas: como variáveis de ambiente ou como arquivos montados em volumes. A segunda forma é preferida para dados grandes ou que mudam com frequência, pois permite recarregamento sem reiniciar o Pod.

kubectl: Interagindo com o Cluster na Prática

Configuração e Contextos

kubectl é a ferramenta de linha de comando para interagir com Kubernetes. Ela se comunica com o kube-apiserver do cluster. Sua configuração é armazenada em ~/.kube/config, um arquivo YAML que define clusters, usuários e contextos.

# Listar todos os contextos disponíveis
kubectl config get-contexts

# Mudar para um contexto específico
kubectl config use-context meu-cluster

# Ver o contexto atual
kubectl config current-context

# Visualizar a configuração completa
kubectl config view

Um contexto conecta um cluster a um usuário (credenciais). Se você trabalha com múltiplos clusters (desenvolvimento, staging, produção), você provavelmente tem múltiplos contextos. Uma prática segura é sempre verificar o contexto atual antes de executar comandos destrutivos em produção.

Operações Básicas com kubectl

As operações fundamentais em Kubernetes seguem um padrão: kubectl [verbo] [tipo-de-recurso] [nome] [flags]. Você pode criar recursos a partir de arquivos YAML, deletá-los, verificar seu status e modificá-los.

# Criar recursos a partir de um arquivo YAML
kubectl apply -f deployment.yaml

# Listar todos os Pods no namespace padrão
kubectl get pods

# Listar todos os Pods em todos os namespaces
kubectl get pods --all-namespaces

# Obter informações detalhadas de um Pod específico
kubectl describe pod nginx-pod

# Ver os logs de um Pod
kubectl logs nginx-pod

# Ver logs em tempo real (follow)
kubectl logs -f nginx-pod

# Deletar um Pod
kubectl delete pod nginx-pod

# Deletar todos os recursos definidos em um arquivo
kubectl delete -f deployment.yaml

# Verificar eventos do cluster
kubectl get events

Quando você aplica um manifesto com kubectl apply, Kubernetes verifica se o recurso já existe. Se existir, ele atualiza. Se não existir, cria. Isso torna fácil fazer alterações incrementais — você só edita o arquivo YAML e aplica novamente.

Debugging e Troubleshooting

Quando algo dá errado, você precisa de habilidades de debugging. Os comandos abaixo são seus aliados:

# Descrever um recurso para ver seu status e eventos
kubectl describe pod meu-pod

# Executar um comando dentro de um Pod (como docker exec)
kubectl exec -it meu-pod -- /bin/bash

# Fazer port-forward de um Pod para sua máquina local
kubectl port-forward pod/meu-pod 8080:8080

# Ver eventos do cluster em tempo real
kubectl get events --sort-by='.lastTimestamp' --watch

# Verificar a saúde dos nós
kubectl get nodes
kubectl describe node meu-node

# Ver métricas de uso (requer metrics-server instalado)
kubectl top nodes
kubectl top pods

kubectl exec é particularmente poderoso para debugging — você acessa o shell do contêiner para verificar configurações, testar conectividade ou investigar por que a aplicação não está funcionando. port-forward é útil para acessar uma aplicação rodando em um Pod sem expô-la publicamente.

Atualizações Declarativas e Rollback

Uma das vantagens de Kubernetes é que você descreve o estado desejado e aplica, não importa qual era o estado anterior. Isso significa que atualizações são naturalmente replicáveis.

# Atualizar a imagem de um Deployment (imperativo, não recomendado em produção)
kubectl set image deployment/nginx-deployment nginx=nginx:1.16.0

# Melhor: editar o manifesto e aplicar novamente
kubectl apply -f deployment.yaml

# Ver o histórico de rollouts
kubectl rollout history deployment/nginx-deployment

# Reverter para a revisão anterior
kubectl rollout undo deployment/nginx-deployment

# Reverter para uma revisão específica
kubectl rollout undo deployment/nginx-deployment --to-revision=2

# Ver o status de um rollout em andamento
kubectl rollout status deployment/nginx-deployment

Em produção, você sempre deve usar kubectl apply -f com manifestos no controle de versão (Git), nunca usar kubectl set image. Isso garante que o histórico completo de mudanças está registrado, e qualquer desenvolvedor pode ver exatamente o que foi alterado e quando.

Exemplo Prático Completo: Deploy de uma Aplicação Real

Para consolidar o aprendizado, vamos deployar uma aplicação real — uma API Node.js simples com um banco de dados PostgreSQL. Este exemplo cobre Deployment, Service, ConfigMap, Secret e Volume.

# namespace.yaml - Criar um namespace separado para a aplicação
apiVersion: v1
kind: Namespace
metadata:
  name: producao
---
# configmap.yaml - Configurações gerais
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
  namespace: producao
data:
  NODE_ENV: "production"
  LOG_LEVEL: "info"
  DATABASE_HOST: "postgres.producao.svc.cluster.local"
  DATABASE_PORT: "5432"
  DATABASE_NAME: "app_db"
---
# secret.yaml - Dados sensíveis
apiVersion: v1
kind: Secret
metadata:
  name: api-secret
  namespace: producao
type: Opaque
stringData:
  DATABASE_USER: "postgres"
  DATABASE_PASSWORD: "securepassword123"
  JWT_SECRET: "sua-chave-secreta-super-segura"
---
# postgres-pvc.yaml - Volume persistente para o banco
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: producao
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
# postgres-deployment.yaml - Deploy do PostgreSQL
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: producao
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:13
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: api-secret
              key: DATABASE_USER
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: api-secret
              key: DATABASE_PASSWORD
        - name: POSTGRES_DB
          valueFrom:
            configMapKeyRef:
              name: api-config
              key: DATABASE_NAME
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
          subPath: postgres
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
      volumes:
      - name: postgres-storage
        persistentVolumeClaim:
          claimName: postgres-pvc
---
# postgres-service.yaml - Service para o PostgreSQL (ClusterIP)
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: producao
spec:
  type: ClusterIP
  selector:
    app: postgres
  ports:
  - protocol: TCP
    port: 5432
    targetPort: 5432
---
# api-deployment.yaml - Deploy da aplicação Node.js
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: producao
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: minha-api:1.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 3000
        envFrom:
        - configMapRef:
            name: api-config
        - secretRef:
            name: api-secret
        livenessProbe:
          httpGet:
            path: /health
            port: http
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /ready
            port: http
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 2
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - api
              topologyKey: kubernetes.io/hostname
---
# api-service.yaml - Service para expor a API (NodePort ou LoadBalancer)
apiVersion: v1
kind: Service
metadata:
  name: api
  namespace: producao
spec:
  type: LoadBalancer
  selector:
    app: api
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: http

Para aplicar este exemplo em um cluster real:

# Criar todos os recursos de uma vez
kubectl apply -f namespace.yaml
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
kubectl apply -f postgres-pvc.yaml
kubectl apply -f postgres-deployment.yaml
kubectl apply -f postgres-service.yaml
kubectl apply -f api-deployment.yaml
kubectl apply -f api-service.yaml

# Ou aplicar um diretório inteiro (se todos os arquivos estão em uma pasta)
kubectl apply -f ./producao/

# Verificar o status
kubectl get pods -n producao
kubectl get services -n producao
kubectl describe deployment api -n producao

# Ver logs de um Pod específico
kubectl logs -f deployment/api -n producao

# Acessar o shell de um Pod para debug
kubectl exec -it -n producao $(kubectl get pods -n producao -l app=api -o jsonpath='{.items[0].metadata.name}') -- /bin/bash

Este exemplo real cobre cenários práticos: uso de ConfigMap para configuração, Secret para dados sensíveis, PersistentVolumeClaim para persistência, probes para health checks, anti-afinidade para distribuir Pods entre nós, e limites de recursos para evitar que um Pod domine a máquina.

Conclusão

Você agora domina os três pilares fundamentais de Kubernetes: Arquitetura, compreendendo como o Control Plane orquestra Nós Worker através de componentes como scheduler, controller-manager e API server; Componentes, sabendo que Pods são efêmeros, Deployments garantem replicação confiável, Services fornecem pontos de acesso estáveis, e ConfigMaps/Secrets gerenciam configuração; e kubectl, podendo criar, atualizar, debugar e gerenciar recursos através de manifestos YAML aplicados declarativamente.

A lição mais importante não é memorizar cada flag ou componente — é entender que Kubernetes oferece primitivas simples que você compõe para resolver problemas complexos de orquestração. Cada recurso (Pod, Deployment, Service) resolve um problema específico, e você aprende combinando-os. Comece pequeno, deployando aplicações simples em um cluster local (minikube, kind ou Docker Desktop), e gradualmente explore cenários mais avançados como Ingress, StatefulSets, Jobs e Custom Resources conforme sua confiança cresce.

Referências


Artigos relacionados