As Camadas de Observabilidade no Kubernetes
Kubernetes adiciona múltiplas camadas de infraestrutura entre a aplicação e o hardware. Cada camada tem seu próprio conjunto de métricas, logs e eventos. Para debugar um problema em produção, você precisa ter visibilidade em todas elas simultaneamente.
| Camada | O que monitorar | Ferramenta principal |
|---|---|---|
| Cluster / Control Plane | API server latência, etcd, scheduler queue | kube-apiserver metrics |
| Nó (Node) | CPU, memória, disco, rede do nó físico/VM | node-exporter |
| Kubernetes objects | Pod status, deployment replicas, HPA, PVC | kube-state-metrics |
| Container runtime | CPU/memória por container, restarts | cAdvisor (via Kubelet) |
| Aplicação | Logs, métricas, traces de negócio | OTel SDK + Fluent Bit |
A complexidade central é que uma latência alta em um endpoint pode ter origem em qualquer camada: a aplicação pode estar com GC pausas longas (camada app), o container pode estar sendo throttled por CPU limit (cAdvisor), o nó pode estar com CPU steal alto por vizinhos barulhentos (node-exporter), ou o Kubernetes scheduler pode estar remanejando pods (KSM + events). Correlacionar todas as camadas ao mesmo timestamp é o que diferencia debugging eficaz de tentativa e erro.
node-exporter — Métricas do Nó
O node-exporter (Prometheus) é um DaemonSet que coleta métricas do sistema operacional de cada nó: CPU (por modo: user, system, iowait, steal), memória (disponível, buffers, cache), disco (read/write ops, latência, saturation), rede (bytes in/out, erros, drops), e métricas de filesystem.
Métricas Críticas do node-exporter
# CPU por modo — "steal" alto indica problema na VM compartilhada
100 - (avg by (instance) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100)
# Memória disponível real (free + buffers + cache)
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes
# I/O wait de disco — indica saturação de disco
rate(node_cpu_seconds_total{mode="iowait"}[5m])
# Disk saturation — tempo médio de uma operação de I/O (latência)
rate(node_disk_io_time_seconds_total[5m]) # 1.0 = 100% saturado
# Espaço em disco restante por mountpoint
(node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100
# Conexões de rede — detectar SYN flood ou CLOSE_WAIT acumulando
node_netstat_Tcp_CurrEstab # conexões TCP estabelecidas
node_netstat_Tcp_InSegs # segmentos TCP recebidos (rate)
CPU Steal — O Risco das VMs Compartilhadas
Em ambientes de cloud com VMs shared (t3.medium da AWS, n1-standard da GCP), o CPU steal indica que a VM precisava de CPU mas o hypervisor estava servindo outra VM. Alta steal (> 5-10%) causa latência imprevisível — a aplicação parece saudável nas métricas próprias, mas latência P99 sobe sem causa aparente. Solução: migrar para instâncias dedicadas ou instâncias com CPU credits garantidos.
Container CPU Throttling
Uma métrica complementar ao node-exporter é o throttling de containers via cAdvisor: container_cpu_cfs_throttled_seconds_total. Um container pode ser throttled mesmo quando o nó tem CPU disponível — isso acontece quando o container excede seu cpu limit no intervalo CFS (100ms por padrão). Alta taxa de throttling com P99 de latência elevada, mas CPU do nó saudável, aponta para cpu limits muito restritivos no deployment. A solução é aumentar o limite ou remover o cpu limit (deixando apenas requests) para workloads com picos de CPU previsíveis.
kube-state-metrics — Estado dos Objetos Kubernetes
kube-state-metrics (KSM) expõe métricas sobre o estado dos objetos Kubernetes: quantos pods estão Running vs Pending vs Failed, quantas réplicas um Deployment tem vs o desejado, status de PersistentVolumeClaims, condições de Nodes, etc. É a principal ferramenta para saber "o cluster está saudável?".
Métricas KSM Essenciais
# Pods que não estão Running — alerta se qualquer pod não-running por > 5min
count by (namespace, pod, reason) (
kube_pod_container_status_waiting_reason > 0
)
# Reasons comuns: CrashLoopBackOff, ImagePullBackoff, OOMKilled, Pending
# Deployment: réplicas disponíveis vs desejadas
kube_deployment_status_replicas_available
/ kube_deployment_status_replicas
# ratio < 1 indica degradação — alertar quando < 0.5 por > 5min
# HPA: está o autoscaler no limite?
kube_horizontalpodautoscaler_status_current_replicas
== kube_horizontalpodautoscaler_spec_max_replicas
# se verdadeiro, HPA atingiu o limite — pode haver problema de capacidade
# PVC não bound — volumes não disponíveis
kube_persistentvolumeclaim_status_phase{phase!="Bound"}
# Node conditions — nó com problema (DiskPressure, MemoryPressure)
kube_node_status_condition{condition!="Ready", status="true"}
# Job falhos — CronJobs com falha
kube_job_failed > 0
# Container OOMKilled — container encerrado por falta de memória
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}
# Detecção antecipada de OOMKill iminente (cAdvisor)
container_memory_working_set_bytes
/ container_spec_memory_limit_bytes > 0.80
# Alerta quando working set > 80% do limit por > 10min
container_memory_working_set_bytes. Use os dois em conjunto — KSM alerta o sintoma, cAdvisor ajuda a diagnosticar a causa.
Kubernetes Events
Kubernetes Events são o log de atividade do cluster — registram eventos como: pod scheduled, pod started, container killed (OOM), image pull failed, liveness probe failed, HPA scale event. Por padrão, Events são mantidos por 1 hora — essenciais para debugging mas perdidos rapidamente.
Exportar Events para Loki/Elasticsearch
Para manter histórico de Events além de 1 hora, exporte-os para o sistema de log aggregation. Ferramentas: kube-event-exporter (exporta Events como logs estruturados para Elasticsearch, Loki, Slack), kubernetes-event-exporter (CNCF), ou via Fluent Bit com plugin de Events.
# kubectl — filtrar events de um namespace ou pod específico
kubectl get events -n production --sort-by=.lastTimestamp
# Filtrar apenas Warnings (erros)
kubectl get events -n production --field-selector type=Warning
# Events de um pod específico
kubectl describe pod <pod-name> -n production
# A seção "Events" mostra: FailedScheduling, BackOff, Killing, etc.
# kube-event-exporter — values.yaml (Helm)
config:
logLevel: error
logFormat: json
route:
routes:
- match:
- receiver: loki
receivers:
- name: loki
loki:
streamLabels:
cluster: production
app: kube-event-exporter
url: http://loki.monitoring.svc:3100/loki/api/v1/push
# Events chegam no Loki como logs estruturados
# busca: {app="kube-event-exporter"} | json | reason="OOMKilled"
# LogQL — encontrar todos OOMKilled nas últimas 24h
{app="kube-event-exporter"} | json
| reason="OOMKilled"
| line_format "{{.involvedObject_namespace}}/{{.involvedObject_name}}"
Events Importantes para Monitorar
- OOMKilled: container encerrado por falta de memória — memory limit muito baixo ou memory leak
- CrashLoopBackOff: container falhando repetidamente — erro na inicialização ou crash na aplicação
- FailedScheduling: pod não consegue ser agendado — recursos insuficientes, taints sem toleration, PVC não bound
- Killing: container encerrado por liveness probe ou graceful termination
- ImagePullBackOff: falha ao baixar a imagem — credencial inválida, imagem não encontrada
- ScalingReplicaSet: HPA ou deploy alterou o número de réplicas — útil para correlacionar mudanças com incidentes
- FailedMount: volume não pôde ser montado — PVC não bound, permissões, StorageClass não disponível
- BackOff: container restart backoff exponencial — cada reinício dobra o tempo de espera (10s, 20s, 40s... até 5min)
OTel Operator para Kubernetes
O OpenTelemetry Operator é um Kubernetes Operator que gerencia a configuração de instrumentação OTel no cluster. Ele resolve dois problemas: (1) implantar e manter o OTel Collector de forma declarativa via CRDs; (2) auto-instrumentar pods sem modificar os deployments das aplicações (via instrumentação por mutating webhook).
OpenTelemetryCollector CRD
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel-collector
namespace: monitoring
spec:
mode: DaemonSet # um Collector por nó (agent mode)
config: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# Scraping de métricas Prometheus dos pods
prometheus:
config:
scrape_configs:
- job_name: pod-scraping
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: "true"
processors:
# Adicionar metadados Kubernetes ao resource dos spans
k8sattributes:
auth_type: serviceAccount
extract:
metadata:
- k8s.pod.name
- k8s.namespace.name
- k8s.node.name
- k8s.deployment.name
labels:
- tag_name: app.version
key: app.kubernetes.io/version
from: pod
batch:
timeout: 5s
# Limitar memória do Collector em ambientes constrained
memory_limiter:
limit_mib: 400
spike_limit_mib: 100
check_interval: 5s
exporters:
otlp/tempo:
endpoint: tempo.monitoring.svc:4317
tls: { insecure: true }
prometheusremotewrite:
endpoint: http://mimir.monitoring.svc:9009/api/v1/push
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, batch]
exporters: [otlp/tempo]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, k8sattributes, batch]
exporters: [prometheusremotewrite]
Auto-Instrumentation via Instrumentation CRD
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: otel-instrumentation
namespace: default
spec:
exporter:
endpoint: http://otel-collector.monitoring.svc:4318
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "0.1" # 10% sampling
# Configuração por linguagem
java:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
python:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
dotnet:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-dotnet:latest
nodejs:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
---
# Pod: anotar para auto-instrumentação
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
metadata:
annotations:
# O Operator injeta automaticamente o agente OTel no container
instrumentation.opentelemetry.io/inject-dotnet: "default/otel-instrumentation"
# Para Python:
# instrumentation.opentelemetry.io/inject-python: "default/otel-instrumentation"
A auto-instrumentação via Operator usa um mutating admission webhook: quando um pod é criado com a annotation correta, o webhook intercepta o request de criação antes de ser persistido no etcd, injeta um init container que prepara o agente OTel, e adiciona variáveis de ambiente ao container da aplicação (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME, NODE_IP para resolver o collector DaemonSet). A imagem Docker original não é alterada — o agente vive apenas no ambiente de execução do pod.
Stack Completo de Observabilidade em Kubernetes
Uma stack bem integrada cobre todas as camadas do cluster com ferramentas que se correlacionam. A stack LGTM (Loki, Grafana, Tempo, Mimir) é o padrão open-source mais adotado para clusters Kubernetes de médio a grande porte.
# kube-prometheus-stack (Helm) — instala o conjunto completo
# Inclui: Prometheus Operator, Grafana, AlertManager,
# kube-state-metrics, node-exporter, ServiceMonitor CRDs
helm install kube-prometheus-stack \
prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi \
--set grafana.persistence.enabled=true \
--set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.resources.requests.storage=10Gi
# Adicionar Loki stack (logs)
helm install loki grafana/loki-stack \
--namespace monitoring \
-f loki-values.yaml # configuração S3, retenção, etc.
# Adicionar Tempo (traces)
helm install tempo grafana/tempo-distributed \
--namespace monitoring \
-f tempo-values.yaml
# Adicionar OTel Operator
helm install opentelemetry-operator \
open-telemetry/opentelemetry-operator \
--namespace monitoring \
--set manager.featureGates=EnableMultiInstrumentation
# Resultado: LGTM stack completo
# L = Loki (logs) → Grafana datasource: Loki
# G = Grafana (UI) → dashboards + alerts
# T = Tempo (traces) → Grafana datasource: Tempo (trace-to-logs via Loki)
# M = Mimir/Prometheus → Grafana datasource: Prometheus-compatible
ServiceMonitor — Scraping Automático de Aplicações
O Prometheus Operator usa ServiceMonitor CRDs para configurar scraping de métricas. Um ServiceMonitor seleciona Services via labels e configura o endpoint de scraping — sem editar o ConfigMap do Prometheus manualmente. O Operator reconcilia continuamente: qualquer ServiceMonitor novo é automaticamente descoberto e adicionado à configuração do Prometheus.
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: order-service
namespace: default
labels:
# Este label deve matchear o seletor do Prometheus
release: kube-prometheus-stack
spec:
selector:
matchLabels:
app: order-service # seleciona Services com este label
endpoints:
- port: http # nome da porta no Service
path: /metrics # endpoint de métricas
interval: 30s
# Adicionar labels ao scrape para enriquecimento
relabelings:
- sourceLabels: [__meta_kubernetes_pod_name]
targetLabel: pod
- sourceLabels: [__meta_kubernetes_namespace]
targetLabel: namespace
---
# PrometheusRule — alertas declarativos via CRD (em vez de editar config do Alertmanager)
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: order-service-alerts
labels:
release: kube-prometheus-stack
spec:
groups:
- name: order-service
rules:
- alert: OrderServiceHighErrorRate
expr: |
rate(http_requests_total{service="order-service", code=~"5.."}[5m])
/ rate(http_requests_total{service="order-service"}[5m]) > 0.01
for: 5m
labels:
severity: page
annotations:
summary: "Order service error rate > 1%"
Debugging de Pods em Produção
kubectl debug — Container Efêmero
Containers efêmeros permitem adicionar um container de debugging a um pod em execução sem reiniciá-lo — útil quando a imagem do pod não tem ferramentas de debugging (distroless, scratch). O container efêmero compartilha o namespace de rede e processo do pod.
# Adicionar container de debugging ao pod em execução
kubectl debug -it pod/order-service-abc123 \
--image=nicolaka/netshoot \
--target=order-service # compartilha namespace de processo
# Dentro do netshoot: inspecionar conexões de rede do pod
# ss -tlnp → sockets TCP abertos
# tcpdump -i eth0 port 80 -A → capturar tráfego HTTP
# dig order-service.default.svc.cluster.local → DNS resolution
# curl -v http://payment-service:8080/health → testar conectividade
# Debug de imagem distroless — criar pod cópia com shell
kubectl debug pod/order-service-abc123 \
-it --copy-to=debug-pod \
--set-image=order-service=busybox \
--share-processes
# Port-forward para acessar pprof endpoint (Go)
kubectl port-forward pod/order-service-abc123 6060:6060
# go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Acessar endpoint de profiling Pyroscope de uma aplicação (.NET)
kubectl port-forward pod/order-service-abc123 4040:4040
Checklist de Debugging de Pod
# 1. Status geral
kubectl get pod <pod> -o wide # status, nó, IP, restarts
# 2. Eventos do pod — causa de problemas de scheduling ou crash
kubectl describe pod <pod> | grep -A 20 Events
# 3. Logs atuais e anteriores (se o container foi reiniciado)
kubectl logs <pod> --tail=100
kubectl logs <pod> --previous # logs do container anterior
# 4. Recursos consumidos vs limits
kubectl top pod <pod> --containers
# 5. Variáveis de ambiente e configuração
kubectl exec <pod> -- env | sort
kubectl exec <pod> -- cat /etc/config/app.yaml # se ConfigMap montado
# 6. Conectividade de rede
kubectl exec <pod> -- curl -v http://<service>:<port>/health
kubectl exec <pod> -- nslookup <service-name> # DNS resolution
# 7. Checar se o problema é o nó (comparar com outros pods no mesmo nó)
kubectl get pods -o wide | grep <node-name>
# Diagnóstico por exit code:
# 0 → processo saiu normalmente (liveness probe mal configurado?)
# 1 → erro genérico na aplicação
# 137 → OOMKilled (SIGKILL, sinal 9)
# 143 → SIGTERM — graceful termination (rolling update, eviction)
Decisões de Engenharia
kube-prometheus-stack vs componentes individuais?
kube-prometheus-stack é quase sempre a escolha certa — inclui Prometheus Operator, Grafana pré-configurado com dashboards para Kubernetes, AlertManager, kube-state-metrics e node-exporter, com CRDs para ServiceMonitor e PrometheusRule. Instalar individualmente faz sentido apenas quando você tem requisitos específicos que o stack não suporta (ex: integração com Datadog ou New Relic como backend). O tempo economizado na configuração inicial é significativo — dashboards de K8s pré-criados, alertas padrão incluídos.
OTel Operator auto-instrumentation vs SDK manual?
Auto-instrumentation é ótima para adoção rápida em times grandes — você instrumenta todos os services de uma linguagem sem alterar os deployments. A limitação: cobertura de frameworks é mais limitada que o SDK completo, e você não tem controle sobre spans de lógica de negócio. SDK manual: mais trabalho, mais controle, melhor para customização de atributos e spans específicos. Estratégia mista: auto-instrumentation para cobertura baseline de HTTP/DB, SDK manual para spans de negócio (checkout, payment) onde os atributos importam.
Prometheus local vs Thanos/Mimir para retenção longa?
Prometheus local: 15-30 dias máximo (TSDB não é otimizado para longo prazo, memória cresce linearmente com séries ativas). Para retenção longa: Thanos ou Mimir como remote storage (escalam para anos com S3, query deduplication entre réplicas). Regra prática: dashboards e alertas (30 dias) ficam no Prometheus local; análise de capacidade e tendências históricas ficam no Thanos/Mimir. kube-prometheus-stack facilita configurar remote_write para Mimir com poucas linhas no values.yaml.
Como detectar OOMKilled antes que vire incidente?
Acompanhar working set memory vs limit via cAdvisor: container_memory_working_set_bytes / container_spec_memory_limit_bytes. Alertar quando > 80% por > 10 minutos — o container está prestes a ser OOMKilled. Também monitorar a tendência: se a memória está crescendo linearmente há horas, é um leak que vai causar OOMKill eventualmente. Use predict_linear(container_memory_working_set_bytes[1h], 3600) para prever quando o limite será atingido. Correlacione com profiling de memória (Pyroscope ou dotnet-dump) para identificar o alocador responsável antes do crash.
Perguntas de Entrevista
Qual a diferença entre kube-state-metrics e node-exporter? Quando você usaria cada um?
kube-state-metrics expõe métricas sobre o estado lógico dos objetos Kubernetes: quantas réplicas um Deployment deseja vs tem disponíveis, qual o status de um Pod (Running/Pending/Failed), se um Node tem a condição Ready=True, etc. É a API do Kubernetes refletida como métricas Prometheus. Use para alertas de "algum pod está em CrashLoopBackOff?" ou "o HPA atingiu o limite de réplicas?".
node-exporter expõe métricas do sistema operacional de cada nó: CPU por modo (user, system, iowait, steal), memória disponível vs total, I/O de disco, latência de disco, tráfego de rede. Não conhece conceitos Kubernetes — é agnóstico ao orquestrador. Use para alertas de "o nó está com CPU a 95%" ou "o disco está quase cheio" ou "há CPU steal alto indicando vizinhos barulhentos".
Os dois são complementares e necessários: kube-state-metrics para o que o Kubernetes está tentando fazer, node-exporter para o que o hardware está suportando. Em um incidente de latência, você consulta ambos: KSM para ver se pods foram evicted ou reiniciados, node-exporter para ver se o nó estava sob pressão de CPU ou memória no momento do incidente. cAdvisor (via Kubelet) completa o quadro com consumo real por container.
O que é um container efêmero no Kubernetes e como você o usaria para debugging?
Um container efêmero (kubectl debug) é um container adicionado a um pod já em execução, sem reiniciá-lo. Ele compartilha o namespace de rede e pode compartilhar o namespace de processo com os containers existentes (flag --target). É usado quando a imagem do pod não tem ferramentas de debugging — ex: imagens distroless baseadas em scratch, sem shell, sem curl, sem netstat — mas você precisa investigar um problema em produção sem alterar o deployment.
Uso prático: kubectl debug -it pod/order-service-xyz --image=nicolaka/netshoot --target=order-service. O container netshoot tem ferramentas de rede (tcpdump, curl, ss, nslookup, dig, iperf3) e você pode inspecionar as conexões de rede do pod, fazer DNS resolution, capturar tráfego HTTP, ou até conectar ao processo via strace se o namespace de processo for compartilhado. Após terminar o debug, o container efêmero é descartado automaticamente quando a sessão termina — não persiste no pod.
Limitação: containers efêmeros não podem ser removidos uma vez adicionados (são imutáveis), e não têm volumes do pod a não ser que você monte explicitamente. Para investigar o sistema de arquivos da aplicação, use --copy-to para criar um pod cópia com uma imagem diferente.
Como você investigaria por que um pod está em CrashLoopBackOff?
Em ordem de diagnóstico: (1) kubectl describe pod <pod> — seção Events mostra o motivo do crash (OOMKilled? Exit code específico? Liveness probe failed? FailedScheduling?); (2) kubectl logs <pod> --previous — logs do container antes do crash atual; sem --previous, você vê logs do container atual que pode ter acabado de iniciar e ainda não logou o erro; (3) o Exit code revela a causa: 137 = OOMKilled (SIGKILL), 143 = SIGTERM (graceful termination), 1 = erro na aplicação, 0 = processo saiu normalmente (possível liveness probe mal configurado); (4) kubectl top pod <pod> --containers — se memory working set estava perto do limit antes do crash, é OOMKill iminente; (5) se o crash é rápido demais para capturar logs, aumente o initialDelaySeconds do liveness probe temporariamente, ou use startupProbe para dar mais tempo de inicialização antes das probes começarem; (6) verificar se há limite de CPU muito restritivo causando timeout nas probes — container_cpu_cfs_throttled_seconds_total.
Como você desenharia uma estratégia de observabilidade para um cluster Kubernetes com 50 teams e 300 microservices?
O desafio nessa escala é isolamento de tenants com acesso self-service. Estratégia em camadas: (1) Métricas — Mimir multi-tenant com tenant ID por namespace ou time; Prometheus por namespace ou Prometheus federado com remote_write para Mimir; ServiceMonitor com label de ownership; Grafana com datasource por tenant usando variável de tenant ID. (2) Logs — Loki com multi-tenancy habilitado; Fluent Bit DaemonSet injetando label de namespace/team em todo log; LogQL com mandatory label filter por tenant. (3) Traces — Tempo com multi-tenancy; OTel Collector com routing por tenant baseado em resource attribute k8s.namespace.name; Instrumentation CRD por namespace. (4) Dashboards — Grafana Org por large team ou folder por namespace; provisioning via GitOps (Grafonnet/Terraform); dashboards padrão de golden signals pré-criados para qualquer ServiceMonitor. (5) Alertas — PrometheusRule CRDs em namespace do team; Alertmanager com routing por label de team para o canal Slack do time; alertas de infra centralizados no time de platform.
A chave é self-service com guardrails: os teams criam seus próprios ServiceMonitors e PrometheusRules via GitOps, mas o platform team define os limites de cardinality (via relabeling drop em KSM) e as cotas de storage por tenant no Mimir.
Explique como funciona tecnicamente a auto-instrumentação via OTel Operator e mutating webhook.
O OTel Operator registra um MutatingAdmissionWebhook no Kubernetes. Quando um pod é criado em um namespace onde um Instrumentation CRD está configurado, o API server envia o request de criação do pod para o webhook antes de persistir no etcd. O webhook inspeciona as annotations do pod: se encontrar instrumentation.opentelemetry.io/inject-dotnet: "default/otel-instrumentation", ele modifica o spec do pod antes de retornar ao API server.
As modificações injetadas: (1) um initContainer que copia os binários do agente (ex: OpenTelemetry.AutoInstrumentation para .NET) para um volume compartilhado via EmptyDir; (2) variáveis de ambiente no container da aplicação: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME (derivado do label app), CORECLR_ENABLE_PROFILING=1, CORECLR_PROFILER e CORECLR_PROFILER_PATH apontando para o agente copiado pelo initContainer; (3) o volume EmptyDir montado no container. A imagem Docker original não é alterada — toda a instrumentação vive no ambiente de execução do pod criado pelo Kubernetes.
Limitações: a auto-instrumentação não cobre spans de lógica de negócio customizada, e frameworks não suportados pela biblioteca de auto-instrumentação ficam sem cobertura. Para Go, não há suporte de auto-instrumentation (Go requer compilation-time instrumentation) — o SDK manual é obrigatório.
Como Praticar
-
Instale kube-prometheus-stack em um cluster local (kind ou k3d) e verifique que kube-state-metrics e node-exporter já estão disponíveis. Abra o Grafana, explore os dashboards pré-criados de Kubernetes. Crie um Deployment de uma aplicação simples expondo
/metrics, escreva um ServiceMonitor e confirme que as métricas aparecem como alvo no Prometheus (/targets).
Critério: ServiceMonitor visible no Prometheus targets como UP; métrica customizada da aplicação consultável no Grafana; pelo menos um alerta disparado via PrometheusRule CRD. -
Configure kube-event-exporter para exportar Events do Kubernetes para Loki. Depois, force um OOMKilled criando um container com memory limit de 32Mi que aloca memória além do limite. Escreva uma LogQL query que encontre todos os OOMKilled events nas últimas 24h, agrupados por namespace.
Critério: Events chegando no Loki com campos JSON parsed; query{app="kube-event-exporter"} | json | reason="OOMKilled"retornando o evento correto; correlação do timestamp do event com a métricacontainer_memory_working_set_bytesno Grafana. -
Simule um CrashLoopBackOff criando um pod que falha na inicialização (exit code 1 após 2 segundos). Siga o checklist de debugging: events, logs --previous, exit code, resources. Depois force um OOMKill (exit code 137) com outro pod. Documente a diferença no diagnóstico entre os dois cenários.
Critério: Root cause identificado corretamente em cada cenário usando apenas kubectl (sem olhar o código-fonte do pod); diferença entre exit code 1 e 137 explicada com evidências dos logs/events. -
Instale o OTel Operator e configure um Instrumentation CRD para uma aplicação em .NET ou Python. Anote o Deployment com a annotation de auto-instrumentation. Confirme que traces aparecem no Tempo/Jaeger com os resource attributes
k8s.pod.name,k8s.namespace.nameek8s.deployment.nameenriquecidos pelo k8sattributes processor.
Critério: Traces visíveis com atributos K8s corretos; link trace-to-logs funcionando no Grafana (clicar no trace abre os logs Loki do pod correspondente pelo timestamp); nenhuma modificação na imagem Docker da aplicação. -
Configure um alerta de OOMKill iminente usando
container_memory_working_set_bytes / container_spec_memory_limit_bytes > 0.80como PrometheusRule, com um for de 10 minutos. Valide que o alerta dispara antes do OOMKill acontecer. Como bônus, adicione um segundo alerta usandopredict_linearpara prever quando o limite será atingido.
Critério: PrometheusRule criada via CRD (sem editar configmap do Prometheus); alerta aparece no Alertmanager como firing; alerta dispara pelo menos 5 minutos antes do OOMKill real ocorrer.
Referências
- docs kube-state-metrics — Exposed Metrics
- docs Prometheus node_exporter — Collectors
- docs OpenTelemetry Operator — Getting Started
- docs kube-prometheus-stack — Helm Chart README
- docs Kubernetes — Ephemeral Containers
- docs Prometheus Operator — Design & CRDs
- docs OTel — k8sattributes Processor
- article Grafana — LGTM Stack: Observability for Kubernetes
- blog Brendan Gregg — Linux Performance
- article CNCF — Observability Whitepaper
- docs Kubernetes — Events API Reference
- book Hightower, Burns, Beda — Kubernetes: Up and Running (3ª ed.)