DevOps

Performance e FinOps: Otimizando Custo e Velocidade na Cloud Já leu

17 min de leitura

Performance e FinOps: Otimizando Custo e Velocidade na Cloud
Existe uma correlação quase universal entre o crescimento de um sistema em produção e o crescimento de seus custos de infraestrutura. O que começa como algumas instâncias EC2 e um banco de dados RDS evolui, ao longo de m

Existe uma correlação quase universal entre o crescimento de um sistema em produção e o crescimento de seus custos de infraestrutura. O que começa como algumas instâncias EC2 e um banco de dados RDS evolui, ao longo de meses, para dezenas de serviços, centenas de recursos e uma fatura AWS que surpreende até os mais experientes quando chega ao fim do mês.

A disciplina de FinOps — Financial Operations — trata a gestão de custos de cloud como uma responsabilidade de engenharia, não apenas financeira. O princípio central é que as pessoas que tomam decisões de arquitetura — engenheiros e arquitetos — devem ter visibilidade dos custos que essas decisões geram, e responsabilidade por otimizá-los. O FinOps não é sobre cortar custos a qualquer custo; é sobre maximizar o valor entregue por cada dólar gasto em infraestrutura.

Performance e custo estão intrinsecamente ligados: um sistema mal dimensionado paga pelo excesso de capacidade; um sistema sobrecarregado degrada a experiência do usuário e frequentemente gera custos de incidente superiores ao que seria economizado com dimensionamento correto. Otimizar os dois simultaneamente — entregando a performance que o negócio precisa pelo menor custo possível — é o objetivo desta disciplina.


Visibilidade de Custos com AWS Cost Explorer e Etiquetas

O primeiro passo de qualquer programa FinOps é visibilidade. Sem saber quanto cada parte do sistema custa, não é possível decidir onde otimizar.

Estratégia de Etiquetas para Alocação de Custos

# tags.tf — convenção de etiquetas para rastreamento de custos

locals {
  tags_comuns = {
    # Identificação do recurso
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "terraform"

    # Alocação de custos
    CostCenter  = var.cost_center      # ex: "eng-backend", "eng-plataforma"
    Team        = var.team             # ex: "checkout", "catalog", "infra"
    Owner       = var.owner            # email do responsável técnico

    # Lifecycle
    CreatedAt   = timestamp()
    ExpiresAt   = var.expires_at       # para recursos temporários

    # Compliance
    DataClass   = var.data_class       # "publico", "interno", "confidencial"
  }
}

# Política SCP (Service Control Policy) que exige etiquetas obrigatórias
# Aplicada no nível de AWS Organization para todos os recursos
resource "aws_organizations_policy" "exigir_etiquetas" {
  name = "ExigirEtiquetasObrigatorias"
  type = "SERVICE_CONTROL_POLICY"

  content = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "ExigirEtiquetaProject"
        Effect = "Deny"
        Action = [
          "ec2:RunInstances",
          "rds:CreateDBInstance",
          "elasticache:CreateCacheCluster",
          "eks:CreateCluster",
        ]
        Resource = "*"
        Condition = {
          "Null" = {
            "aws:RequestedRegion" = "false"
            "aws:ResourceTag/Project" = "true"
          }
        }
      }
    ]
  })
}

Dashboard de Custos com AWS Cost Explorer

#!/bin/bash
# scripts/relatorio-custos.sh
# Gera relatório de custos por serviço e equipe para o mês atual

MES_INICIO=$(date +%Y-%m-01)
MES_FIM=$(date -d "$(date +%Y-%m-01) +1 month" +%Y-%m-%d 2>/dev/null || \
          date -v+1m -v1d +%Y-%m-%d)

echo "=== Relatório de Custos AWS — $(date +%B/%Y) ==="
echo ""

# Custo total do mês
TOTAL=$(aws ce get-cost-and-usage \
  --time-period Start=${MES_INICIO},End=${MES_FIM} \
  --granularity MONTHLY \
  --metrics BlendedCost \
  --query 'ResultsByTime[0].Total.BlendedCost.Amount' \
  --output text)

echo "💰 Custo total do mês: USD ${TOTAL}"
echo ""

# Custo por serviço AWS
echo "📊 Custo por serviço:"
aws ce get-cost-and-usage \
  --time-period Start=${MES_INICIO},End=${MES_FIM} \
  --granularity MONTHLY \
  --metrics BlendedCost \
  --group-by Type=DIMENSION,Key=SERVICE \
  --query 'ResultsByTime[0].Groups[*].{
    Servico: Keys[0],
    Custo: Metrics.BlendedCost.Amount
  }' \
  --output json \
  | jq 'sort_by(.Custo | tonumber) | reverse | .[:10][] |
    "  \(.Servico): USD \(.Custo | tonumber | . * 100 | round / 100)"' \
  -r

echo ""

# Custo por equipe (via tag Team)
echo "👥 Custo por equipe:"
aws ce get-cost-and-usage \
  --time-period Start=${MES_INICIO},End=${MES_FIM} \
  --granularity MONTHLY \
  --metrics BlendedCost \
  --group-by Type=TAG,Key=Team \
  --query 'ResultsByTime[0].Groups[*].{
    Equipe: Keys[0],
    Custo: Metrics.BlendedCost.Amount
  }' \
  --output json \
  | jq '.[] | "  \(.Equipe): USD \(.Custo | tonumber | . * 100 | round / 100)"' \
  -r

echo ""

# Anomalias de custo detectadas
echo "⚠️  Anomalias detectadas:"
aws ce get-anomalies \
  --date-interval StartDate=${MES_INICIO},EndDate=${MES_FIM} \
  --query 'Anomalies[*].{
    Servico: AnomalyScore.CurrentScore,
    ImpactoUSD: Impact.TotalImpact,
    Inicio: AnomalyStartDate
  }' \
  --output json \
  | jq '.[] | select(.ImpactoUSD > 100) |
    "  Impacto USD \(.ImpactoUSD | round) desde \(.Inicio)"' \
  -r

Alertas de Orçamento

# budget.tf

resource "aws_budgets_budget" "mensal" {
  name         = "${var.project_name}-${var.environment}-mensal"
  budget_type  = "COST"
  limit_amount = var.budget_mensal_usd
  limit_unit   = "USD"

  time_unit  = "MONTHLY"
  time_period_start = "2025-01-01_00:00"

  # Alertas em 70%, 90% e 100% do orçamento
  notification {
    comparison_operator        = "GREATER_THAN"
    threshold                  = 70
    threshold_type             = "PERCENTAGE"
    notification_type          = "FORECASTED"
    subscriber_email_addresses = [var.finops_email]
  }

  notification {
    comparison_operator        = "GREATER_THAN"
    threshold                  = 90
    threshold_type             = "PERCENTAGE"
    notification_type          = "FORECASTED"
    subscriber_email_addresses = [var.finops_email]
    subscriber_sns_topic_arns  = [aws_sns_topic.alertas_custo.arn]
  }

  notification {
    comparison_operator        = "GREATER_THAN"
    threshold                  = 100
    threshold_type             = "PERCENTAGE"
    notification_type          = "ACTUAL"
    subscriber_email_addresses = [var.finops_email]
    subscriber_sns_topic_arns  = [aws_sns_topic.alertas_custo.arn]
  }

  cost_filters = {
    TagKeyValue = ["user:Environment$${var.environment}"]
  }
}

# Detecção automática de anomalias de custo
resource "aws_ce_anomaly_monitor" "servicos" {
  name              = "${var.project_name}-monitor-anomalias"
  monitor_type      = "DIMENSIONAL"
  monitor_dimension = "SERVICE"
}

resource "aws_ce_anomaly_subscription" "alertas" {
  name      = "${var.project_name}-alertas-anomalias"
  frequency = "DAILY"

  monitor_arn_list = [aws_ce_anomaly_monitor.servicos.arn]

  subscriber {
    type    = "SNS"
    address = aws_sns_topic.alertas_custo.arn
  }

  # Alerta apenas se o impacto for maior que USD 50
  threshold_expression {
    dimension {
      key           = "ANOMALY_TOTAL_IMPACT_ABSOLUTE"
      values        = ["50"]
      match_options = ["GREATER_THAN_OR_EQUAL"]
    }
  }
}

Otimização de Compute: EC2, ECS e Lambda

Savings Plans e Reserved Instances

Para cargas de trabalho previsíveis — servidores de aplicação que rodam 24/7 — o uso de On-Demand é o tipo mais caro de compute. Duas alternativas oferecem descontos significativos:

Compute Savings Plans — compromisso de uso de uma quantidade de compute (em USD/hora) por 1 ou 3 anos, com desconto de até 66% sobre On-Demand. Aplicam-se automaticamente a EC2, Fargate e Lambda.

Reserved Instances — reserva de instâncias específicas com desconto de até 72% para comprometimento de 3 anos com pagamento antecipado.

# Identifica instâncias candidatas a Savings Plans
# Instâncias que rodam > 80% do tempo no último mês são boas candidatas
aws ce get-reservation-coverage \
  --time-period Start=$(date -d "30 days ago" +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity MONTHLY \
  --group-by Type=DIMENSION,Key=INSTANCE_TYPE \
  --query 'CoveragesByTime[0].Groups[*].{
    Tipo: Keys[0],
    Cobertura: Coverage.CoverageHours.CoverageHoursPercentage,
    OnDemandCusto: Coverage.CoverageHours.OnDemandHours
  }' \
  --output table

# Recomendações de Savings Plans da AWS
aws ce get-savings-plans-purchase-recommendation \
  --savings-plans-type COMPUTE_SP \
  --term-in-years ONE_YEAR \
  --payment-option NO_UPFRONT \
  --lookback-period-in-days THIRTY_DAYS \
  --query 'SavingsPlansPurchaseRecommendation.{
    PoupancaMensalEstimada: SavingsPlansDetails[0].EstimatedMonthlySavingsAmount,
    CompromissoHorario: SavingsPlansDetails[0].HourlyCommitmentToPurchase
  }'

Instâncias Spot para Cargas de Trabalho Tolerantes a Interrupção

Instâncias Spot oferecem desconto de até 90% sobre On-Demand. São adequadas para cargas que toleram interrupção — jobs de CI/CD, processamento em batch, workers de fila:

# spot-workers.tf

# Auto Scaling Group com mix de On-Demand e Spot
resource "aws_autoscaling_group" "workers" {
  name                = "${var.project_name}-${var.environment}-workers"
  vpc_zone_identifier = module.vpc.ids_subnets_privadas
  min_size            = 1
  max_size            = 20
  desired_capacity    = 2

  mixed_instances_policy {
    instances_distribution {
      # 20% On-Demand para baseline, 80% Spot para escala
      on_demand_base_capacity                  = 1
      on_demand_percentage_above_base_capacity = 20
      spot_allocation_strategy                 = "price-capacity-optimized"
    }

    launch_template {
      launch_template_specification {
        launch_template_id = aws_launch_template.worker.id
        version            = "$Latest"
      }

      # Múltiplos tipos de instância aumentam disponibilidade de Spot
      override {
        instance_type = "m6i.large"
      }
      override {
        instance_type = "m6a.large"
      }
      override {
        instance_type = "m5.large"
      }
      override {
        instance_type = "m5a.large"
      }
      override {
        instance_type = "m7i.large"
      }
    }
  }

  tag {
    key                 = "Name"
    value               = "${var.project_name}-${var.environment}-worker"
    propagate_at_launch = true
  }
}

# Handler de interrupção Spot — drena o worker antes da instância ser terminada
# A AWS notifica 2 minutos antes de interromper uma instância Spot
resource "aws_cloudwatch_event_rule" "spot_interruption" {
  name        = "spot-interruption-${var.environment}"
  description = "Detecta notificações de interrupção de instâncias Spot"

  event_pattern = jsonencode({
    source      = ["aws.ec2"]
    detail-type = ["EC2 Spot Instance Interruption Warning"]
  })
}

resource "aws_cloudwatch_event_target" "spot_interruption_lambda" {
  rule      = aws_cloudwatch_event_rule.spot_interruption.name
  target_id = "DrenaWorker"
  arn       = aws_lambda_function.drena_worker.arn
}

Rightsizing: Dimensionamento Correto de Instâncias

Um dos maiores desperdícios em cloud é o over-provisioning — instâncias dimensionadas para picos que raramente ocorrem. O AWS Compute Optimizer analisa métricas históricas e recomenda o tipo de instância ideal:

#!/bin/bash
# scripts/rightsizing-report.sh
# Gera relatório de recomendações de rightsizing

echo "=== Recomendações de Rightsizing — AWS Compute Optimizer ==="
echo ""

# Recomendações para instâncias EC2
echo "📦 Instâncias EC2 com recomendação de downsizing:"
aws compute-optimizer get-ec2-instance-recommendations \
  --filters Name=Finding,Values=Overprovisioned \
  --query 'instanceRecommendations[*].{
    Instancia: instanceName,
    AtualTipo: currentInstanceType,
    RecomendadoTipo: recommendationOptions[0].instanceType,
    PoupancaMensal: recommendationOptions[0].estimatedMonthlySavings.value,
    Moeda: recommendationOptions[0].estimatedMonthlySavings.currency,
    UsoMaxCPU: utilizationMetrics[?name==`CPU`].value | [0]
  }' \
  --output json \
  | jq '.[] | "\(.Instancia): \(.AtualTipo) → \(.RecomendadoTipo) | CPU max: \(.UsoMaxCPU)% | Economia: \(.Moeda) \(.PoupancaMensal)/mês"' \
  -r

echo ""

# Recomendações para funções Lambda
echo "λ Funções Lambda sub ou sobredimensionadas:"
aws compute-optimizer get-lambda-function-recommendations \
  --filters Name=Finding,Values=Overprovisioned \
  --query 'lambdaFunctionRecommendations[*].{
    Funcao: functionArn,
    MemoriaAtual: memorySizeRecommendationOptions[0].memorySize,
    MemoriaRecomendada: memorySizeRecommendationOptions[0].memorySize,
    Poupanca: memorySizeRecommendationOptions[0].projectedUtilizationMetrics[0].value
  }' \
  --output json \
  | jq '.[] | "\(.Funcao | split(":") | .[-1]): → \(.MemoriaRecomendada)MB"' \
  -r

Otimização de Banco de Dados

Identificando Queries Lentas com Performance Insights

#!/usr/bin/env python3
# scripts/analise-queries-lentas.py
# Identifica as queries mais custosas no RDS via Performance Insights

import boto3
import json
from datetime import datetime, timedelta, timezone

pi = boto3.client('pi', region_name='us-east-1')
rds = boto3.client('rds', region_name='us-east-1')

INSTANCIA = 'minha-api-production-db'
PERIODO_HORAS = 24

# Obtém o ResourceIdentifier da instância RDS
response = rds.describe_db_instances(
    DBInstanceIdentifier=INSTANCIA
)
resource_id = response['DBInstances'][0]['DbiResourceId']

fim = datetime.now(timezone.utc)
inicio = fim - timedelta(hours=PERIODO_HORAS)

# Busca as top 10 queries por tempo total de execução
response = pi.get_resource_metrics(
    ServiceType='RDS',
    Identifier=resource_id,
    StartTime=inicio,
    EndTime=fim,
    PeriodInSeconds=3600,
    MetricQueries=[
        {
            'Metric': 'db.load.avg',
            'GroupBy': {
                'Group': 'db.sql',
                'Dimensions': ['db.sql.statement'],
                'Limit': 10,
            }
        }
    ]
)

print(f"=== Top 10 Queries por Carga — Últimas {PERIODO_HORAS}h ===\n")

for i, key in enumerate(
    response['MetricList'][0].get('Keys', []), 1
):
    statement = key['Dimensions'].get('db.sql.statement', 'N/A')
    # Trunca queries longas para exibição
    if len(statement) > 120:
        statement = statement[:120] + '...'

    # Calcula a carga média
    valores = [
        p['Value'] for p in key.get('DataPoints', [])
        if 'Value' in p
    ]
    carga_media = sum(valores) / len(valores) if valores else 0

    print(f"{i}. Carga média: {carga_media:.2f} AAS")
    print(f"   Query: {statement}")
    print()

print("\n💡 Queries com carga > 1.0 AAS merecem análise de índices.")

Otimizando Leituras com Connection Pooling

// src/database/pool.js — configuração otimizada do pool de conexões
const { Pool } = require('pg');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: { rejectUnauthorized: true },

  // Dimensionamento do pool baseado na instância RDS
  // Regra geral: min(max_connections / num_pods, 10)
  max: parseInt(process.env.DB_POOL_MAX || '10'),
  min: parseInt(process.env.DB_POOL_MIN || '2'),

  // Tempo de idle antes de fechar conexão extra
  idleTimeoutMillis: 30000,

  // Timeout para obter conexão do pool
  connectionTimeoutMillis: 5000,

  // Timeout para queries individuais
  statement_timeout: 30000,   // 30 segundos
  query_timeout: 30000,

  // Keepalive para conexões de longa duração
  keepAlive: true,
  keepAliveInitialDelayMillis: 10000,
});

// Métricas do pool expostas para o Prometheus
const { Gauge } = require('prom-client');

const poolTotal = new Gauge({
  name: 'db_pool_total_connections',
  help: 'Total de conexões no pool',
});

const poolIdle = new Gauge({
  name: 'db_pool_idle_connections',
  help: 'Conexões ociosas no pool',
});

const poolWaiting = new Gauge({
  name: 'db_pool_waiting_clients',
  help: 'Clientes aguardando conexão disponível',
});

setInterval(() => {
  poolTotal.set(pool.totalCount);
  poolIdle.set(pool.idleCount);
  poolWaiting.set(pool.waitingCount);
}, 5000);

pool.on('error', (err) => {
  console.error(JSON.stringify({
    level: 'error',
    msg: 'Erro inesperado no pool de conexões',
    err: err.message,
  }));
});

module.exports = pool;

Otimização de Storage e Transferência de Dados

S3 Intelligent-Tiering e Lifecycle Policies

O storage é frequentemente o item de custo mais negligenciado — buckets crescem indefinidamente sem políticas de ciclo de vida:

# s3-lifecycle.tf

resource "aws_s3_bucket_lifecycle_configuration" "dados" {
  bucket = aws_s3_bucket.dados.id

  # Logs de aplicação — transitam para Glacier e são deletados
  rule {
    id     = "lifecycle-logs-aplicacao"
    status = "Enabled"

    filter {
      prefix = "logs/"
    }

    transition {
      days          = 30
      storage_class = "STANDARD_IA"  # Acesso infrequente após 30 dias
    }

    transition {
      days          = 90
      storage_class = "GLACIER_IR"   # Glacier Instant Retrieval após 90 dias
    }

    expiration {
      days = 365  # Deleta após 1 ano
    }

    noncurrent_version_expiration {
      noncurrent_days = 30  # Versões antigas deletadas em 30 dias
    }
  }

  # Backups — retidos mais tempo, transitam para Glacier Deep Archive
  rule {
    id     = "lifecycle-backups"
    status = "Enabled"

    filter {
      prefix = "backups/"
    }

    transition {
      days          = 7
      storage_class = "STANDARD_IA"
    }

    transition {
      days          = 30
      storage_class = "GLACIER"
    }

    transition {
      days          = 90
      storage_class = "DEEP_ARCHIVE"  # USD 0.00099/GB — mais barato possível
    }

    expiration {
      days = 2555  # 7 anos para compliance
    }
  }

  # Assets de frontend — Intelligent-Tiering gerencia automaticamente
  rule {
    id     = "lifecycle-assets-frontend"
    status = "Enabled"

    filter {
      prefix = "frontend/"
    }

    transition {
      days          = 0
      storage_class = "INTELLIGENT_TIERING"
    }
  }
}

# Configuração do Intelligent-Tiering para assets
resource "aws_s3_bucket_intelligent_tiering_configuration" "assets" {
  bucket = aws_s3_bucket.dados.id
  name   = "assets-inteligente"

  filter {
    prefix = "frontend/"
  }

  tiering {
    access_tier = "DEEP_ARCHIVE_ACCESS"
    days        = 180
  }

  tiering {
    access_tier = "ARCHIVE_ACCESS"
    days        = 90
  }
}

Reduzindo Custos de Transferência de Dados

Transferência de dados entre regiões AWS e para a internet é uma das fontes de custo mais surpreendentes. Estratégias para reduzir:

# vpc-endpoints.tf — acessa serviços AWS sem tráfego de internet

# Gateway Endpoint para S3 — gratuito, elimina tráfego para internet
resource "aws_vpc_endpoint" "s3" {
  vpc_id       = module.vpc.vpc_id
  service_name = "com.amazonaws.${var.aws_region}.s3"
  vpc_endpoint_type = "Gateway"

  route_table_ids = concat(
    module.vpc.ids_route_tables_privadas,
    module.vpc.ids_route_tables_publicas
  )

  tags = merge(local.tags_comuns, {
    Name = "${var.project_name}-${var.environment}-s3-endpoint"
  })
}

# Interface Endpoint para Secrets Manager
# Evita tráfego de internet — cobra por hora + GB processado
resource "aws_vpc_endpoint" "secrets_manager" {
  vpc_id              = module.vpc.vpc_id
  service_name        = "com.amazonaws.${var.aws_region}.secretsmanager"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = module.vpc.ids_subnets_privadas
  security_group_ids  = [aws_security_group.vpc_endpoints.id]

  tags = merge(local.tags_comuns, {
    Name = "${var.project_name}-${var.environment}-secretsmanager-endpoint"
  })
}

# Interface Endpoint para ECR — acelera pull de imagens em EKS/ECS
resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id              = module.vpc.vpc_id
  service_name        = "com.amazonaws.${var.aws_region}.ecr.api"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = module.vpc.ids_subnets_privadas
  security_group_ids  = [aws_security_group.vpc_endpoints.id]

  tags = local.tags_comuns
}

resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id              = module.vpc.vpc_id
  service_name        = "com.amazonaws.${var.aws_region}.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = module.vpc.ids_subnets_privadas
  security_group_ids  = [aws_security_group.vpc_endpoints.id]

  tags = local.tags_comuns
}

Dashboard FinOps no Grafana

Um dashboard dedicado a custos conecta métricas técnicas ao impacto financeiro:

{
  "dashboard": {
    "title": "FinOps — Custos e Eficiência",
    "panels": [
      {
        "title": "Custo por Requisição Atendida",
        "type": "stat",
        "description": "USD gasto por cada 1000 requisições processadas — métrica de eficiência de custo",
        "targets": [
          {
            "expr": "(aws_billing_estimated_charges / on()(sum(increase(http_requests_total[24h])) / 1000))",
            "legendFormat": "USD / 1k req"
          }
        ]
      },
      {
        "title": "Custo Estimado do Dia (Projeção Mensal)",
        "type": "timeseries",
        "targets": [
          {
            "expr": "aws_billing_estimated_charges",
            "legendFormat": "Custo acumulado USD"
          }
        ]
      },
      {
        "title": "Utilização de CPU dos Nós EKS",
        "description": "Baixa utilização indica oportunidade de rightsizing ou consolidação",
        "type": "gauge",
        "targets": [
          {
            "expr": "avg(100 - (avg by(node) (irate(node_cpu_seconds_total{mode='idle'}[5m])) * 100))",
            "legendFormat": "CPU média dos nós"
          }
        ],
        "thresholds": {
          "steps": [
            {"color": "red", "value": 0},
            {"color": "yellow", "value": 40},
            {"color": "green", "value": 60}
          ]
        }
      },
      {
        "title": "Memória Não Utilizada nos Nós",
        "type": "timeseries",
        "targets": [
          {
            "expr": "sum(node_memory_MemAvailable_bytes) / sum(node_memory_MemTotal_bytes) * 100",
            "legendFormat": "% memória livre nos nós"
          }
        ]
      }
    ]
  }
}

Custo do Pipeline de CI/CD

Os próprios pipelines de CI/CD têm um custo que cresce com a frequência de commits e o tamanho do time. Otimizações que reduzem o tempo de pipeline reduzem diretamente o custo de GitHub Actions ou do runner:

# Estratégias para pipelines mais eficientes

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Cache de node_modules — evita npm install em cada run
      - uses: actions/cache@v4
        id: cache-deps
        with:
          path: |
            ~/.npm
            node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Instala dependências (apenas se cache miss)
        if: steps.cache-deps.outputs.cache-hit != 'true'
        run: npm ci

      # Cache de camadas Docker — evita rebuild de camadas inalteradas
      - uses: actions/cache@v4
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

      - uses: docker/setup-buildx-action@v3

      - uses: docker/build-push-action@v5
        with:
          context: .
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
          tags: ghcr.io/empresa/api:${{ github.sha }}
          push: true

      # Rotaciona o cache — evita crescimento indefinido
      - name: Rotaciona cache do Buildx
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

Relatório Semanal de FinOps Automatizado

# .github/workflows/relatorio-finops-semanal.yml
name: Relatório Semanal de FinOps

on:
  schedule:
    - cron: '0 8 * * MON'  # Segunda-feira às 8h
  workflow_dispatch:

jobs:
  relatorio:
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_FINOPS_ROLE_ARN }}
          aws-region: us-east-1

      - name: Gera relatório de custos
        run: bash scripts/relatorio-custos.sh > relatorio.txt

      - name: Gera recomendações de rightsizing
        run: python3 scripts/analise-queries-lentas.py >> relatorio.txt

      - name: Publica no Slack
        uses: slackapi/slack-github-action@v1.26.0
        with:
          channel-id: ${{ vars.SLACK_FINOPS_CHANNEL }}
          payload: |
            {
              "blocks": [
                {
                  "type": "header",
                  "text": {
                    "type": "plain_text",
                    "text": "📊 Relatório Semanal de FinOps"
                  }
                },
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "```${{ steps.relatorio.outputs.summary }}```"
                  }
                },
                {
                  "type": "actions",
                  "elements": [
                    {
                      "type": "button",
                      "text": {"type": "plain_text", "text": "Ver no AWS Cost Explorer"},
                      "url": "https://console.aws.amazon.com/cost-management/home"
                    }
                  ]
                }
              ]
            }
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

O Que Vem a Seguir

O próximo artigo cobre Resiliência e Chaos Engineering — como construir sistemas que falham graciosamente, como usar o AWS Fault Injection Simulator para testar a resiliência em produção e como implementar os padrões de resiliência fundamentais: circuit breaker, retry com backoff exponencial e bulkhead.


Referências para Aprofundamento

FinOps e otimização de custos - FinOps Foundation — finops.org — Definição oficial do FinOps, incluindo o modelo de maturidade e as práticas recomendadas para times de engenharia e finanças. - AWS Cost Optimization Hub — docs.aws.amazon.com — Centro de otimização de custos da AWS com recomendações consolidadas de Savings Plans, rightsizing e recursos ociosos. - AWS Compute Optimizer — docs.aws.amazon.com — Documentação do Compute Optimizer com guia de interpretação das recomendações e integração com AWS Organizations.

Performance - AWS Performance Efficiency Pillar — docs.aws.amazon.com — Pilar de Eficiência de Performance do AWS Well-Architected Framework, cobrindo seleção de recursos, monitoramento e trade-offs de arquitetura. - Use the Right Tool — AWS Prescriptive Guidance — docs.aws.amazon.com — Guia prescritivo da AWS para otimização de performance em aplicações cloud-native

Comentários

Mais em DevOps

Artigo 28 — State do Terraform: Entendendo o Arquivo Mais Crítico do Projeto
Artigo 28 — State do Terraform: Entendendo o Arquivo Mais Crítico do Projeto

Artigo 28 — State do Terraform: Entendendo o Arquivo Mais Crítico do Projeto...

AIOps na Prática: O Que a IA Já Faz em Operações Hoje
AIOps na Prática: O Que a IA Já Faz em Operações Hoje

Poucas áreas da tecnologia acumulam tanto entusiasmo e tanta confusão simultâ...

Resiliência e Chaos Engineering
Resiliência e Chaos Engineering

Todo engenheiro que opera sistemas em produção por tempo suficiente aprende u...