Lab práctico · Semana 10: Resiliencia, operaciones y costes: Well-Architected, DR, observabilidad, IaC y FinOps
Infraestructura como código con CloudFormation: stacks, change sets, drift y rollback
Qué vas a construir
Un stack de CloudFormation con varios recursos gobernados por una sola plantilla, y vas a practicar las operaciones que pregunta el examen:
- Una plantilla con Parameters, Mappings, Conditions, Resources y Outputs.
- Crear el stack para el entorno
dev. - Previsualizar el paso a
prodcon un change set y detectar qué recursos se reemplazan. - Provocar un cambio manual y encontrarlo con drift detection.
- Activar la protección contra borrado (termination protection).
- Ver un rollback automático cuando una creación falla.
flowchart LR
T["plantilla.yaml"] --> V["validate-template"]
V --> C["create-stack (dev)"]
C --> CS["change set a prod"]
CS -->|"revisar y ejecutar"| U["stack actualizado"]
U --> M["cambio manual fuera de CFN"]
M --> D["detect-stack-drift"]
D --> L["Limpieza: delete-stack"]
Antes de empezar
- Usuario de IAM Identity Center o de IAM con MFA (no el raíz) con permisos sobre CloudFormation, S3, SNS, SSM, CloudWatch y CloudWatch Logs.
- AWS CloudShell en la región eu-south-2 (España).
export AWS_REGION=eu-south-2
export PILA=lab19
mkdir -p ~/lab19 && cd ~/lab19
Paso 1: escribir la plantilla
Qué y por qué: una plantilla parametrizada permite desplegar entornos idénticos (desarrollo, producción, región de DR) sin copiar y pegar. Fíjate en cada sección:
Parameters: el entorno se elige al desplegar.Mappings: tabla con la retención de logs por entorno.Conditions: la alarma solo se crea enprod.Resources: bucket cifrado y versionado, tema SNS, parámetro de SSM, grupo de logs y alarma.Outputs: valores útiles; el nombre del bucket se exporta para que otros stacks lo importen conFn::ImportValue.
cat > plantilla.yaml <<'EOF'
AWSTemplateFormatVersion: "2010-09-09"
Description: Lab 19 - entorno base gobernado por CloudFormation
Parameters:
Entorno:
Type: String
AllowedValues: [dev, prod]
Default: dev
Description: Entorno que se despliega
Mappings:
Retencion:
dev:
Dias: 7
prod:
Dias: 30
Conditions:
EsProd: !Equals [!Ref Entorno, prod]
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Tags:
- Key: Proyecto
Value: lab19
- Key: Entorno
Value: !Ref Entorno
Tema:
Type: AWS::SNS::Topic
Properties:
Tags:
- Key: Proyecto
Value: lab19
Parametro:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /lab19/${Entorno}/bucket
Type: String
Value: !Ref Bucket
Description: Nombre del bucket del entorno
Logs:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /lab19/${Entorno}/app
RetentionInDays: !FindInMap [Retencion, !Ref Entorno, Dias]
AlarmaFallosSNS:
Type: AWS::CloudWatch::Alarm
Condition: EsProd
Properties:
AlarmDescription: Hay notificaciones SNS fallidas
Namespace: AWS/SNS
MetricName: NumberOfNotificationsFailed
Dimensions:
- Name: TopicName
Value: !GetAtt Tema.TopicName
Statistic: Sum
Period: 300
EvaluationPeriods: 1
Threshold: 0
ComparisonOperator: GreaterThanThreshold
TreatMissingData: notBreaching
AlarmActions:
- !Ref Tema
Outputs:
NombreBucket:
Description: Bucket del entorno
Value: !Ref Bucket
Export:
Name: !Sub ${AWS::StackName}-bucket
ArnTema:
Description: Tema de alertas
Value: !Ref Tema
EOF
aws cloudformation validate-template --template-body file://plantilla.yaml
validate-template comprueba la sintaxis y devuelve los parámetros detectados. No valida que los valores sean correctos para cada servicio: eso se descubre al desplegar.
Paso 2: crear el stack de desarrollo
aws cloudformation create-stack --stack-name $PILA \
--template-body file://plantilla.yaml \
--parameters ParameterKey=Entorno,ParameterValue=dev \
--tags Key=Proyecto,Value=lab19
aws cloudformation wait stack-create-complete --stack-name $PILA
aws cloudformation describe-stacks --stack-name $PILA \
--query 'Stacks[0].[StackStatus,Outputs]'
aws cloudformation list-stack-resources --stack-name $PILA \
--query 'StackResourceSummaries[*].[LogicalResourceId,ResourceType,ResourceStatus]' --output table
Observa que no existe AlarmaFallosSNS: la condición EsProd es falsa. Abre también la consola de CloudFormation y revisa la pestaña Events: verás el orden en el que se crearon los recursos según sus dependencias (el parámetro de SSM espera al bucket porque usa !Ref Bucket).
Paso 3: previsualizar el cambio a producción con un change set
Qué y por qué: en producción nunca actualices a ciegas. Un change set muestra qué va a pasar con cada recurso antes de ejecutarlo, incluido si se reemplaza (se borra y se crea otro: en una base de datos implicaría perder datos si no tienes DeletionPolicy o snapshot).
aws cloudformation create-change-set --stack-name $PILA \
--change-set-name a-prod \
--use-previous-template \
--parameters ParameterKey=Entorno,ParameterValue=prod
aws cloudformation wait change-set-create-complete --stack-name $PILA --change-set-name a-prod
aws cloudformation describe-change-set --stack-name $PILA --change-set-name a-prod \
--query 'Changes[*].ResourceChange.[Action,LogicalResourceId,ResourceType,Replacement]' \
--output table
Interpreta la tabla:
AddparaAlarmaFallosSNS(ahora la condición es verdadera).ModifyconReplacement = TrueparaParametroyLogs: su nombre cambia (/lab19/prod/...) y ese atributo no se puede modificar en caliente, así que CloudFormation creará recursos nuevos y borrará los viejos.Modifysin reemplazo para el bucket (cambia una etiqueta).
Si te convence, ejecútalo:
aws cloudformation execute-change-set --stack-name $PILA --change-set-name a-prod
aws cloudformation wait stack-update-complete --stack-name $PILA
aws ssm get-parameter --name /lab19/prod/bucket --query Parameter.Value --output text
aws cloudwatch describe-alarms --alarm-name-prefix $PILA --query 'MetricAlarms[*].[AlarmName,StateValue]'
Paso 4: provocar deriva y detectarla (drift detection)
Qué y por qué: alguien «arregla algo rápido» en la consola y el recurso deja de coincidir con la plantilla. Drift detection compara la configuración real con la esperada. Es una estrategia de automatización para garantizar la integridad de la infraestructura (infrastructure integrity).
Suspende el versionado del bucket fuera de CloudFormation:
export BUCKET=$(aws cloudformation describe-stacks --stack-name $PILA \
--query "Stacks[0].Outputs[?OutputKey=='NombreBucket'].OutputValue" --output text)
aws s3api put-bucket-versioning --bucket $BUCKET \
--versioning-configuration Status=Suspended
Lanza la detección y espera el resultado:
DETECCION=$(aws cloudformation detect-stack-drift --stack-name $PILA \
--query StackDriftDetectionId --output text)
until [ "$(aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id $DETECCION --query DetectionStatus --output text)" != "DETECTION_IN_PROGRESS" ]; do
sleep 5
done
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id $DETECCION --query '[StackDriftStatus,DriftedStackResourceCount]'
aws cloudformation describe-stack-resource-drifts --stack-name $PILA \
--stack-resource-drift-status-filters MODIFIED \
--query 'StackResourceDrifts[*].[LogicalResourceId,PropertyDifferences[*].[PropertyPath,ExpectedValue,ActualValue]]'
Verás DRIFTED y la diferencia en /VersioningConfiguration/Status (esperado Enabled, real Suspended). Corrige la deriva devolviendo el recurso a lo que dice la plantilla:
aws s3api put-bucket-versioning --bucket $BUCKET \
--versioning-configuration Status=Enabled
Ojo: una actualización del stack no corrige la deriva por sí sola si la plantilla no cambia esa propiedad. Para detectar cambios de configuración de forma continua y en toda la cuenta, usarías AWS Config (módulo 07).
Paso 5: protección contra borrado
aws cloudformation update-termination-protection --stack-name $PILA \
--enable-termination-protection
aws cloudformation delete-stack --stack-name $PILA
El borrado falla con un error que menciona la protección. Es la red de seguridad para stacks de producción. Desactívala para poder limpiar al final:
aws cloudformation update-termination-protection --stack-name $PILA \
--no-enable-termination-protection
Paso 6: rollback automático ante un error
Qué y por qué: si un recurso no se puede crear, CloudFormation deshace todo lo creado en esa operación. Crea un stack con un nombre de bucket inválido (mayúsculas y guiones bajos no están permitidos):
cat > fallo.yaml <<'EOF'
Resources:
TemaBueno:
Type: AWS::SNS::Topic
BucketMalo:
Type: AWS::S3::Bucket
Properties:
BucketName: Nombre_No_Valido
EOF
aws cloudformation create-stack --stack-name lab19-fallo --template-body file://fallo.yaml
until aws cloudformation describe-stacks --stack-name lab19-fallo \
--query 'Stacks[0].StackStatus' --output text | grep -qE 'COMPLETE|FAILED'; do
sleep 5
done
aws cloudformation describe-stacks --stack-name lab19-fallo --query 'Stacks[0].StackStatus'
aws cloudformation describe-stack-events --stack-name lab19-fallo \
--query "StackEvents[?ResourceStatus=='CREATE_FAILED'].[LogicalResourceId,ResourceStatusReason]"
El estado final es ROLLBACK_COMPLETE: el tema SNS que sí se había creado se ha borrado. Un stack en ese estado no se puede actualizar; solo borrar:
aws cloudformation delete-stack --stack-name lab19-fallo
aws cloudformation wait stack-delete-complete --stack-name lab19-fallo
Para saber más: StackSets (sin ejecutar)
Con StackSets desplegarías esta misma plantilla en varias cuentas y regiones con una sola operación: por ejemplo, el mismo entorno base en eu-south-2 y en eu-west-1 como región de DR, o un rol de auditoría en todas las cuentas de una OU. Con permisos service-managed (integrados con AWS Organizations) los stacks se crean automáticamente en las cuentas nuevas de la OU. No lo ejecutamos porque requiere Organizations o roles de administración y ejecución en cada cuenta.
Comprueba que funciona
- Tras el paso 3,
aws ssm get-parameter --name /lab19/prod/bucketdevuelve el nombre del bucket y ya no existe/lab19/dev/bucket. - Tras el paso 4, la detección de deriva mostró
DRIFTEDy la propiedadVersioningConfiguration/Status. - Tras el paso 6,
lab19-falloterminó enROLLBACK_COMPLETEsin dejar recursos.
Limpieza
El bucket está vacío, así que CloudFormation puede borrarlo. Borrar el stack elimina todos sus recursos en el orden inverso de dependencias.
aws cloudformation delete-stack --stack-name $PILA
aws cloudformation wait stack-delete-complete --stack-name $PILA
aws cloudformation list-stacks --stack-status-filter DELETE_COMPLETE \
--query "StackSummaries[?starts_with(StackName,'lab19')].[StackName,StackStatus]"
aws ssm get-parameters-by-path --path /lab19 --recursive --query 'Parameters[*].Name'
aws logs describe-log-groups --log-group-name-prefix /lab19 --query 'logGroups[*].logGroupName'
cd ~ && rm -rf ~/lab19
Las dos últimas consultas deben devolver listas vacías. Si el borrado del stack falla porque el bucket tiene objetos (por ejemplo, si subiste alguno), vacíalo antes, incluidas las versiones, o bórralo desde la consola de S3 con Empty.
Preguntas para pensar como arquitecto
- Tu empresa quiere recuperar en
eu-west-1toda la infraestructura deeu-south-2en menos de 4 horas si cae la región, con el mínimo coste permanente. ¿Qué papel juega CloudFormation?
Respuesta
Es la base de una estrategia backup and restore (o pilot light): la infraestructura está descrita como código y se despliega en la región de DR solo cuando hace falta (o reducida con Conditions y parámetros), mientras que los datos llegan allí con copias entre regiones (AWS Backup, snapshots, replicación de S3). Sin IaC, reconstruir a mano haría imposible cumplir el RTO. Conviene tener las AMI copiadas y las cuotas ampliadas en la región de DR.
- Un operador modificó un security group a mano y la plantilla dice otra cosa. ¿Cómo lo detectas y cómo evitas que vuelva a pasar?
Respuesta
Lo detectas con drift detection de CloudFormation (bajo demanda) o de forma continua con reglas de AWS Config, que además pueden remediar con Systems Manager Automation. Para prevenirlo: permisos de IAM que solo permitan cambios a través de CloudFormation (el rol de servicio del stack), SCP, y un proceso de cambios con change sets revisados.
- Vas a actualizar el stack de una base de datos RDS de producción y el change set indica
Replacement: True. ¿Qué haces?
Respuesta
No ejecutarlo sin más: el reemplazo crea una instancia nueva y borra la antigua. Revisa qué propiedad lo provoca y busca una alternativa sin reemplazo. Como protección, la plantilla debe llevar DeletionPolicy: Snapshot (o Retain) y UpdateReplacePolicy: Snapshot en la base de datos, y el stack, termination protection y una stack policy que impida reemplazarla.
- Los equipos de desarrollo quieren crear sus propios entornos con esta plantilla, pero sin tener permisos amplios de IAM. ¿Qué servicio usarías?
Respuesta
AWS Service Catalog: publicas la plantilla como producto en un portfolio compartido con esos equipos y defines una launch constraint con el rol de IAM que CloudFormation usará para crear los recursos. Los desarrolladores solo necesitan permiso para lanzar productos del catálogo.