Toda la evidencia
AWSFinOpsTerraform

Tratar el gasto en la nube como el uptime: alertas de costes en una AWS Organization

Resumen

Hicimos que el gasto en AWS de la organización fuera observable y generara alertas, igual que ya hacíamos con el uptime: umbrales, previsiones, enrutamiento y un responsable para cada alerta. Las alertas de previsión nos avisan ahora unos 9 días antes de que se supere un presupuesto.

El trabajo tiene dos capas. Una capa de alertas, construida como un módulo reutilizable de Terraform sobre AWS Budgets, nos dice cuándo el gasto va hacia donde no debería. Una capa de visibilidad, un método de auditoría que cruza los datos de facturación con el inventario de recursos, nos dice por qué.

Ese mismo enfoque condicionó después una decisión dentro de la propia observabilidad: si los logs de ECS van a CloudWatch Logs o a Loki a través de FireLens.

El reto

La AWS Organization tiene una cuenta de gestión y cuentas miembro. El uptime tenía alertas, dashboards y guardias. El gasto no tenía nada de eso: la primera señal de un problema de costes era la factura mensual, semanas después de que el dinero ya se hubiera ido.

Ese vacío tiene una forma predecible. Un NAT Gateway olvidado, un grupo de logs sin retención o un entorno de pruebas que nadie desmontó cuestan un poco cada día. Nada de eso es lo bastante llamativo como para verlo en la consola, y todo se va acumulando. Un mes, un entorno de pruebas de carga que se quedó encendido en una cuenta de staging añadió unos 1.100 $ a una factura de unos 9.500 $, y nadie se dio cuenta hasta que llegó la factura.

El objetivo era tratar el coste como una señal operativa: medido de forma continua, con alertas antes de superar el límite y con un responsable.

Restricciones

  1. Avisar antes, no después. Una alerta que salta cuando el presupuesto ya se ha gastado es un post-mortem, no una alerta. La previsión era un requisito imprescindible.
  2. Un módulo, múltiples cuentas. Los presupuestos tenían que definirse una vez en Terraform y replicarse por cuenta, servicio o etiqueta, no montarse a golpe de clic en la consola.
  3. Enrutamiento sin rehacer nada. Añadir un nuevo suscriptor, como un canal de chat o una herramienta de guardias, no debe implicar tocar ni recrear el presupuesto.
  4. Planes sin ruido. El módulo tenía que producir una salida limpia de terraform plan mes tras mes, para que el drift real siguiera siendo visible.
  5. Primero lo nativo de AWS. Usar Budgets, Cost Explorer, las APIs de etiquetado y SNS antes de recurrir a herramientas FinOps de terceros.

Arquitectura

El sistema tiene dos capas unidas por un único bucle de retroalimentación. La capa de alertas indica cuándo el gasto se desvía del plan; la capa de visibilidad indica por qué, y sus etiquetas vuelven a la capa de alertas como filtros de presupuesto.

Dos capas: alertas con AWS Budgets y SNS, visibilidad con Cost Explorer y la Tagging API

Todo lo relativo a las alertas está en Terraform. La auditoría se ejecuta contra las APIs de Cost Explorer y de etiquetado, y su resultado duradero es un esquema de etiquetas, no un informe.

La capa de alertas: un módulo de Budgets reutilizable

La capa de alertas es un único módulo de Terraform construido sobre AWS Budgets, instanciado por cuenta, por servicio o por etiqueta.

Por qué Budgets y no alarmas de facturación de CloudWatch

El enfoque clásico es una alarma de CloudWatch sobre la métrica EstimatedCharges. Tiene dos problemas. Las métricas de facturación solo existen en us-east-1, así que todas las alarmas tienen que vivir allí, se ejecuten donde se ejecuten las cargas de trabajo. Y la alarma solo salta cuando los cargos reales cruzan la línea; no tiene previsión.

AWS Budgets admite umbrales tanto reales como de previsión, filtra por servicio, etiqueta o cuenta vinculada, y la monitorización y las notificaciones básicas son gratuitas.

Umbrales: tres reales y uno de previsión

NotificaciónUmbralQué significa
Real50%Control a mitad de mes; sobre todo informativo
Real80%El gasto va por delante del plan; alguien debería mirarlo
Real100%Presupuesto superado
Previsión100%AWS prevé que se superará a final de mes; hay que actuar ya

La notificación de previsión es la que cambia el comportamiento. Convierte el coste en algo de lo que te avisan, no algo que revisas, mientras todavía hay tiempo para actuar.

Mes de ejemplo: la alerta de previsión salta mucho antes de que el gasto real supere el 100 %

Fan-out con SNS, y la política que la mayoría de módulos hacen mal

Budgets publica en un topic de SNS, y los suscriptores cuelgan de ese topic. Budgets no puede publicar en un topic a menos que la política del topic permita explícitamente el principal de servicio budgets.amazonaws.com. Sin ella, el presupuesto se crea sin problemas, la notificación queda configurada y nunca se entrega nada.

Poner el topic entre el presupuesto y sus suscriptores también los desacopla: una nueva lista de correo, un webhook de chat o una integración de guardias es una nueva suscripción, y el recurso del presupuesto no se toca nunca.

Alcance

El módulo acepta un mapa de filtros de coste, así que el mismo código cubre una cuenta vinculada entera, un único servicio como Amazon EC2 o una etiqueta de asignación de costes como un equipo o un entorno.

Una versión simplificada de los recursos principales:

data "aws_iam_policy_document" "billing_topic" {
  statement {
    sid       = "AllowBudgetsPublish"
    actions   = ["sns:Publish"]
    resources = [aws_sns_topic.billing.arn]

    principals {
      type        = "Service"
      identifiers = ["budgets.amazonaws.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "aws:SourceAccount"
      values   = [data.aws_caller_identity.current.account_id]
    }
  }
}

resource "aws_sns_topic_policy" "billing" {
  arn    = aws_sns_topic.billing.arn
  policy = data.aws_iam_policy_document.billing_topic.json
}

locals {
  notifications = [
    { type = "ACTUAL", threshold = 50 },
    { type = "ACTUAL", threshold = 80 },
    { type = "ACTUAL", threshold = 100 },
    { type = "FORECASTED", threshold = 100 },
  ]
}

resource "aws_budgets_budget" "this" {
  name              = var.name
  budget_type       = "COST"
  limit_amount      = var.limit_amount
  limit_unit        = "USD"
  time_unit         = "MONTHLY"
  time_period_start = "2024-01-01_00:00" # fijado en el pasado, ver compromisos

  dynamic "cost_filter" {
    for_each = var.cost_filters # p. ej. { LinkedAccount = ["111111111111"] }
    content {
      name   = cost_filter.key
      values = cost_filter.value
    }
  }

  dynamic "notification" {
    for_each = local.notifications
    content {
      comparison_operator       = "GREATER_THAN"
      threshold                 = notification.value.threshold
      threshold_type            = "PERCENTAGE"
      notification_type         = notification.value.type
      subscriber_sns_topic_arns = [aws_sns_topic.billing.arn]
    }
  }

  depends_on = [aws_sns_topic_policy.billing]
}

La capa de visibilidad: la facturación se cruza con el inventario

Las alertas dicen que el gasto se ha desviado, pero no por qué. El método de auditoría que responde a eso lee dos fuentes y las compara.

  • Lo que pagas: Cost Explorer y su API, agrupados por cuenta, servicio y tipo de uso.
  • Lo que existe: la Resource Groups Tagging API, que lista los recursos etiquetados en todos los servicios y regiones.

Ninguna de las dos vistas basta por sí sola, porque AWS factura por uso, no por inventario. La transferencia de datos, el procesamiento del NAT y la ingesta de logs aparecen en la factura, pero no son recursos que se puedan listar. Un volumen sin asociar o un balanceador de carga inactivo es un recurso, pero su coste se diluye en una línea de servicio que parece normal. Los hallazgos útiles están donde las dos vistas no coinciden.

Facturación e inventario se solapan; el despilfarro se esconde donde hay recursos que existen y facturan pero no tienen responsable

Los sospechosos habituales

DespilfarroPor qué pasa desapercibidoCómo lo encontramosSolución
NAT Gateways inactivosCargo por hora más procesamiento, facturado como EC2-OtherTipo de uso NatGateway-Hours con poco NatGateway-BytesEliminarlos, o compartir uno por VPC donde no haga falta alta disponibilidad
Volúmenes EBS sin asociarSe quedan atrás cuando se terminan las instanciasVolúmenes en estado availableSnapshot si hace falta y después borrarlos
Elastic IPs huérfanasLas direcciones IPv4 públicas se facturan por horaDirecciones sin asociaciónLiberarlas
Buckets S3 sin políticas de ciclo de vidaLos objetos antiguos y las subidas multipart incompletas siguen acumulándoseBuckets sin configuración de ciclo de vidaReglas de expiración y transición
Grupos de logs sin retenciónLa retención por defecto es indefinidaGrupos de logs con la retención configurada para no caducar nuncaDefinir la retención de cada grupo de logs

Las etiquetas como gobernanza continua

La auditoría es una foto fija; las etiquetas mantienen la imagen al día. Un conjunto pequeño y obligatorio de etiquetas de asignación de costes abarata la respuesta a cualquier pregunta futura: ¿de qué equipo es esto y es de producción? Las etiquetas activadas también se convierten en filtros de presupuesto en el módulo anterior, así que un equipo puede tener su propio presupuesto sin necesidad de una cuenta nueva.

El esquema tiene cuatro claves: owner, environment, service y cost-center. Los default_tags del provider de Terraform las aplican a todo lo que creamos, una tag policy de AWS Organizations informa de los recursos que no cumplen, y una comprobación en CI hace fallar cualquier plan que fuera a crear un recurso sin etiquetas.

La observabilidad también tiene una curva de costes

En cuanto el gasto es visible, empieza a condicionar las decisiones sobre la propia observabilidad. El caso más claro fue a dónde envían sus logs los servicios de ECS.

  • CloudWatch Logs no requiere infraestructura propia y se integra de forma nativa con ECS. Su coste es casi por completo por GB ingerido, así que crece de forma lineal con el volumen de logs.
  • FireLens hacia Loki envía los logs a través de un sidecar de Fluent Bit hasta Loki, que ya funciona como parte de nuestro stack LGTM y guarda los chunks en S3. Su coste es mayoritariamente fijo: lo que ocupa Loki, el almacenamiento de objetos barato y un pequeño peaje de CPU y memoria por tarea para el sidecar.

Con poco volumen, CloudWatch es más barato y más sencillo. A partir de cierto volumen de ingesta diaria, el coste fijo de Loki es menor que la factura por GB. Tomamos la decisión servicio a servicio según ese punto de cruce, no como una regla general.

El coste de CloudWatch Logs crece con el volumen; Loki es mayoritariamente fijo; la elección cambia en el punto de cruce

El punto de cruce quedó en unos 3 GB de logs por servicio y día. Con el precio de ingesta de Fráncfort, de unos 0,63 $ por GB, eso son unos 57 $ al mes por servicio en CloudWatch, frente a unos pocos dólares de sidecar y S3 en un stack de Loki que ya teníamos en marcha. Tres de nuestros 12 servicios de ECS estaban por encima de esa línea y pasaron a FireLens; los otros nueve se quedaron en CloudWatch Logs.

La clase de logs Infrequent Access de CloudWatch reduce la diferencia para los logs que apenas se consultan, a cambio de perder funciones como los filtros de métricas. Vale la pena calcular su coste como tercera opción antes de migrar un servicio.

Decisiones clave y compromisos

Dónde viven los presupuestos

Los presupuestos pueden estar centralizados en la cuenta de gestión, uno por cuenta miembro mediante un filtro LinkedAccount, o dentro de cada cuenta miembro. Los presupuestos centralizados ofrecen un único lugar para ver la cobertura y un único estado de Terraform, pero los equipos de las cuentas miembro no pueden verlos. Los presupuestos por cuenta ponen la responsabilidad donde se produce el gasto, a costa de múltiples despliegues que hay que mantener coherentes.

El precio no es el factor decisivo. Los presupuestos que solo se usan para monitorizar y notificar son gratuitos; solo se cobran los presupuestos con acciones, a partir del tercero.

Usamos un modelo híbrido. Cada cuenta miembro tiene un presupuesto centralizado en la cuenta de gestión con un filtro LinkedAccount, lo que da cobertura completa desde un único estado de Terraform. Las tres cuentas de producción tienen además sus propios presupuestos por servicio, a cargo de los equipos que gestionan esos servicios.

El retraso de las etiquetas

Una etiqueta de asignación de costes no sirve de nada en el momento en que la añades. Una nueva clave de etiqueta puede tardar hasta 24 horas en estar disponible para activarse en la cuenta de gestión, y unas 24 horas más después de activarla en aparecer en Cost Explorer y en los filtros de presupuesto. Por eso los presupuestos filtrados por etiqueta empiezan a ciegas. La cuenta de gestión puede aplicar la activación de etiquetas con carácter retroactivo hasta doce meses, lo que recupera el historial, pero no las alertas que no llegaron.

Umbrales fijos frente a Cost Anomaly Detection

Los umbrales fijos responden a una sola pregunta: ¿vamos a superar el plan? Se les escapa un pico que se queda por debajo del presupuesto, como un servicio cuyo coste diario se triplica en un mes en el que la cuenta acaba igualmente en el 70 %. Cost Anomaly Detection modela el gasto normal por servicio o por cuenta y señala las desviaciones con independencia del presupuesto. Ambos se complementan: los presupuestos para el plan, la detección de anomalías para la forma.

Drift: fijar time_period_start

Si time_period_start se deja sin definir, AWS lo rellena con el inicio del periodo actual. Al mes siguiente, Terraform ve un valor distinto y propone un cambio en cada plan. Fijarlo en una fecha pasada mantiene los planes limpios, de modo que un diff en un presupuesto siempre significa que algo ha cambiado.

Qué falló

  • Notificaciones silenciosas. Un presupuesto con una notificación que apunta a un topic de SNS parece completo tanto en Terraform como en la consola. Sin la política del topic para budgets.amazonaws.com, no se entrega nada y no aparece ningún error. La corrección se incorporó al módulo, con un depends_on explícito para que la política exista antes que el presupuesto.
  • Un plan que nunca se estabilizaba. Dejar time_period_start en manos de AWS hacía que cada nuevo mes generara un diff en todos los presupuestos. Fijar la fecha lo resolvió, pero solo después de que el ruido hubiera acostumbrado a la gente a pasar por alto los diffs de los presupuestos.
  • Presupuestos por etiqueta que marcaban cero. Los presupuestos filtrados por una etiqueta recién activada no mostraban gasto durante el primer día o los dos primeros días. No pasaba nada; simplemente la etiqueta todavía no estaba en los datos de facturación.

Ninguno de los tres costó dinero directamente, pero el primero hizo que nuestros primeros presupuestos no enviaran alertas, sin que nadie lo supiera, durante unas dos semanas.

Resultados

Ahora el coste se comporta como cualquier otra señal de producción: tiene umbrales, previsión, una ruta de alertas y un responsable.

MedidaResultadoCómo se midió
Antelación de las alertas de previsiónDe 6 a 12 días, unos 9 de mediaFecha de la notificación de previsión frente a fecha en que el gasto real superó el 100 %
Cobertura de presupuestosTodas las cuentasCuentas con al menos un presupuesto activo y un suscriptor que funciona
Incidentes detectados por las alertas3 en los cuatro primeros mesesHistorial de alertas del topic de SNS
Coste de las propias alertasGratisLos presupuestos solo de notificación no tienen coste en AWS

El caso más claro fue en una cuenta de staging, una cuenta pequeña dentro de la organización. Tras un incidente, se quedó activado el nivel de log debug en un servicio de ECS, y la ingesta en CloudWatch Logs pasó de menos de 1 GB a unos 40 GB al día. El día 9 del mes saltó la alerta de previsión de esa cuenta en el canal compartido, y esa misma tarde se restableció el nivel de log. Si se hubiera dejado hasta la factura, habría añadido unos 550 $ al mes.

Qué haría de otra manera

  • Enviar las alertas de presupuesto a Mattermost desde el primer día. Las alertas de seguridad de CloudTrail ya llegan a Mattermost. Poner las alertas de costes en el mismo sitio, como un suscriptor más de SNS, las convertiría en parte de la imagen operativa diaria en lugar de en una carpeta de correo.
  • Activar Cost Anomaly Detection junto con los presupuestos. Los umbrales fijos eran el primer paso adecuado, pero no pueden ver un pico que se queda por debajo del plan.
  • Configurar pronto Cost and Usage Reports con Athena. Cost Explorer responde a las preguntas habituales; CUR con Athena responde a las incómodas, como el coste por tarea o por cliente, con SQL y todo el historial.
  • Acordar el esquema de etiquetas antes de la auditoría. Por el retraso de las etiquetas, cada día sin etiquetas activadas es un día de gasto que luego no podrás atribuir sin un proceso retroactivo.