Cómo bajé 42% la factura de AWS sin tocar la disponibilidad · Daniel Tinizaray

Cómo bajé 42% la factura de AWS sin tocar la disponibilidad · Daniel Tinizaray

Hace unos meses heredé la administración de una cuenta AWS con 12 instancias EC2, una RDS de producción y un entorno de staging que corría 24/7. La factura rondaba los $5,600 al mes y nadie podía decirme qué parte correspondía a qué servicio. En dos ciclos de iteración la bajé a $3,250: un 42% menos, con cero incidentes de disponibilidad. Este post es el registro de cómo lo hice, paso a paso, con los números reales.

El punto de partida: una factura sin dueño

Antes de tocar una sola instancia, medí. Lo primero que encontré no fue despilfarro, fue opacidad: el 38% del gasto mensual no tenía tags de proyecto ni de entorno. Sin eso, cualquier decisión de ahorro es una apuesta. La regla que me puse: no optimizo lo que no puedo atribuir.

Paso 1: tagging y Cost Explorer antes de tocar nada

Implementé un esquema de tags obligatorio (project, environment, owner, auto-shutdown) y verifiqué cobertura con el mismo CLI de AWS, agrupando la factura por tag:

# ¿Cuánto del gasto mensual tiene tag de proyecto?
aws ce get-cost-and-usage \
  --time-period Start=2026-07-01,End=2026-07-31 \
  --granularity MONTHLY \
  --metrics UnblendedCost \
  --group-by Type=TAG,Key=project

# Instancias sin tag 'project' (las primeras en regularizarse)
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[?!not_null(Tags[?Key==`project`].Value)] | [].InstanceId'

Tardé tres días en regularizarlo, aplicado vía Terraform para que no se degradara con el tiempo. Solo la visibilidad ya reveló dos candidatos obvios: un entorno de staging corriendo igual que producción y seis instancias con CPU promedio bajo el 15%.

Paso 2: Savings Plans para la carga que nunca se apaga

La base de datos y el tier de aplicación corren igual un domingo que un martes: carga estable y predecible. Para ese bloque comprometí un Compute Savings Plan de un año. El tramo estable de cómputo pasaba de ~$2,300/mes en on-demand a ~$960/mes con el compromiso: -$1,340 al mes sin cambiar una línea de código ni una instancia. La carga variable y el burst quedaron en on-demand a propósito: no quería un compromiso que penalizara flexibilidad futura.

Paso 3: right-sizing con datos de CloudWatch, no con corazonadas

Compute Optimizer me dio la lista corta, pero no confié en una recomendación sin verificar el percentil 95 de 30 días por instancia:

# p95 de CPU de 30 días antes de decidir cualquier downgrade
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-0abc123def456 \
  --start-time 2026-06-01T00:00:00Z \
  --end-time   2026-07-01T00:00:00Z \
  --period 86400 --statistics p95

Con p95 bajo el 40% de forma sostenida, moví seis instancias de m5.xlarge a t3.large vía Terraform, con validación de tipos aprobados para que nadie reintroduzca el sobre-dimensionamiento por accidente:

variable "app_instance_type" {
  description = "Tipo de instancia del tier de aplicación (aprobado en auditoría FinOps)"
  type        = string
  default     = "t3.large"

  validation {
    condition     = contains(["t3.large", "t3.xlarge", "m5.xlarge"], var.app_instance_type)
    error_message = "Tipo no aprobado: revisar auditoría FinOps antes de cambiarlo."
  }
}

Ese movimiento me ahorró $520 al mes. Aplicar cada cambio como diff de Terraform me dio una propiedad que no esperaba: cada downgrade quedó documentado, reversible y revisable en git.

Paso 4: apagar staging con EventBridge

El entorno de staging corría 730 horas al mes cuando se usaba unas 220. Desplegué una Lambda mínima disparada por EventBridge (apagado a las 19:00, encendido a las 07:00, lunes a viernes), filtrando por el tag que definí en el paso 1:

import boto3

def handler(event, context):
    ec2 = boto3.client("ec2")
    instances = ec2.describe_instances(
        Filters=[
            {"Name": "tag:auto-shutdown", "Values": ["true"]},
            {"Name": "instance-state-name", "Values": ["running"]},
        ],
    )
    ids = [i["InstanceId"]
           for r in instances["Reservations"]
           for i in r["Instances"]]
    if ids:
        ec2.stop_instances(InstanceIds=ids)
        print(f"Stopped: {ids}")

Eso recortó $490 al mes en staging. El tag auto-shutdown como control (y no una lista dura de IDs en la Lambda) significa que apagar un entorno nuevo es un cambio de Terraform de una línea.

Resultados

  • Factura mensual: $5,600 → $3,250 (-42%), unos $28,000 al año.
  • Desglose: Savings Plans -$1,340 · right-sizing -$520 · auto-shutdown -$490.
  • Disponibilidad: cero incidentes durante la transición; el p99 de la API quedó en ~180ms, sin variación medible respecto al periodo previo.
  • Gasto sin tag: de 38% a menos del 3% del total.

Lo que aprendí

  • El orden importa: tagging primero, ahorro después. Sin atribución, hubiera optimizado a ciegas y probablemente roto algo que nadie sabía que era crítico.
  • Nunca hice un downgrade sin el p95 de 30 días de CloudWatch delante. Compute Optimizer sugiere; la métrica confirma.
  • Hacer cada cambio como diff de IaC (Terraform) convirtió la optimización en un proceso auditable, no en una serie de clics en la consola.

Si quieres ver más de cómo trabajo infraestructura y costos, está todo documentado en danieltini.dev y en mi GitHub.


¿Te gustó este artículo?

Si estás lidiando con estos desafíos en tu empresa, hablemos. Sin compromiso. 30 minutos para entender tu situación.

Agenda una llamada gratuita →