Declaração do Problema
Precisa de visibilidade abrangente sobre os seus sistemas em produção, incluindo métricas, logs, traces e alertas, para identificar, diagnosticar e resolver problemas rapidamente.
Três Pilares da Observability
┌─────────────────────────────────────────────────────────────────────────┐
│ Observability Platform │
├─────────────────────┬─────────────────────┬─────────────────────────────┤
│ Metrics │ Logs │ Traces │
│ │ │ │
│ • System metrics │ • Application logs │ • Request flow │
│ • Application │ • Audit logs │ • Service dependencies │
│ metrics │ • Access logs │ • Latency breakdown │
│ • Business │ • Error logs │ • Error propagation │
│ metrics │ │ │
├─────────────────────┴─────────────────────┴─────────────────────────────┤
│ Alerting & Dashboards │
└─────────────────────────────────────────────────────────────────────────┘
Visão Geral da Arquitetura
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Application │ │ Application │ │ Application │
│ Pod A │ │ Pod B │ │ Pod C │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ metrics │ logs │ traces
│ /metrics │ stdout │ OTLP
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Prometheus │ │ Fluentd │ │ Jaeger │
│ Scraping │ │ Collection │ │ Collector │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────┐
│ Grafana │
│ Dashboards, Alerts, Exploration │
└──────────────────────────────────────────────────┘
1. Métricas com Prometheus
Instalar a Stack do Prometheus
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--values prometheus-values.yaml
prometheus-values.yaml
prometheus:
prometheusSpec:
retention: 15d
resources:
requests:
memory: 1Gi
cpu: 500m
limits:
memory: 2Gi
cpu: 1
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
# Service discovery para pods com anotações prometheus.io
additionalScrapeConfigs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
alertmanager:
config:
global:
slack_api_url: 'https://hooks.slack.com/services/xxx'
route:
group_by: ['alertname', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'slack-notifications'
routes:
- match:
severity: critical
receiver: 'pagerduty'
receivers:
- name: 'slack-notifications'
slack_configs:
- channel: '#alerts'
send_resolved: true
title: '{{ .Status | toUpper }}: {{ .CommonAnnotations.summary }}'
text: '{{ .CommonAnnotations.description }}'
- name: 'pagerduty'
pagerduty_configs:
- service_key: 'your-pagerduty-key'
grafana:
adminPassword: 'secure-password'
persistence:
enabled: true
size: 10Gi
Instrumentação de Métricas da Aplicação
Node.js com prom-client
const client = require('prom-client');
const express = require('express');
// Criar um registry
const register = new client.Registry();
// Adicionar métricas por defeito (CPU, memória, etc.)
client.collectDefaultMetrics({ register });
// Métricas personalizadas
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.001, 0.005, 0.015, 0.05, 0.1, 0.5, 1, 5]
});
register.registerMetric(httpRequestDuration);
const httpRequestsTotal = new client.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
register.registerMetric(httpRequestsTotal);
const activeConnections = new client.Gauge({
name: 'active_connections',
help: 'Number of active connections'
});
register.registerMetric(activeConnections);
// Middleware para acompanhar métricas
const metricsMiddleware = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const route = req.route?.path || req.path;
httpRequestDuration.observe(
{ method: req.method, route, status_code: res.statusCode },
duration
);
httpRequestsTotal.inc({ method: req.method, route, status_code: res.statusCode });
});
next();
};
// Endpoint de métricas
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
PHP/Laravel com Prometheus Exporter
// composer require promphp/prometheus_client_php
use Prometheus\CollectorRegistry;
use Prometheus\RenderTextFormat;
use Prometheus\Storage\Redis;
class MetricsController extends Controller
{
private CollectorRegistry $registry;
public function __construct()
{
$adapter = new Redis(['host' => env('REDIS_HOST')]);
$this->registry = new CollectorRegistry($adapter);
}
public function index()
{
$renderer = new RenderTextFormat();
return response($renderer->render($this->registry->getMetricFamilySamples()))
->header('Content-Type', RenderTextFormat::MIME_TYPE);
}
}
// Middleware para métricas de pedidos
class MetricsMiddleware
{
public function handle($request, Closure $next)
{
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
$histogram = $this->registry->getOrRegisterHistogram(
'app',
'http_request_duration_seconds',
'Request duration',
['method', 'route', 'status'],
[0.01, 0.05, 0.1, 0.5, 1, 5]
);
$histogram->observe(
$duration,
[$request->method(), $request->route()->uri(), $response->status()]
);
return $response;
}
}
Anotações de Pod para Scraping
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
containers:
- name: app
ports:
- name: metrics
containerPort: 8080
2. Logging Centralizado
Instalar a Stack do Loki
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install loki grafana/loki-stack \
--namespace monitoring \
--set promtail.enabled=true \
--set grafana.enabled=false \ # Usar o Grafana existente
--values loki-values.yaml
loki-values.yaml
loki:
persistence:
enabled: true
size: 100Gi
config:
limits_config:
retention_period: 30d
table_manager:
retention_deletes_enabled: true
retention_period: 30d
promtail:
config:
snippets:
scrapeConfigs: |
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
- cri: {}
- json:
expressions:
level: level
message: message
timestamp: timestamp
- labels:
level:
- timestamp:
source: timestamp
format: RFC3339
relabel_configs:
- source_labels:
- __meta_kubernetes_pod_label_app
target_label: app
- source_labels:
- __meta_kubernetes_namespace
target_label: namespace
- source_labels:
- __meta_kubernetes_pod_name
target_label: pod
Logging Estruturado nas Aplicações
Node.js com Winston
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: {
service: 'user-service',
version: process.env.APP_VERSION
},
transports: [
new winston.transports.Console()
]
});
// Utilização
logger.info('User created', {
userId: user.id,
email: user.email,
requestId: req.id
});
logger.error('Database connection failed', {
error: err.message,
stack: err.stack,
retryCount: 3
});
Logging em PHP/Laravel
// config/logging.php
'channels' => [
'stdout' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stdout',
],
'formatter' => JsonFormatter::class,
],
],
// Utilização
Log::channel('stdout')->info('User created', [
'user_id' => $user->id,
'email' => $user->email,
'request_id' => request()->header('X-Request-ID'),
]);
3. Distributed Tracing com Jaeger
Instalar o Jaeger
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm repo update
helm install jaeger jaegertracing/jaeger \
--namespace monitoring \
--set provisionDataStore.cassandra=false \
--set storage.type=elasticsearch \
--set storage.elasticsearch.host=elasticsearch.monitoring.svc \
--set collector.service.type=ClusterIP
Instrumentação com OpenTelemetry
Node.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'user-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
environment: process.env.NODE_ENV,
}),
traceExporter: new OTLPTraceExporter({
url: 'http://jaeger-collector.monitoring.svc:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
// Spans personalizados
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('user-service');
async function processOrder(orderId) {
return tracer.startActiveSpan('processOrder', async (span) => {
span.setAttribute('order.id', orderId);
try {
// Processar a lógica de encomendas
const result = await validateOrder(orderId);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
span.recordException(error);
throw error;
} finally {
span.end();
}
});
}
4. Regras de Alerting
Alertas Críticos
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: critical-alerts
namespace: monitoring
spec:
groups:
- name: availability
rules:
- alert: ServiceDown
expr: up{job="kubernetes-pods"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Service {{ $labels.instance }} is down"
description: "{{ $labels.job }} has been down for more than 1 minute."
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status_code=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
> 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate on {{ $labels.service }}"
description: "Error rate is {{ $value | humanizePercentage }} (>5%)"
- alert: HighLatency
expr: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "High latency on {{ $labels.service }}"
description: "95th percentile latency is {{ $value }}s"
- name: resources
rules:
- alert: HighMemoryUsage
expr: |
container_memory_usage_bytes{container!=""}
/
container_spec_memory_limit_bytes{container!=""}
> 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "High memory usage in {{ $labels.pod }}"
description: "Memory usage is {{ $value | humanizePercentage }}"
- alert: HighCPUUsage
expr: |
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod, namespace)
/
sum(container_spec_cpu_quota{container!=""}/container_spec_cpu_period{container!=""}) by (pod, namespace)
> 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "High CPU usage in {{ $labels.pod }}"
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} is crash looping"
- name: database
rules:
- alert: DatabaseConnectionsExhausted
expr: |
pg_stat_activity_count{datname!~"template.*"}
/
pg_settings_max_connections > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "PostgreSQL connections near limit"
- alert: DatabaseReplicationLag
expr: pg_replication_lag > 300
for: 5m
labels:
severity: critical
annotations:
summary: "PostgreSQL replication lag is {{ $value }}s"
5. Dashboards do Grafana
Dashboard da Aplicação (JSON)
{
"title": "Application Overview",
"panels": [
{
"title": "Request Rate",
"type": "graph",
"targets": [
{
"expr": "sum(rate(http_requests_total[5m])) by (service)",
"legendFormat": "{{ service }}"
}
]
},
{
"title": "Error Rate",
"type": "graph",
"targets": [
{
"expr": "sum(rate(http_requests_total{status_code=~\"5..\"}[5m])) by (service) / sum(rate(http_requests_total[5m])) by (service)",
"legendFormat": "{{ service }}"
}
]
},
{
"title": "Latency (p95)",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))",
"legendFormat": "{{ service }}"
}
]
},
{
"title": "Active Pods",
"type": "stat",
"targets": [
{
"expr": "count(kube_pod_status_phase{phase=\"Running\", namespace=\"production\"})"
}
]
}
]
}
A Mentalidade de Observability de um Senior
«O servidor está em baixo.» «Porquê?» «Não sei, não consigo fazer SSH.» Se depende de SSH para ver logs, já perdeu.
Engenheiros seniores tratam a observability como uma parte central do design do sistema. Constroem os sinais que tornam o debugging rápido e a resposta a incidentes eficaz.
Defina Primeiro os SLOs
A observability é mais útil quando está ligada a objetivos claros. Defina SLOs para fluxos de trabalho críticos, como disponibilidade e latência da API. Use esses SLOs para definir limiares de alertas e objetivos de dashboards. Se não souber como é o «bom», não consegue detetar quando o sistema está pouco saudável.
Logging Estruturado: Pare de Registar Strings
Mau: [ERROR] User 123 failed payment: timeout Bom: {"level": "error", "user_id": 123, "action": "payment", "error": "timeout"}
No Kibana, pode filtrar por action: payment. Com strings, está a escrever RegEx.
Correlation IDs São Inegociáveis
Em microservices, um clique do utilizador desencadeia logs em 5 serviços.
Padrão: 1. O gateway gera X-Request-ID. 2. Cada serviço regista request_id. 3. Cada serviço passa-o a jusante nos headers.
Resultado: Uma única query no Kibana mostra o trace completo em todo o cluster.
Estratégia de Retenção de Logs
Os logs são caros. Planeie em conformidade:
- Hot (SSD): Últimos 7 dias. Pesquisa rápida.
- Warm (HDD): Últimos 30 dias. Pesquisa lenta.
- Cold (S3): Último 1 ano. Arquivado.
- Eliminar: Política automatizada de Index Lifecycle Management (ILM).
Conceba o Alerting para Ação
Os alertas devem desencadear ações claras. Use limiares que reflitam o impacto real no utilizador e evite alertar por ruído. Se um alerta não puder ser acionado, deve ser removido ou alterado.
Use alertas multi-sinal sempre que possível, como taxa de erro mais latência mais saturação. Isto reduz falsos positivos.
Armadilhas Comuns
- Registar demasiado sem estrutura.
- Falta de correlation IDs entre serviços.
- Alertar para cada erro em vez do impacto no utilizador.
- Sem políticas de retenção, levando a custos descontrolados.
- Dashboards desatualizados ou não utilizados.
Acesso Rápido a Logs de Containers
Adicione aliases simples para engenheiros de on-call fazerem stream de logs sem esperar pelo pipeline central:
# Acesso rápido a logs de pods
alias klogs='kubectl logs -f'
alias dlogs='docker logs -f'
# Fazer stream de logs de todos os pods de um deployment
kubectl logs -f deployment/myapp --all-containers
Checklist de Observability
Métricas
- [ ] Métricas de sistema recolhidas (CPU, memória, disco, rede)
- [ ] Métricas da aplicação instrumentadas (métricas RED)
- [ ] Métricas de negócio acompanhadas
- [ ] Dashboards criados para serviços-chave
- [ ] Regras de alerting definidas para SLOs
Logs
- [ ] Logging estruturado implementado (formato JSON)
- [ ] Níveis de log usados adequadamente
- [ ] Request IDs propagados por todos os serviços
- [ ] Dados sensíveis não registados
- [ ] Retenção de logs configurada com políticas ILM
Traces
- [ ] Distributed tracing ativado
- [ ] Propagação de contexto a funcionar
- [ ] Caminhos críticos com trace
- [ ] Sampling configurado adequadamente
Alerting
- [ ] Rotação de on-call definida
- [ ] Políticas de escalonamento configuradas
- [ ] Fadiga de alertas minimizada
- [ ] Runbooks associados aos alertas
- [ ] Alertas multi-sinal para caminhos críticos
Resumo dos Três Pilares
- Logs dizem-lhe porquê algo aconteceu.
- Métricas dizem-lhe quando.
- Traces dizem-lhe onde.
Precisa dos três. Uma boa stack de observability transforma sistemas complexos em sistemas compreensíveis.
Artigos Relacionados na Wiki