Monitoramento de Performance no Kubernetes: Ferramentas e Técnicas para Otimização

Monitore a performance do Kubernetes com métricas úteis, Prometheus, Grafana, kubectl e hábitos práticos de ajuste de recursos.

Monitoramento de Performance no Kubernetes: Ferramentas e Técnicas para Otimização

O monitoramento de performance no Kubernetes não se resume a observar gráficos de CPU. Um cluster pode apresentar baixa média de CPU enquanto os usuários enfrentam requisições lentas. Um pod pode ter memória suficiente na maior parte do dia e ainda ser morto durante um job em lote. Um nó pode parecer saudável até que a pressão no disco comece a expulsar pods. Um bom monitoramento conecta os sinais do cluster à experiência que realmente importa: o serviço é rápido, disponível e previsível?

O primeiro erro é começar pelas ferramentas em vez das perguntas. Prometheus, Grafana, metrics-server, kube-state-metrics e plataformas de monitoramento em nuvem são úteis, mas não decidem o que é importante. Você decide isso ao entender a carga de trabalho. Uma API pública se preocupa com latência e erros. Um worker de fila se preocupa com backlog e taxa de processamento. Um job noturno se preocupa com tempo de conclusão e pods com falha. Uma carga de trabalho similar a banco de dados se preocupa com latência de disco e pressão de memória.

Para uma visão rápida, kubectl top ainda é útil:

kubectl top nodes
kubectl top pods -A
kubectl top pod -n production api-7d9c8f7b9d-2x4mq --containers

Esses comandos dependem do metrics-server. Eles fornecem uso recente de CPU e memória, não um histórico completo. Use-os durante a triagem, não como seu único sistema de monitoramento. Se um pod foi reiniciado há dez minutos por falta de memória, kubectl top pode não mostrar o pico que causou isso.

Prometheus é a base comum para métricas do Kubernetes porque coleta dados de séries temporais e funciona bem com a descoberta de serviços do Kubernetes. Em uma configuração típica, as métricas vêm de vários lugares. O kubelet expõe métricas de recursos de contêineres e pods. O cAdvisor, integrado ao kubelet, contribui com dados de CPU, memória, sistema de arquivos e rede dos contêineres. O node-exporter relata métricas de nível de host. O kube-state-metrics transforma o estado de objetos do Kubernetes em métricas: réplicas desejadas, réplicas disponíveis, fases dos pods, condições dos nós e muito mais.

O Grafana então transforma essas métricas em dashboards. Um bom dashboard não é uma parede de medidores. Ele deve responder a perguntas específicas rapidamente: qual serviço está lento, quais pods estão sendo limitados, quais nós estão sob pressão, qual Deployment está falhando ao ser implantado e se o autoscaling está acompanhando.

Comece pela camada de aplicação. Para serviços voltados ao usuário, os sinais mais importantes são taxa de requisição, taxa de erro e latência. Se você tem SLOs, coloque-os em gráficos. Um gráfico de CPU do pod não informa se o checkout está falhando. Métricas de aplicação informam. Instrumente serviços com bibliotecas de cliente do Prometheus, OpenTelemetry ou o sistema de monitoramento que sua plataforma já usa. As métricas do Kubernetes explicam por que o serviço está não saudável; as métricas da aplicação informam que ele está não saudável.

Em seguida, conecte os sintomas da aplicação aos recursos do pod. O uso de CPU é fácil de interpretar erroneamente no Kubernetes. Um contêiner com limite de CPU pode ser limitado mesmo quando a média de CPU não parece dramática. A limitação ocorre quando o contêiner tenta usar mais tempo de CPU do que seu limite permite dentro do período de agendamento. Para aplicações sensíveis à latência, isso pode causar requisições lentas que parecem aleatórias.

Uma consulta PromQL útil para limitação é:

rate(container_cpu_cfs_throttled_periods_total{namespace="production", container!=""}[5m])

Um valor crescente significa que o contêiner está sendo limitado. Combine-o com uso de CPU e latência de requisição. Se os picos de latência coincidirem com a limitação, considere aumentar ou remover o limite de CPU, aumentar as réplicas ou otimizar o caminho do código. Algumas equipes definem requests de CPU, mas evitam limites de CPU para serviços sensíveis à latência, confiando em requests, autoscaling e controles de capacidade do nó. Isso pode ser razoável, mas requer disciplina em nível de cluster para que cargas de trabalho barulhentas não prejudiquem outras.

A memória se comporta de forma diferente. A CPU pode ser limitada; a memória não pode ser desacelerada da mesma forma. Se um contêiner exceder seu limite de memória, ele pode ser morto por OOM. Procure por razões de reinicialização:

kubectl describe pod -n production api-7d9c8f7b9d-2x4mq
kubectl get pod -n production api-7d9c8f7b9d-2x4mq -o jsonpath='{.status.containerStatuses[*].lastState}'

No Prometheus, monitore a memória do conjunto de trabalho e compare com os limites:

container_memory_working_set_bytes{namespace="production", container!=""}

Não ajuste a memória com base em uma única hora tranquila. Observe picos de tráfego, janelas de batch, implantações e comportamento de coleta de lixo. Serviços Java, Go, Node.js e Python têm perfis de memória diferentes. Um limite que parece generoso durante o tráfego normal pode ser muito apertado durante a inicialização, aquecimento de cache ou uma requisição grande.

Os requests de recursos são importantes porque o scheduler os usa para posicionar os pods. Se os requests forem muito baixos, o Kubernetes pode empacotar muitos pods ocupados no mesmo nó. Tudo parece eficiente até que esses pods fiquem ocupados ao mesmo tempo. Se os requests forem muito altos, o cluster desperdiça capacidade e o autoscaling pode adicionar nós mais cedo do que o necessário. O melhor request geralmente é baseado no uso observado mais uma margem de segurança, não um valor copiado de outro serviço.

O Vertical Pod Autoscaler pode ajudar recomendando requests com base no uso histórico. Muitas equipes executam o VPA primeiro no modo de recomendação, pois atualizações automáticas podem reiniciar pods dependendo da configuração e do tipo de carga de trabalho. Trate as recomendações como entrada, não como lei. Um serviço com picos raros, mas importantes, pode precisar de mais margem do que seu histórico médio sugere.

O Horizontal Pod Autoscaler é útil quando mais réplicas realmente melhoram a taxa de transferência. Funciona bem para serviços web sem estado e workers que podem compartilhar carga. Não resolve um gargalo de thread única, um bloqueio de banco de dados ou uma dependência downstream já saturada.

Um HPA básico pode ser assim:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Monitore o comportamento do HPA, não apenas a contagem de réplicas. Se ele escala para cima e para baixo constantemente, ajuste as janelas de estabilização, os alvos ou a métrica. Se atingir maxReplicas e a latência ainda estiver ruim, o problema pode ser capacidade, código ou uma dependência. Se nunca escalar enquanto os pods estão claramente sobrecarregados, verifique a disponibilidade das métricas e se os requests estão definidos. Os alvos de utilização de CPU dependem dos requests de CPU; requests ausentes ou irreais podem tornar o autoscaling enganoso.

A saúde do nó é a próxima camada. Um problema de pod que aparece em muitos serviços em um nó geralmente é um problema de nó. Monitore saturação de CPU, média de carga, memória disponível, pressão de disco, uso de inodes, latência do sistema de arquivos, erros de rede e saúde do kubelet. Condições do nó como MemoryPressure, DiskPressure e PIDPressure devem estar visíveis em dashboards e alertas.

Use kubectl describe node quando um nó parecer suspeito:

kubectl describe node worker-12

Observe as condições, recursos alocados, eventos e pods agendados no nó. Um nó pode estar sobrecarregado por limites, requests ou uso real. A seção de recursos alocados ajuda a ver se as suposições de agendamento correspondem à realidade.

O monitoramento do plano de controle é importante mesmo que seus pods de aplicação pareçam bem. A latência do servidor da API pode atrasar implantações, autoscaling e controladores. A latência ou problemas de disco do etcd podem tornar todo o cluster lento. Problemas no controller manager e scheduler podem atrasar o posicionamento de pods ou a reconciliação. Em Kubernetes gerenciado, você pode não ver todos os componentes do plano de controle, mas os provedores de nuvem geralmente expõem algumas métricas de saúde e latência da API.

Eventos são úteis durante incidentes, mas não são um armazenamento de métricas de longo prazo. Ainda assim, eles geralmente explicam o que acabou de acontecer:

kubectl get events -A --sort-by=.lastTimestamp

Procure por falhas de agendamento, erros de pull de imagem, falhas de probe, expulsões e mensagens de back-off. Os eventos podem ser ruidosos, então filtre por namespace ou objeto envolvido quando necessário.

As probes merecem monitoramento cuidadoso. Liveness probes muito agressivas podem reiniciar um aplicativo lento, mas em recuperação, e piorar um incidente. Readiness probes que falham corretamente podem proteger os usuários removendo pods ruins do serviço. Acompanhe as falhas de probe e correlacione-as com limitação de CPU, pausas de GC, timeouts downstream e implantações.

Para cargas de trabalho com uso intensivo de armazenamento, CPU e memória do contêiner não são suficientes. Monitore latência de volume persistente, taxa de transferência de disco, profundidade de fila e capacidade do sistema de arquivos. Um pod esperando por armazenamento lento pode mostrar baixa CPU porque está bloqueado. Se um banco de dados ou fila é executado no Kubernetes, as métricas de armazenamento fazem parte da performance da aplicação, não são triviais de infraestrutura.

Um caminho prático de solução de problemas começa amplo e se estreita. Primeiro, confirme o sintoma voltado ao usuário: latência, erros, jobs com falha ou backlog. Segundo, identifique o escopo: um pod, um Deployment, um nó, um namespace ou todo o cluster. Terceiro, verifique mudanças recentes: implantações, atualizações de configuração, atividade do autoscaler, rotações de nós ou picos de tráfego. Quarto, inspecione o comportamento de recursos do pod: limitação de CPU, pressão de memória, reinicializações e falhas de probe. Quinto, inspecione a saúde do nó e das dependências.

A emissão de alertas deve evitar acordar pessoas para ruídos inofensivos. Alerte primeiro pelo impacto no usuário: alta taxa de erro, alta latência, prazo de job perdido, idade crescente da fila. Em seguida, alerte por indicadores antecedentes fortes: OOMKills frequentes, limitação sustentada de CPU em serviços sensíveis à latência, pods indisponíveis abaixo das réplicas desejadas, pressão no nó, pods pendentes persistentes e HPA preso no máximo de réplicas enquanto as métricas do serviço estão ruins.

O objetivo não é utilização perfeita. Um cluster rodando a 95% de uso de recursos o dia todo pode parecer eficiente até que um nó falhe e não haja espaço para reagendar pods. Deixe capacidade para implantações, retentativas, picos de tráfego e falhas. A otimização deve reduzir o desperdício sem remover o buffer que mantém os incidentes pequenos.

Um bom monitoramento de performance no Kubernetes parece prático. Você pode abrir um dashboard e ver a saúde do serviço, saúde dos pods, saúde dos nós e comportamento de escalabilidade sem caçar por vinte abas. Você pode responder se uma lentidão é código, limites de recursos, pressão no nó, armazenamento, rede ou plano de controle. E quando você altera requests, limites ou autoscaling, pode ver se a mudança ajudou em vez de adivinhar.

Visualizações em nível de namespace são úteis quando muitas equipes compartilham um cluster. Uma única equipe pode não ver a saturação em nível de nó se apenas monitorar seus próprios Deployments. As equipes de plataforma devem expor dashboards que mostrem requests de CPU e memória por namespace, uso real, contagens de pods, reinicializações e limitação. Isso torna as conversas sobre capacidade menos emocionais. Em vez de dizer que uma equipe está usando "demais", você pode mostrar tendências de request, pico de uso e desperdício.

A otimização de custos deve vir depois dos sinais de confiabilidade, não antes. Se um serviço nunca teve requests ajustados, você pode encontrar economias fáceis. Mas cortar requests agressivamente pode criar pressão de agendamento e problemas de vizinho barulhento. Um bom processo altera uma classe de carga de trabalho por vez, observa latência e reinicializações e deixa notas de reversão. Trate o ajuste de recursos como código de produção: mudanças pequenas, resultados medidos.

As próprias implantações podem criar incidentes de performance. Uma implantação que substitui muitos pods de uma vez pode sobrecarregar caches frios, pools de conexão ou serviços downstream. Monitore a duração da implantação, réplicas indisponíveis e latência da aplicação durante as implantações. Ajuste maxSurge e maxUnavailable com base em como o serviço se comporta durante a inicialização. Um serviço com aquecimento lento pode precisar de uma implantação conservadora mesmo que a performance em estado estável seja boa.

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

Essa configuração não é universalmente melhor, mas mostra o trade-off: implantação mais lenta, mais proteção contra quedas de capacidade. Para um serviço sem estado que inicia instantaneamente, você pode escolher uma implantação mais rápida. Para um serviço JVM aquecendo caches e abrindo muitas conexões downstream, mais lento pode ser mais seguro.

Fique de olho na cardinalidade das métricas. Os rótulos do Kubernetes são tentadores, mas rótulos de alta cardinalidade, como UID do pod, ID de requisição ou ID de usuário, podem tornar o Prometheus caro e lento. Use rótulos que ajudem a agregar: namespace, workload, pod, contêiner, nó, código de status, padrão de rota. Evite rótulos que criem uma nova série temporal para cada usuário ou cada requisição. O monitoramento não deve se tornar a coisa que prejudica a performance do cluster.

Logs e traces completam o quadro. Métricas informam que a latência aumentou; traces podem mostrar qual chamada downstream ficou lenta; logs podem mostrar o erro ou timeout exato. O OpenTelemetry é comumente usado para conectar esses sinais, mas a ferramenta importa menos que a correlação. Use nomes de serviço, namespaces, versões e IDs de trace consistentes para que você possa ir de um alerta aos logs relevantes sem adivinhar.

Para sistemas de batch e worker, monitore a idade do backlog em vez de apenas a CPU do pod. Um worker de fila pode estar saudável no nível do pod enquanto fica para trás porque o trabalho recebido excede a capacidade de processamento. Métricas como idade da mensagem mais antiga, jobs concluídos por minuto, retentativas e contagens de dead letter geralmente importam mais que a utilização do contêiner. O HPA pode escalar com base em métricas personalizadas ou externas quando a CPU é o sinal errado.

Revise os dashboards após incidentes. Se os respondedores tiveram que executar cinco comandos manuais para responder à mesma pergunta, essa pergunta pertence a um dashboard ou a um runbook. O monitoramento melhora com o uso. O objetivo não é prever todas as falhas; é tornar a próxima investigação mais curta e menos dependente da memória de uma pessoa.