Blog de Amazon Web Services (AWS)
Pruebas de resiliencia sin código con AWS Step Functions y AWS Fault Injection Service
Por Oriol Matavacas, Sr. Solutions Architect en AWS.
Nivel: 300 – DevOps, SRE y equipos de plataforma. Serverless
Introducción
La mayoría de los equipos sabe que debería probar la resiliencia de sus servicios de forma continua. Aun así, esas pruebas rara vez llegan al pipeline de CI/CD. El motivo no suele ser la falta de voluntad, sino el coste de mantenimiento del código de integración.
Ese código incluye funciones para descubrir la topología del servicio, construir plantillas de experimento, sondear métricas y decidir si el resultado es aceptable. Con el tiempo, acumula dependencias, se rompe con cada cambio de API y la prueba de resiliencia termina ejecutándose de forma manual y esporádica.
En esta publicación, aprenderá cómo una máquina de estados declarativa reemplaza ese código. Una máquina de estados de AWS Step Functions orquesta el flujo completo – descubrimiento, validación, inyección progresiva de fallos y veredicto – utilizando únicamente integraciones directas de servicio. AWS Fault Injection Service (AWS FIS) ejecuta el experimento real y Amazon Bedrock genera y evalúa los escenarios. El resultado es una prueba reproducible que su pipeline de CI/CD puede invocar para decidir si un despliegue avanza o se detiene.
Descripción de la solución
La solución se define en un único documento en Amazon States Language (ASL), con expresiones en JSONata. La máquina de estados sigue un flujo progresivo que va de lo general a lo concreto:
- Descubrimiento: Consulta el estado real del servicio y de los recursos en los que se apoya, para establecer una línea base. En este ejemplo, un servicio de Amazon ECS sobre AWS Fargate, esto incluye el recuento de tareas, la distribución entre zonas de disponibilidad, el estado del balanceador y las métricas de referencia.
- Validación: Comprueba que el servicio cumple los requisitos para la inyección de fallos.
- Generación: Amazon Bedrock propone un plan de tres escenarios ordenados por severidad.
- Barrera de seguridad: Filtra el plan contra una lista de acciones permitidas.
- Ejecución progresiva: Ejecuta los escenarios uno a uno, del más conservador al más agresivo.
- Evaluación: Amazon Bedrock valora los resultados, asigna una puntuación de alta disponibilidad (haScore) y emite un veredicto determinista.
Cada pieza usa una integración nativa: AWS Step Functions llama a Amazon ECS, AWS FIS, Amazon CloudWatch y Elastic Load Balancing (el servicio que administra el Application Load Balancer del servicio) mediante integraciones con el SDK de AWS, y a Amazon Bedrock mediante su integración optimizada. Al no depender de código intermedio, la solución se reduce a un único documento JSON que se puede versionar y auditar como un todo. La transformación de datos, la validación y el sondeo se expresan directamente en la definición del estado. Para una visión general de las integraciones con el SDK de AWS disponibles en Step Functions, consulte Uso de integraciones de servicio del SDK de AWS. Para la integración optimizada con Amazon Bedrock, consulte Invocar y personalizar modelos de Amazon Bedrock con Step Functions. La Figura 1 muestra el grafo completo de la máquina de estados.
Figura 1: Arquitectura de la solución.
Requisitos previos
Para implementar esta solución, necesita lo siguiente:
- Una cuenta de AWS con AWS Step Functions, AWS FIS, Amazon ECS y Amazon CloudWatch en la región donde reside el servicio que va a probar (AWS FIS solo actúa sobre recursos de su propia región; los ejemplos usan eu-west-1). Amazon Bedrock puede invocarse desde otra región, por ejemplo mediante un perfil de inferencia entre regiones. La sección de disponibilidad regional detalla otras regiones compatibles.
- Un servicio de Amazon ECS sobre AWS Fargate en ejecución, con al menos dos tareas repartidas en dos zonas de disponibilidad y con tráfico distribuido a través de un Application Load Balancer (administrado por Elastic Load Balancing).
- La inyección de fallos habilitada en la definición de tarea, con
"enableFaultInjection": true. El campopidModeen task no es estrictamente necesario para las dos acciones de esta solución (aws:ecs:stop-taskyaws:network:disrupt-connectivity), pero se recomienda configurarlo desde el inicio. Esto evita tener que actualizar la definición de tarea y redesplegar el servicio si más adelante se amplían las acciones a experimentos que actúan dentro del contenedor (comoaws:ecs:task-kill-processoaws:ecs:task-cpu-stress), ya que estas sí requierenpidMode: task. Para más detalles sobre la configuración de inyección de fallos en tareas de Amazon ECS, consulte Inyección de fallos en Amazon ECS. - Acceso al modelo de Amazon Bedrock indicado en la entrada (por ejemplo, Claude Sonnet 4.5 de Anthropic). Para modelos de Anthropic como el usado en esta publicación, es necesario completar una vez el formulario de primer uso (First Time Use) en la consola de Amazon Bedrock antes de la primera invocación. Los modelos de Amazon Bedrock de otros proveedores se habilitan automáticamente al invocarlos por primera vez.
- Un rol de experimento de AWS FIS con permisos para actuar sobre las tareas de Amazon ECS y las subredes de destino.
- Una alarma de Amazon CloudWatch configurada como condición de parada del experimento.
- Roles de IAM que concedan a la máquina de estados permiso para invocar cada servicio. La sección de permisos IAM detalla la política mínima recomendada.
Importante: Los fragmentos de código incluidos en esta publicación contienen todas las piezas necesarias para reproducir la solución. El archivo ASL completo y la plantilla de AWS CloudFormation están disponibles en el repositorio de ejemplos de AWS en GitHub. Como punto de partida para familiarizarse con el patrón, se puede desplegar el proyecto de ejemplo Encadenamiento de prompts de IA con Amazon Bedrock directamente desde la consola de AWS Step Functions: demuestra la integración optimizada con Amazon Bedrock y el uso de JSONata en un flujo multi-paso similar al de esta publicación.
Costes estimados
Esta solución genera costes en los siguientes servicios:
| Servicio | Concepto | Estimación por ejecución |
|---|---|---|
| AWS Step Functions | Transiciones de estado (~200-400 por ejecución) | ~$0.005–$0.01 |
| AWS FIS | Minutos de acción de experimento (3 escenarios × ~5 min) | ~$0.15 |
| Amazon Bedrock | Tokens de entrada/salida (2 invocaciones: generación + evaluación) | ~$0.02–$0.05 (varía según modelo) |
| Amazon CloudWatch | Solicitudes GetMetricData (6-8 por ejecución) | ~$0.003 |
| Amazon CloudWatch | Alarma estándar (coste mensual, no por ejecución) | ~$0.10/mes |
El coste total por ejecución es inferior a $0.25 en la mayoría de los casos. Para estimaciones actualizadas, se puede usar la Calculadora de precios de AWS.
Disponibilidad regional
Esta solución requiere que todos los servicios estén disponibles en la misma región. El requisito más restrictivo es la compatibilidad de JSONata en AWS Step Functions y la disponibilidad de los modelos de Amazon Bedrock. La documentación de disponibilidad regional de AWS Step Functions y la disponibilidad de modelos de Amazon Bedrock proporcionan información actualizada.
Los ejemplos de esta publicación usan la región Europa (Irlanda), eu-west-1.
Límites y cuotas relevantes
Es necesario tener en cuenta los siguientes límites al usar esta solución:
| Servicio | Límite | Valor por defecto |
|---|---|---|
| AWS Step Functions | Eventos de historial por ejecución | 25,000 |
| AWS Step Functions | Tamaño máximo de payload (entrada/salida por estado) | 256 KB |
| AWS FIS | Plantillas de experimento por cuenta | 500 |
| AWS FIS | Experimentos activos simultáneos | 5 |
| Amazon Bedrock | Solicitudes por minuto (varía según modelo y región) | Consultar cuotas de Bedrock |
Si el servicio tiene más de 100 tareas o el descubrimiento genera respuestas grandes, la ejecución podría acercarse al límite de payload de 256 KB. En ese caso, es recomendable filtrar las tareas por zona de disponibilidad en el descubrimiento.
Anatomía de la máquina de estados
La máquina de estados ejecuta seis fases en orden. Descubre la configuración real del servicio. Valida que cumple los requisitos para la inyección de fallos. Amazon Bedrock genera un plan de escenarios progresivos. Una barrera de seguridad filtra ese plan. La máquina ejecuta los escenarios de menor a mayor severidad. Por último, evalúa los resultados y emite un veredicto determinista.
Entrada de la máquina de estados
La máquina de estados recibe un único objeto JSON con la configuración del servicio a validar. A continuación se muestra la estructura con valores de ejemplo:
{
"clusterName": "fis-ecs-apache-cluster",
"serviceName": "fis-ecs-apache-service",
"targetGroupArn": "arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/fis-ecs-apache-tg/EXAMPLE",
"targetGroupId": "targetgroup/fis-ecs-apache-tg/EXAMPLE",
"loadBalancerId": "app/fis-ecs-apache-alb/EXAMPLE",
"fisRoleArn": "arn:aws:iam::111122223333:role/fis-ecs-apache-fis-role",
"stopConditionAlarmArn": "arn:aws:cloudwatch:eu-west-1:111122223333:alarm:fis-ecs-apache-healthyhosts-low",
"bedrockModelId": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
}
| Campo | Descripción |
|---|---|
clusterName |
Nombre del clúster de Amazon ECS. |
serviceName |
Nombre del servicio de Amazon ECS que se va a validar. |
targetGroupArn |
ARN completo del grupo de destino de Elastic Load Balancing asociado al servicio. |
targetGroupId |
Nombre completo del grupo de destino (formato targetgroup/nombre/id), usado en las dimensiones de Amazon CloudWatch. |
loadBalancerId |
Nombre completo del balanceador (formato app/nombre/id), usado en las dimensiones de Amazon CloudWatch. |
fisRoleArn |
ARN del rol de IAM que AWS FIS asume para ejecutar los experimentos. |
stopConditionAlarmArn |
ARN de la alarma de Amazon CloudWatch que actúa como condición de parada. Si la alarma entra en estado ALARM durante un experimento, AWS FIS lo detiene de inmediato. |
bedrockModelId |
Identificador del modelo de Amazon Bedrock. Admite un perfil de inferencia entre regiones (por ejemplo, eu.anthropic.claude-sonnet-4-5-20250929-v1:0). |
Nota: Los valores
111122223333yEXAMPLEson marcadores de posición. Es necesario reemplazarlos con los valores reales de su cuenta. Si se despliega la infraestructura de ejemplo con la plantilla de AWS CloudFormation incluida en el repositorio, es posible obtener estos valores a partir de los outputs del stack.
Descubrimiento del servicio
El flujo arranca en el estado DiscoverCluster, un estado Parallel con cinco ramas que se ejecutan a la vez. Cada rama es un estado Task que llama a una API distinta:
DescribeServicesen Amazon ECSListTasksyDescribeTaskspara enumerar las tareas en ejecuciónDescribeTargetHealthen Elastic Load BalancingGetMetricDataen Amazon CloudWatch para obtener métricas de referenciaListActionsen AWS FIS para obtener las acciones disponibles
Al ejecutarse en paralelo, las cinco llamadas terminan antes y consolidan la imagen completa del servicio en un mismo punto (Figura 2).
Figura 2: Estado DiscoverCluster – descubrimiento en paralelo del servicio.
El bloque Assign del estado Parallel reúne las salidas de las cinco ramas en variables reutilizables:
"Assign": {
"serviceConfig": "{% $states.result[0].service %}",
"desiredCount": "{% $states.result[0].service.DesiredCount %}",
"taskDefinitionArn": "{% $states.result[0].service.TaskDefinition %}",
"tasks": "{% $states.result[1].tasks %}",
"azDistribution": "{% $distinct($states.result[1].tasks.AvailabilityZone) %}",
"targetHealth": "{% $states.result[2].targetHealth %}",
"baselineMetrics": "{% $states.result[3].baselineMetrics %}",
"availableFISActions": "{% $states.result[4].ecsTaskActions %}"
}
La expresión $states.result contiene la lista de resultados de las ramas, indexada por posición. La función $distinct($states.result[1].tasks.AvailabilityZone) recorre todas las tareas, extrae su zona de disponibilidad y elimina duplicados. La variable azDistribution contiene la lista de zonas únicas, sin código adicional – una función nativa de JSONata resuelve la deduplicación.
Para más ejemplos del uso de JSONata en definiciones de estados, incluyendo las funciones $distinct, $sort y el campo Assign, consulte Procesamiento de entrada y salida con JSONata y la guía de variables en Step Functions.
Validación de requisitos
El estado DescribeTaskDefinition recupera la definición de tarea y guarda tres valores clave: enableFaultInjection, pidMode y la familia de la definición (Family). Con esos datos, el estado ValidatePrerequisites decide si la inyección puede continuar:
"ValidatePrerequisites": {
"Type": "Choice",
"Comment": "Validate that the service meets FIS requirements",
"Choices": [
{
"Condition": "{% $enableFaultInjection = true and $count($tasks) >= 2 and $count($azDistribution) >= 2 and $pidMode = 'task' %}",
"Next": "GenerateScenarios"
}
],
"Default": "PrerequisiteFailed"
}
La condición comprueba cuatro requisitos a la vez:
- La inyección de fallos está activada (
enableFaultInjection = true). - Hay al menos dos tareas en ejecución.
- Las tareas se reparten entre al menos dos zonas de disponibilidad.
pidModeestask.
Los requisitos 1, 2 y 4 son necesarios para la inyección de fallos en Amazon ECS. El requisito 3 garantiza una topología mínima para probar la conmutación entre zonas. Si el servicio no cumple estos requisitos, la máquina termina en PrerequisiteFailed (estado de tipo Fail) con una causa que detalla qué requisito falló. La prueba se detiene antes de crear un solo experimento.
Generación de escenarios con Amazon Bedrock
Cuando el servicio pasa la validación, el estado GenerateScenarios invoca a Amazon Bedrock para redactar un plan de tres escenarios progresivos a partir de la configuración real del servicio. El modelo devuelve texto que se convierte en datos estructurados con una expresión JSONata:
$parse($replace($states.result.Body.content[0].text, /```json\n?|```\n?/, ''))
Esta expresión limpia posibles marcas de formato Markdown del modelo y parsea el resultado como JSON. Los escenarios se guardan en la variable scenarios.
Nota: Amazon Bedrock genera sugerencias basadas en la configuración del servicio. Estas sugerencias se validan siempre contra la barrera de seguridad antes de ejecutarse. Es recomendable fijar la versión del modelo en la entrada (por ejemplo,
eu.anthropic.claude-sonnet-4-5-20250929-v1:0) para obtener comportamiento predecible entre ejecuciones. El proyecto de ejemplo Encadenamiento de prompts de IA con Amazon Bedrock muestra un patrón similar de múltiples invocaciones a Amazon Bedrock dentro de un flujo de Step Functions, y se puede desplegar directamente desde la consola para experimentar con la integración.
Barrera de seguridad (guardrails)
Como los escenarios los propone un modelo generativo, el estado ValidateGuardrails los revisa antes de ejecutar nada. Este estado comprueba tres condiciones:
- Todas las acciones propuestas pertenecen a una lista de acciones permitidas.
- Ninguna duración supera los 300 segundos.
- Ningún porcentaje de destino supera el 100%.
Si alguna condición falla, la ejecución se detiene en GuardrailViolation. La generación con IA aporta flexibilidad; la barrera de seguridad aporta control. La Figura 3 muestra cómo estos estados de validación deciden si la ejecución continúa o se detiene.
Además, la plantilla de cada experimento establece EmptyTargetResolutionMode en fail. Si AWS FIS no encuentra recursos que coincidan con las etiquetas de selección, el experimento falla de inmediato en lugar de finalizar con éxito sin actuar sobre ningún recurso. Esto garantiza que el resultado del experimento refleja una ejecución real sobre los recursos esperados, y que el veredicto final es fiable.
Nota: La barrera de seguridad protege contra acciones inesperadas del modelo. En entornos de producción, conviene limitar las acciones permitidas al mínimo necesario y revisar periódicamente los planes que genera el modelo.
Figura 3: Flujo de validación – de los requisitos previos a la barrera de seguridad.
Ejecución progresiva
El corazón de la ejecución es ExecuteProgressiveScenarios, un estado Map con MaxConcurrency igual a 1. Los escenarios se procesan en serie y ordenados por severidad ascendente con una función de comparación JSONata:
$sort($scenarios, function($a, $b){ $a.level - $b.level })
Dentro del Map, cada escenario recorre su propio subflujo:
- Crear la plantilla del experimento en AWS FIS.
- Iniciar el experimento.
- Sondear el estado del experimento hasta que termine.
- Esperar la recuperación del servicio.
- Recoger métricas posteriores al experimento.
El estado IsExperimentComplete decide el desenlace de cada nivel:
"IsExperimentComplete": {
"Type": "Choice",
"Choices": [
{ "Condition": "{% $experimentStatus = 'completed' %}", "Next": "WaitForRecovery" },
{ "Condition": "{% $experimentStatus = 'failed' %}", "Next": "LevelFailed" },
{ "Condition": "{% $experimentStatus = 'stopped' %}", "Next": "LevelStopped" }
],
"Default": "WaitForExperiment"
}
La rama Default devuelve el flujo a WaitForExperiment, un estado Wait de 30 segundos. Este ciclo forma un bucle de sondeo nativo mientras el experimento sigue en marcha:
- Si el experimento se completa →
WaitForRecoveryconcede 60 segundos para que el servicio se estabilice, y luegoCollectPostMetricscaptura las métricas. El nivel se marca como PASS. - Si el experimento falla, el nivel se marca con estado de fallo – FAIL.
- Si una condición de parada lo detiene → el nivel se marca como STOPPED.
Este patrón de sondeo nativo (un estado Wait seguido de un Task, un Choice cuyo Default devuelve al Wait) es un patrón estándar en Step Functions, visible en la Figura 4. El proyecto de ejemplo Sondeo del estado de un trabajo con Lambda y AWS Batch, implementa el mismo ciclo de espera y comprobación.
La Figura 4 muestra este subflujo dentro del estado Map, con el bucle de sondeo entre WaitForExperiment y CheckExperimentStatus y las tres salidas posibles.
Figura 4: Subflujo de ejecución progresiva dentro del estado Map.
Evaluación y veredicto
Al terminar el Map, el estado EvaluateResults invoca a Amazon Bedrock con una valoración global de todos los niveles. Un estado Choice (EvaluateVerdict) comprueba el resultado:
- Si el veredicto es PASS → la ejecución termina en
ValidationPassed. La valoración completa queda en la salida de la ejecución. - Si el veredicto no es PASS → la ejecución termina en
ValidationFailed(estado de tipo Fail). La máquina de estados falla a propósito para que el pipeline lo detecte.
El veredicto es PASS cuando haScore alcanza 70 (configurable en el estado EvaluateVerdict). Para una exigencia mayor, basta con aumentar ese umbral en la definición del estado. No hay almacenamiento externo: el resultado queda en la propia ejecución de AWS Step Functions (Figura 5).
Figura 5: Evaluación y veredicto final.
Acciones de AWS FIS compatibles
La barrera de seguridad de esta solución solo admite dos acciones:
| Acción | Descripción | Requisitos |
|---|---|---|
aws:ecs:stop-task |
Detiene tareas a través del plano de control de Amazon ECS | No requiere agente sidecar |
aws:network:disrupt-connectivity |
Corta la conectividad de una subred (simula caída de zona de disponibilidad) | No requiere agente sidecar |
Ninguna de las dos requiere el contenedor sidecar del agente de AWS Systems Manager, que sí necesitan los experimentos que actúan dentro del contenedor (estrés de CPU, latencia de red, pérdida de paquetes). Esto mantiene el plan ligero y aplicable a un servicio de Fargate existente sin modificaciones adicionales.
Como AWS FIS cubre muchos más servicios, puede aplicar el mismo patrón (descubrir, generar, validar, inyectar y evaluar) a otros recursos. Por ejemplo: detener instancias de Amazon EC2, provocar una conmutación por error en Amazon RDS o eliminar pods en Amazon EKS. Solo es necesario cambiar la acción y el objetivo del experimento. La Figura 6 muestra un ejemplo de experimento completado en la consola de AWS FIS.
Figura 6: Salida de un experimento en la consola de AWS FIS, con la acción inyectada, el objetivo seleccionado por etiquetas y el estado final.
Limpieza
Para evitar cargos por recursos que ya no use, elimine los siguientes recursos:
- Máquina de estados de AWS Step Functions: La máquina no genera coste mientras no se ejecuta, pero es recomendable eliminarla si ya no se necesita.
- Plantillas de experimento de AWS FIS: Es necesario eliminar las plantillas autogeneradas (etiquetadas con
AutoGenerated=true). - Roles de IAM: Es necesario retirar el rol de ejecución de la máquina de estados y el rol del experimento de AWS FIS.
- Alarma de Amazon CloudWatch: La alarma sigue facturando mientras esté activa (~$0.10/mes por alarma estándar). Es recomendable eliminarla si se creó solo para esta prueba.
- Inyección de fallos en la definición de tarea: Si se activó
enableFaultInjectionsolo para esta prueba, es necesario actualizar la definición de tarea para desactivarla.
Recursos adicionales
- Documentación de AWS Step Functions
- Guía de JSONata en AWS Step Functions
- Documentación de AWS Fault Injection Service
- Inyección de fallos en Amazon ECS
- Integración optimizada de Amazon Bedrock con AWS Step Functions
- Encadenamiento de prompts de IA con Amazon Bedrock (sample project)
- Repositorio de código de esta solución (enlace disponible tras la publicación del repositorio)
- Segunda parte de esta serie: Integrar pruebas de resiliencia en el pipeline de CI/CD con AWS Step Functions
Conclusión
En esta publicación, aprendió cómo un único archivo JSON declarativo valida la resiliencia de un servicio de Amazon ECS sobre AWS Fargate. La máquina de estados descubre la topología, valida los requisitos, genera escenarios progresivos con Amazon Bedrock, los filtra con una barrera de seguridad, ejecuta los experimentos con AWS FIS y emite un veredicto determinista.
En la segunda parte de esta serie, aprenderá cómo integrar esta validación como puerta de calidad en su pipeline de CI/CD con GitHub Actions y AWS CodePipeline, y cómo depurar ejecuciones fallidas con las herramientas de observabilidad de AWS Step Functions.
Acerca del autor
![]() |
Oriol Matavacas Rodriguez es Senior Solutions Architect en AWS. Ayuda a organizaciones en la adopción estratégica de servicios de AWS, con experiencia en arquitecturas serverless, migración y DevOps. |
