DevOps Admin

Guia Completo de SRE na Prática: SLIs, SLOs, SLAs e Error Budgets Já leu

SRE: O Fundamento para Confiabilidade em Produção Site Reliability Engineering (SRE) é uma disciplina que aplica princípios de engenharia de software à operação de sistemas. Criada pelo Google, ela reconhece uma verdade fundamental: sistemas perfeitos não existem. Em vez de buscar confiabilidade absoluta (impossível e economicamente inviável), SRE define o nível de confiabilidade que o negócio realmente necessita e trabalha para mantê-lo de forma sustentável. A proposta de SRE é revolucionária porque muda o paradigma. Não se trata mais de "quanto mais disponibilidade, melhor". Em vez disso, perguntamos: "Qual é a disponibilidade adequada para este serviço?" e "Como operamos de forma eficiente dentro dessa margem?". Isso cria um contrato claro entre produto, negócio e operações, eliminando conflitos invisíveis e permitindo que engenheiros façam escolhas arquiteturais informadas. SLI, SLO e SLA: A Tríade de Confiabilidade O que é um SLI (Service Level Indicator)? Um SLI é uma métrica que mede o comportamento observável do seu sistema. É um número entre 0

SRE: O Fundamento para Confiabilidade em Produção

Site Reliability Engineering (SRE) é uma disciplina que aplica princípios de engenharia de software à operação de sistemas. Criada pelo Google, ela reconhece uma verdade fundamental: sistemas perfeitos não existem. Em vez de buscar confiabilidade absoluta (impossível e economicamente inviável), SRE define o nível de confiabilidade que o negócio realmente necessita e trabalha para mantê-lo de forma sustentável.

A proposta de SRE é revolucionária porque muda o paradigma. Não se trata mais de "quanto mais disponibilidade, melhor". Em vez disso, perguntamos: "Qual é a disponibilidade adequada para este serviço?" e "Como operamos de forma eficiente dentro dessa margem?". Isso cria um contrato claro entre produto, negócio e operações, eliminando conflitos invisíveis e permitindo que engenheiros façam escolhas arquiteturais informadas.

SLI, SLO e SLA: A Tríade de Confiabilidade

O que é um SLI (Service Level Indicator)?

Um SLI é uma métrica que mede o comportamento observável do seu sistema. É um número entre 0 e 100% que indica "o quanto de tempo meu sistema se comportou como esperado?". SLIs não são opiniões — são dados mensuráveis e objetivos extraídos diretamente da observabilidade.

Exemplos concretos de SLIs incluem: latência de requisições HTTP (percentual de requisições respondidas em menos de 200ms), taxa de erro (percentual de requisições que não retornaram 5xx), disponibilidade de conexões de banco de dados, ou taxa de sucesso de transações de pagamento. A escolha do SLI deve refletir o que o usuário realmente percebe.

O que é um SLO (Service Level Objective)?

Um SLO é uma meta que você se compromete a atingir para um SLI específico. Se o SLI é "percentual de requisições com latência < 200ms", o SLO seria algo como "99.9% das requisições devem responder em menos de 200ms, medido em janelas de 30 dias". O SLO transforma uma métrica em um alvo negociado.

SLOs devem ser escolhidos estrategicamente. Não defina SLOs que são impossíveis de manter — isto causa esgotamento. Defina SLOs que seu negócio realmente necessita. Um serviço crítico pode ter 99.99% de disponibilidade, enquanto uma API interna pode ter 99.5%. A diferença é enorme em custo operacional.

O que é um SLA (Service Level Agreement)?

Um SLA é um contrato legal ou comercial com consequências financeiras ou de outro tipo se não for cumprido. Um SLA geralmente é mais restritivo que o SLO correspondente (para deixar margem de segurança). Por exemplo: seu SLO pode ser 99.95%, mas seu SLA com clientes é 99.9% — isso deixa 0.05% como amortecedor.

SLAs existem principalmente em contextos B2B, onde você oferece um serviço a clientes externos. SLOs existem em contextos onde você define metas internas. Muitas organizações confundem os termos, o que causa problemas. Deixe claro: SLA = contrato com consequências; SLO = meta operacional interna.

Medindo SLIs na Prática

Exemplos de SLIs Bem Estruturados

A escolha do SLI determinará tudo que vem depois. Um bom SLI deve ser: (1) mensurável com ferramentas que você já possui, (2) correlacionado com a satisfação do usuário, e (3) acionável — você consegue fazer algo quando ele piora.

SLIs ruins geralmente medem componentes de infraestrutura em vez de experiência do usuário. "CPU em 85%" é um indicador de saúde, não de confiabilidade. "99% das requisições respondidas em menos de 500ms" é um bom SLI.

# Exemplo: Calculando SLI de latência usando Prometheus
# Este código coleta métricas e calcula o percentual de requisições rápidas

from prometheus_client import Counter, Histogram, start_http_server
import time

# Histograma para rastrear latências
request_latency = Histogram(
    'http_request_duration_seconds',
    'Latência de requisições HTTP',
    buckets=(0.1, 0.2, 0.5, 1.0, 2.0)
)

# Contador para requisições bem-sucedidas
requests_success = Counter(
    'http_requests_success_total',
    'Total de requisições bem-sucedidas'
)

# Contador para requisições falhadas
requests_failed = Counter(
    'http_requests_failed_total',
    'Total de requisições falhadas'
)

def simulated_request():
    """Simula uma requisição e registra sua latência"""
    import random
    start = time.time()

    # 95% das requisições levam entre 50 e 200ms
    # 5% levam mais de 500ms
    if random.random() < 0.95:
        time.sleep(random.uniform(0.05, 0.2))
        requests_success.inc()
    else:
        time.sleep(random.uniform(0.5, 2.0))
        requests_failed.inc()

    duration = time.time() - start
    request_latency.observe(duration)

# Iniciando servidor Prometheus
if __name__ == '__main__':
    start_http_server(8000)
    print("Métricas disponíveis em http://localhost:8000/metrics")

    # Loop simulando requisições
    for _ in range(100):
        simulated_request()

Instrumentação e Coleta de Dados

Para medir SLIs, você precisa de instrumentação. A abordagem mais comum é usar logs estruturados ou métricas de tempo real. Logs são úteis para análise histórica, mas métricas são melhores para alertas em tempo real.

# Exemplo: Capturando SLI de taxa de erro com logging estruturado
import json
import logging
from datetime import datetime

# Configurar logger estruturado
class StructuredLogger:
    def __init__(self, name):
        self.logger = logging.getLogger(name)
        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter('%(message)s'))
        self.logger.addHandler(handler)
        self.logger.setLevel(logging.INFO)

    def log_request(self, endpoint, status_code, duration_ms, user_id):
        event = {
            'timestamp': datetime.utcnow().isoformat(),
            'endpoint': endpoint,
            'status_code': status_code,
            'duration_ms': duration_ms,
            'user_id': user_id,
            'success': status_code < 400
        }
        self.logger.info(json.dumps(event))

# Usar o logger
logger = StructuredLogger('api')

# Simulando requisições
logger.log_request('/api/users', 200, 145, 'user_123')
logger.log_request('/api/products', 500, 2340, 'user_456')
logger.log_request('/api/orders', 200, 256, 'user_789')

# Saída:
# {"timestamp": "2024-01-15T10:30:00.123456", "endpoint": "/api/users", 
#  "status_code": 200, "duration_ms": 145, "user_id": "user_123", "success": true}

Error Budget: O Controle Financeiro de Falhas

O Conceito de Error Budget

Um error budget é literalmente o "orçamento de erros" que você pode ter sem violar seu SLO. Se seu SLO é 99.9% de disponibilidade mensal, você tem um orçamento de 0.1% de downtime. Em 30 dias, isso equivale a aproximadamente 43 minutos de downtime permissível.

O error budget muda radicalmente como você toma decisões. Se seu erro budget está se esgotando (poucas horas restantes), você para deployments. Se seu error budget está abundante (semanas restantes), você pode fazer experiências, refatorar agressivamente ou deployar features mais frequentemente. O error budget alinha os incentivos de produto, desenvolvimento e operações.

Calculando e Monitorando Error Budget

# Exemplo: Calculadora de error budget e monitoramento
from datetime import datetime, timedelta

class ErrorBudgetTracker:
    def __init__(self, slo_percentage, period_days=30):
        """
        slo_percentage: seu SLO em percentual (ex: 99.9)
        period_days: período de medição (geralmente 30 dias)
        """
        self.slo_percentage = slo_percentage
        self.period_days = period_days

        # Calcular o orçamento máximo de erros
        # Se SLO é 99.9%, o error budget é 0.1%
        self.error_budget_percentage = 100 - slo_percentage

        # Converter em minutos (para um mês de 30 dias)
        total_minutes = period_days * 24 * 60
        self.budget_minutes = (self.error_budget_percentage / 100) * total_minutes
        self.remaining_budget = self.budget_minutes

    def record_downtime(self, downtime_minutes):
        """Registra um período de downtime"""
        self.remaining_budget -= downtime_minutes
        utilization = ((self.budget_minutes - self.remaining_budget) / self.budget_minutes) * 100

        return {
            'downtime_recorded_minutes': downtime_minutes,
            'remaining_budget_minutes': max(0, self.remaining_budget),
            'budget_utilization_percent': round(utilization, 2),
            'status': self._get_status()
        }

    def _get_status(self):
        """Retorna status do error budget"""
        if self.remaining_budget <= 0:
            return 'EXHAUSTED'
        elif self.remaining_budget < (self.budget_minutes * 0.1):
            return 'CRITICAL'
        elif self.remaining_budget < (self.budget_minutes * 0.3):
            return 'LOW'
        else:
            return 'HEALTHY'

# Exemplo de uso
tracker = ErrorBudgetTracker(slo_percentage=99.9, period_days=30)

print(f"Error Budget Total: {tracker.budget_minutes:.1f} minutos (~{tracker.budget_minutes/60:.1f} horas)")
print(f"SLO: {tracker.slo_percentage}%\n")

# Simulando downtimes
incidents = [
    ('Database crash', 15),
    ('Network issue', 8),
    ('Bad deployment', 5),
]

for incident_name, minutes in incidents:
    result = tracker.record_downtime(minutes)
    print(f"Incidente: {incident_name}")
    print(f"  Status: {result['status']}")
    print(f"  Restante: {result['remaining_budget_minutes']:.1f} minutos")
    print(f"  Utilização: {result['budget_utilization_percent']}%\n")

# Saída esperada:
# Error Budget Total: 43.2 minutos (~0.7 horas)
# SLO: 99.9%
#
# Incidente: Database crash
#   Status: HEALTHY
#   Restante: 28.2 minutos
#   Utilização: 34.72%
#
# Incidente: Network issue
#   Status: LOW
#   Restante: 20.2 minutos
#   Utilização: 53.24%
#
# Incidente: Bad deployment
#   Status: CRITICAL
#   Restante: 15.2 minutos
#   Utilização: 64.81%

Usando Error Budget para Tomar Decisões

O error budget não é apenas uma métrica — é um instrumento de governança. Quando o error budget está saudável, você investe em melhorias de produto. Quando está crítico, você congela deploys e foca em confiabilidade. Quando está esgotado, você trabalha em modo de crise.

# Exemplo: Sistema que ajusta comportamento baseado em error budget
class DeploymentGatekeeper:
    def __init__(self, tracker):
        self.tracker = tracker

    def can_deploy_feature(self):
        """Decide se é seguro deployar uma feature nova"""
        if self.tracker.remaining_budget < (self.tracker.budget_minutes * 0.2):
            # Menos de 20% do budget restante
            return False, "Error budget crítico. Apenas fixes de segurança."
        return True, "Deployment autorizado."

    def deployment_strategy(self):
        """Recomenda estratégia de deployment baseada em budget"""
        budget_percent = (self.tracker.remaining_budget / self.tracker.budget_minutes) * 100

        if budget_percent > 50:
            return "canary"  # Pequena porção, rápido feedback
        elif budget_percent > 20:
            return "blue_green"  # Mais controlado
        else:
            return "manual_approval"  # Aprovação humana necessária

# Usando o gatekeeper
gatekeeper = DeploymentGatekeeper(tracker)

authorized, reason = gatekeeper.can_deploy_feature()
print(f"Deploy autorizado: {authorized}")
print(f"Razão: {reason}")
print(f"Estratégia recomendada: {gatekeeper.deployment_strategy()}")

Implementando SRE em Uma Arquitetura Real

Arquitetura de Observabilidade

Para colocar SRE em prática, você precisa de uma pilha de observabilidade robusta. Isso inclui métricas (Prometheus), logs (ELK, Loki) e traces distribuídos (Jaeger). Sem observabilidade, você não consegue medir SLIs, portanto não consegue operar SRE.

# Exemplo: Instrumentação completa de uma aplicação Flask com SRE em mente
from flask import Flask, request, jsonify
from prometheus_client import Counter, Histogram, Gauge, generate_latest
import time
import random

app = Flask(__name__)

# Métricas Prometheus
http_requests_total = Counter(
    'http_requests_total',
    'Total de requisições HTTP',
    ['method', 'endpoint', 'status']
)

http_request_duration = Histogram(
    'http_request_duration_seconds',
    'Duração das requisições HTTP',
    ['endpoint'],
    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5)
)

database_connections = Gauge(
    'database_connections_active',
    'Conexões ativas com banco de dados'
)

# Middleware para capturar métricas
@app.before_request
def before_request():
    request.start_time = time.time()

@app.after_request
def after_request(response):
    duration = time.time() - request.start_time

    # Registrar métrica de duração
    http_request_duration.labels(endpoint=request.path).observe(duration)

    # Registrar métrica de contagem
    http_requests_total.labels(
        method=request.method,
        endpoint=request.path,
        status=response.status_code
    ).inc()

    return response

# Endpoints da aplicação
@app.route('/api/users/<user_id>', methods=['GET'])
def get_user(user_id):
    """Buscar um usuário por ID"""
    # Simular latência variável
    time.sleep(random.uniform(0.05, 0.15))

    # 2% de chance de erro
    if random.random() < 0.02:
        return jsonify({'error': 'Database error'}), 500

    return jsonify({'id': user_id, 'name': 'John Doe'}), 200

@app.route('/api/health', methods=['GET'])
def health_check():
    """Healthcheck simples"""
    database_connections.set(random.randint(5, 20))
    return jsonify({'status': 'healthy'}), 200

@app.route('/metrics', methods=['GET'])
def metrics():
    """Endpoint Prometheus para scraping de métricas"""
    return generate_latest()

if __name__ == '__main__':
    print("Iniciando aplicação SRE-ready")
    print("- Métricas: http://localhost:5000/metrics")
    print("- Health: http://localhost:5000/api/health")
    print("- API: http://localhost:5000/api/users/<id>")
    app.run(debug=False, port=5000)

Alertas Baseados em SLO

Alertas tradicionais monitoram componentes. Alertas SRE monitoram SLIs. Quando seu SLI se desvia do SLO, você precisa saber rapidamente. A chave é criar alertas que identificam problemas que afetam o usuário, não problemas em camadas de infraestrutura.

# Exemplo: Regras de alerta Prometheus para SRE
# Arquivo: prometheus-sre-rules.yml

groups:
  - name: sre_alerts
    interval: 30s
    rules:
      # SLI: Taxa de erro HTTP
      - alert: HighErrorRate
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[5m]))
            /
            sum(rate(http_requests_total[5m]))
          ) > 0.001
        for: 2m
        annotations:
          summary: "Taxa de erro acima de 0.1% (SLO violado)"
          description: "{{ $value | humanizePercentage }} de erro nos últimos 5 minutos"

      # SLI: Latência P95
      - alert: HighLatencyP95
        expr: |
          histogram_quantile(0.95, 
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
          ) > 0.2
        for: 5m
        annotations:
          summary: "P95 de latência acima de 200ms"
          description: "P95: {{ $value }}s"

      # Error Budget: Taxa de consumo
      - alert: ErrorBudgetBurnRateTooHigh
        expr: |
          # Queimando mais de 10% do budget mensal por dia
          (sum(rate(http_requests_total{status=~"5.."}[1h])) 
           / sum(rate(http_requests_total[1h]))) > 0.001
        for: 1h
        annotations:
          summary: "Taxa de consumo de error budget muito alta"
          description: "Em 30 dias, error budget será esgotado"

      # Alerta crítico: Error budget quase esgotado
      - alert: ErrorBudgetCritical
        expr: |
          # Se mantiver esse burn rate, error budget acaba em 3 dias
          (ALERTS{severity="critical"}) > 0
        annotations:
          summary: "Error budget será esgotado em menos de 3 dias"
          description: "CONGELE deployments, focue em confiabilidade"

Cultura e Tomada de Decisão com SRE

Alinhando Incentivos

SRE funciona porque cria um contrato claro. Desenvolvimento sabe que tem um budget para "quebrar coisas" (error budget), operações sabe exatamente quanto pode tolerar, e produto entende o trade-off entre features e confiabilidade. Quando o error budget está saudável, todos ganham: produto entrega features, desenvolvimento itera rapidamente, operações opera de forma sustentável.

O erro mais comum é tentar alcançar 100% de uptime. Isso é impossível e desperdiça recursos enormes. Um SLO de 99.95% (52 minutos de downtime por ano) é completamente razoável para a maioria dos serviços. Invista os recursos economizados em experiência do usuário, ao invés de redundância infinita.

# Exemplo: Framework de decisão SRE
class SREDecisionFramework:
    def __init__(self, slo_tracker, incident_count_week):
        self.tracker = slo_tracker
        self.incidents_week = incident_count_week

    def should_prioritize_reliability_work(self):
        """Decide se é hora de parar de adicionar features e focar em confiabilidade"""
        # Critério 1: Error budget crítico
        budget_health = (self.tracker.remaining_budget / self.tracker.budget_minutes)

        # Critério 2: Muitos incidents
        too_many_incidents = self.incidents_week > 3

        return budget_health < 0.3 or too_many_incidents

    def post_incident_decision(self, incident_severity):
        """Decide ações após um incidente"""
        if incident_severity == 'critical' and self.tracker.remaining_budget < (self.tracker.budget_minutes * 0.1):
            return {
                'action': 'IMPLEMENT_FIX',
                'timeline': 'IMMEDIATE',
                'freeze_features': True,
                'reason': 'Restaurar confiabilidade do sistema'
            }
        elif incident_severity == 'minor':
            return {
                'action': 'POSTMORTEM',
                'timeline': '48_HOURS',
                'freeze_features': False,
                'reason': 'Aprender e melhorar processos'
            }
        else:
            return {
                'action': 'INVESTIGATE',
                'timeline': '24_HOURS',
                'freeze_features': False,
                'reason': 'Entender causa raiz'
            }

# Usar o framework
framework = SREDecisionFramework(tracker, incident_count_week=2)

print(f"Priorizar confiabilidade: {framework.should_prioritize_reliability_work()}")
print(f"Ação pós-incidente: {framework.post_incident_decision('critical')}")

Conclusão

Os três pontos-chave que você deve internalizar sobre SRE são: (1) SLI, SLO e SLA são contratos diferentes com propósitos diferentes — SLI mede realidade, SLO define meta operacional, SLA é contrato legal. Confundir os termos cria ambiguidade perigosa; (2) Error budget transforma como você toma decisões — não se trata de "quanto mais uptime, melhor", mas sim "qual é o nível de confiabilidade necessário e como operamos eficientemente dentro dessa margem?", eliminando conflitos invisíveis entre produto e operações; (3) SRE requer observabilidade obsessiva — você não consegue operar SRE sem métricas estruturadas, logs e traces distribuídos que permitam medir SLIs em tempo real e tomar decisões baseadas em dados, não em intuição.

Referências


Artigos relacionados