Módulo 6: Finops For Tokens

5. Tagging de asignación de costo para la carga de IA

Descripción

La lección 3 dejó un FAIL real, deliberado y sin resolver: bedrock-budget.rego exige Workload=GenAIExtraction sobre aws_bedrock_guardrail, y bedrock.tf todavía no lo declara. Esta lección cierra ese FAIL con HCL real — un local nuevo, ai_workload_tags, aplicado a los dos recursos que esta guía declaró para la carga de IA (el guardrail y BedrockManifestExtractorRole). En el camino, correr -p cost-policy/ completo por primera vez en este proyecto expone un segundo hallazgo real, más antiguo, que esta lección también cierra: common_tags nunca llevó CostCenter/Owner, los dos tags que finops-and-cost-guardrails-guide ya estableció como parte de la taxonomía completa de Andes Cargo.

Conexión con el módulo

Esta es la lección donde el FAIL de la lección 3 se convierte, con evidencia real, en PASS. Es también la primera vez que este módulo corre cost-policy/ como biblioteca completa (los dos archivos juntos), no un archivo a la vez — el mismo momento que el Módulo 5, lección 3 de esta guía ya vivió con policy/ completo.


Analogía: la etiqueta de vuelo, no solo el nombre del dueño

finops-and-cost-guardrails-guide ya comparó los cuatro tags genéricos (Project, Environment, CostCenter, Owner) con un sistema de dirección postal completo: nombre de familia, código de zona, contacto responsable. Workload es una pieza más específica todavía — piensa en la etiqueta de vuelo que se le pone a una maleta en un aeropuerto con conexiones: no reemplaza la etiqueta de nombre del dueño (Project/Owner siguen ahí), agrega el número exacto de vuelo al que esa maleta específica pertenece, para que el sistema de manejo de equipaje sepa, sin ambigüedad, a qué itinerario corresponde cuando hay decenas de maletas del mismo dueño viajando en distintos vuelos el mismo día. Workload=GenAIExtraction es esa etiqueta de vuelo — identifica, sin ambigüedad, que este recurso específico pertenece a esta carga de trabajo específica, aunque el resto de la infraestructura comparta el mismo Project.


Paso 1 — ai_workload_tags, un local nuevo, con alcance acotado

En locals.tf, junto a common_tags y manifest_extractor_model_arn (Módulo 3):

locals {
  common_tags = {
    Project     = "andes-cargo"
    Environment = var.environment
    ManagedBy   = "terraform"
    # Module 6, lesson 5: CostCenter/Owner complete the four-tag taxonomy
    # finops-and-cost-guardrails-guide, Module 4, lesson 3 already declared as
    # canonical for Andes Cargo (LOGISTICS-001 / platform-team@andescargo.io) --
    # values cited verbatim from that lesson, not invented here.
    CostCenter  = "LOGISTICS-001"
    Owner       = "platform-team@andescargo.io"
  }

  manifest_extractor_model_arn = "arn:aws:bedrock:${var.aws_region}::foundation-model/amazon.nova-lite-v1:0"

  # Module 6, lesson 5: common_tags plus the one tag specific to this workload.
  # Applied only to the two resources this guide's Terraform declares for the AI
  # workload (the guardrail and the role) -- never merged into common_tags itself,
  # which every other Andes Cargo resource (the S3 bucket, the DynamoDB table, the
  # deterministic Lambda) also uses and has nothing to do with GenAI.
  ai_workload_tags = merge(local.common_tags, {
    Workload = "GenAIExtraction"
  })
}

Dos cambios, con dos motivos distintos. Primero, CostCenter/Owner se agregan a common_tags — no porque este módulo lo pida, sino porque son parte de la taxonomía de cuatro tags que finops-and-cost-guardrails-guide Módulo 4, lección 3 ya fijó como obligatoria para todo recurso facturable de Andes Cargo, y common_tags de este proyecto nunca los había llevado desde que el Módulo 1 de esta guía lo declaró — un hallazgo real, no una invención de este módulo, expuesto en el Paso 3. Segundo, ai_workload_tags es un local nuevo, no una modificación a common_tags: merge(local.common_tags, { Workload = "GenAIExtraction" }) produce un mapa que incluye los cinco tags —los cuatro genéricos más Workload—, pero solo se aplica a los recursos que lo referencian explícitamente, nunca a todo el proyecto por herencia automática.


Paso 2 — Aplicando ai_workload_tags, en bedrock.tf

Dos líneas cambian, ambas de tags = local.common_tags a tags = local.ai_workload_tags:

 module "manifest_extractor_guardrail" {
   source = "./modules/bedrock-guardrail"
   # ... (argumentos sin cambios)
-  tags = local.common_tags
+  tags = local.ai_workload_tags
 }
 module "bedrock_manifest_extractor_role" {
   source = "./modules/iam-role"
   role_name               = "BedrockManifestExtractorRole"
   trust_policy_json       = data.aws_iam_policy_document.bedrock_manifest_extractor_trust.json
   permissions_policy_json = data.aws_iam_policy_document.bedrock_manifest_extractor_permissions.json
-  tags                    = local.common_tags
+  tags                    = local.ai_workload_tags
 }

Fíjate en un detalle importante de alcance: BedrockManifestExtractorRole también recibe ai_workload_tags, aunque bedrock-budget.rego (lección 3) nunca evalúa recursos IAM — la misma razón exacta que finops-and-cost-guardrails-guide Módulo 4, lección 3 ya estableció para CostCenter/Owner en roles: IAM no factura, así que ninguna política de asignación de costo necesita revisarlo. Etiquetar el rol de todas formas con Workload no es incorrecto —es consistencia general del proyecto, útil para cualquiera que audite manualmente "qué pertenece a esta carga de IA"—, pero la política automatizada, con toda intención, solo lo exige sobre el recurso que sí puede generar gasto.


Paso 3 — terraform plan real, y el primer PASS de la biblioteca completa

terraform validate -no-color
terraform plan -input=false -no-color -out=tfplan
terraform show -json tfplan > tfplan.json

Qué esperar (literal — ejecutado para escribir esta lección):

Success! The configuration is valid.

Plan: 17 to add, 0 to change, 0 to destroy.

Diecisiete recursos, el mismo número que el Módulo 5 de esta guía ya confirmó — ningún recurso nuevo, solo un cambio de tags sobre dos que ya existían. Ahora, la política que dejó el FAIL en la lección 3:

conftest test tfplan.json -p cost-policy/bedrock-budget.rego

Qué esperar (literal):

2 tests, 2 passed, 0 warnings, 0 failures, 0 exceptions

PASS real — el FAIL de la lección 3 queda cerrado. Y, por primera vez en este módulo, la biblioteca completa de cost-policy/, los dos archivos juntos:

conftest test tfplan.json -p cost-policy/

Qué esperar (literal — ejecutado para escribir esta lección, contra el proyecto completo con CostCenter/Owner ya completos):

3 tests, 3 passed, 0 warnings, 0 failures, 0 exceptions

Tres tests, no dos: una regla de required-cost-tags.rego (ahora pasando limpio, gracias a CostCenter/Owner agregados en el Paso 1) más las dos de bedrock-budget.rego. Las tres pasan, en una sola corrida, sobre el mismo plan que un job cost-tags real evaluaría antes de dejar avanzar un merge — la lección 8 de este módulo corre exactamente este comando dentro de ese job, con act pull_request, de punta a punta.


El hallazgo honesto: por qué CostCenter/Owner no estaban, y por qué cerrarlo aquí es correcto

Vale la pena decir esto con precisión, sin esconderlo: antes de esta lección, conftest test tfplan.json -p cost-policy/ (el directorio completo) habría mostrado seis FAIL — dos tags faltantes (CostCenter, Owner) sobre cada uno de los tres recursos facturables clásicos (el bucket S3, la tabla DynamoDB, la función Lambda determinista). Esto no es un problema de esta guía específicamente, ni de la carga de IA — es un supuesto que common_tags, declarado desde el Módulo 1 de esta misma guía, nunca terminó de completar contra la taxonomía que finops-and-cost-guardrails-guide ya fijó como obligatoria. Como esta lección es la primera vez que este módulo corre -p cost-policy/ (el directorio entero, no solo bedrock-budget.rego), es también la primera vez que ese hallazgo se vuelve visible — y, siguiendo la misma disciplina de honestidad que cada lección de este ecosistema aplica, se cierra aquí mismo, no se ignora ni se pospone. CostCenter=LOGISTICS-001 y Owner=platform-team@andescargo.io son los valores exactos, citados textualmente, de finops-and-cost-guardrails-guide Módulo 4, lección 3 — nunca inventados para esta lección.


Errores comunes

Agregar Workload directamente a common_tags, en vez de crear ai_workload_tags (el error que el Ejercicio 3 de la lección 3 ya anticipó). Qué pasa: alguien, buscando el camino más corto, escribe Workload = "GenAIExtraction" dentro de common_tags. Cómo detectarlo: si el bucket S3 o la tabla DynamoDB aparecen con Workload=GenAIExtraction en el plan, aunque no tengan nada que ver con Bedrock. Cómo corregirlo: Workload vive únicamente en ai_workload_tags, un local separado, aplicado explícitamente solo a manifest_extractor_guardrail y bedrock_manifest_extractor_role — nunca fusionado en common_tags, que todo el proyecto usa.

Corregir el FAIL de la lección 3 pero no volver a correr -p cost-policy/ completo, y perderse el hallazgo de CostCenter/Owner (de verificación parcial). Qué pasa: alguien corre solo conftest test tfplan.json -p cost-policy/bedrock-budget.rego, ve PASS, y da la lección por terminada sin correr la biblioteca completa. Cómo detectarlo: si nunca viste los seis FAIL de required-cost-tags.rego en ningún momento de tu propio recorrido. Cómo corregirlo: el Paso 3 de esta lección corre ambos comandos a propósito — verificar solo el archivo nuevo esconde un hallazgo real que solo aparece al correr el directorio completo, exactamente el tipo de omisión que este módulo entero está diseñado para prevenir.

Copiar LOGISTICS-001/platform-team@andescargo.io de memoria, sin verificar contra la fuente citada, e introducir una inconsistencia de mayúsculas o formato. Qué pasa: alguien escribe Logistics-001 o platformteam@andescargo.io, con una diferencia sutil de formato. Cómo detectarlo: si tu valor no coincide, carácter por carácter, con finops-and-cost-guardrails-guide Módulo 4, lección 3. Cómo corregirlo: copia el valor exacto de la fuente citada — la misma disciplina de consistencia que esa misma lección ya advirtió como error común para CostCenter, ahora aplicada aquí, un módulo y una guía después.


Ejercicios

Ejercicio 1 — Predice si BedrockManifestExtractorRole, con ai_workload_tags aplicado, dispararía algún FAIL de required-cost-tags.rego. Basándote en el alcance de esa política (finops-and-cost-guardrails-guide Módulo 4, lección 3), justifica tu respuesta.

Ver solución

No dispararía ningún FAIL, y por dos razones independientes que refuerzan la misma conclusión: primero, required-cost-tags.rego limita su billable_resource_types a aws_s3_bucket, aws_lambda_function y aws_dynamodb_tableaws_iam_role/aws_iam_role_policy (los tipos reales que declara BedrockManifestExtractorRole) nunca aparecen en ese conjunto, así que la política ni siquiera evalúa ese recurso. Segundo, aunque lo evaluara, ai_workload_tags ya incluye los cuatro tags genéricos completos (vía merge(local.common_tags, ...)), así que pasaría de todas formas. El resultado es el mismo por ambas razones, pero solo la primera es la causa real.

Ejercicio 2 — Calcula cuántos tests totales esperarías de conftest test tfplan.json -p cost-policy/ si, en el futuro, se agregara una tercera política a cost-policy/ con dos reglas propias. Usa el conteo del Paso 3 de esta lección como base.

Ver solución

Cinco: los tres actuales (3 tests, 3 passed, confirmado en el Paso 3 de esta lección) más las dos reglas del archivo hipotético nuevo. conftest -p <directorio> suma las reglas de todos los archivos .rego que encuentra, sin importar cuántos haya — el mismo comportamiento aditivo que el Ejercicio 1 del Módulo 5, lección 3 de esta guía ya practicó contando las reglas de cuatro archivos en policy/.

Ejercicio 3 — Un compañero pregunta por qué CostCenter/Owner se cierran en una lección de este módulo (GenAI), en vez de en una lección de finops-and-cost-guardrails-guide directamente. Explica, con el argumento de "el hallazgo se vuelve visible donde se corre el comando por primera vez", por qué esta lección es el lugar correcto para cerrarlo.

Ver solución

finops-and-cost-guardrails-guide escribió required-cost-tags.rego y, en su propio recorrido, corrió -p cost-policy/ contra su propia copia del proyecto — en ese momento, sin la carga de IA todavía, es razonable asumir que esa guía ya lo cerró correctamente en su propio contexto. Esta guía, al construir su propia copia del proyecto desde el Módulo 1, heredó common_tags sin volver a verificar contra la taxonomía completa de cuatro tags hasta este momento específico — la primera vez que esta guía corre -p cost-policy/ completo. El hallazgo es real independientemente de dónde se originó; cerrarlo aquí, en el momento exacto en que se vuelve visible por primera vez en este proyecto, es la misma disciplina de "nunca esconder un resultado real" que cada lección de este ecosistema exige — posponerlo a una guía distinta, ya cerrada, no sería una opción real.


Resumen y siguiente paso

Esta lección cerró el FAIL de la lección 3: ai_workload_tags, un local nuevo con alcance acotado a dos recursos, aplicado a manifest_extractor_guardrail y bedrock_manifest_extractor_role. Confirmaste, con terraform plan y conftest reales, que bedrock-budget.rego pasa de FAIL a PASS (2 tests, 2 passed), y que la biblioteca completa de cost-policy/ pasa limpia por primera vez (3 tests, 3 passed) — después de cerrar, en el camino, un segundo hallazgo real y honesto: CostCenter/Owner, ausentes de common_tags desde el Módulo 1, ahora completos con los valores exactos que finops-and-cost-guardrails-guide ya estableció.

Antes de avanzar deberías poder: explicar por qué ai_workload_tags es un local separado, nunca fusionado en common_tags; correr conftest -p cost-policy/ y predecir el número exacto de tests; y explicar por qué BedrockManifestExtractorRole recibe Workload aunque ninguna política automatizada lo exija ahí.

La lección 6 vuelve a la calculadora de la lección 4: un cambio de volumen deliberadamente desproporcionado en GENAI-COST-PROFILE.md, detenido por el step del pipeline antes de llegar a ningún apply.

Recursos

  1. finops-and-cost-guardrails-guide, Módulo 4, lección 3 — la fuente exacta de los valores LOGISTICS-001/platform-team@andescargo.io, citados aquí sin inventar ninguno nuevo.
  2. Terraform Docs — la función merge() — la función usada para construir ai_workload_tags sin duplicar los cuatro tags genéricos.
  3. Este curso, Módulo 5, lección 3, Paso 5 — el precedente exacto de "correr la biblioteca completa por primera vez, no solo el archivo nuevo", reaplicado aquí a cost-policy/.
  4. Este módulo, lección 3 — el origen del FAIL que esta lección cierra.