Skip to content

📙 Clase 1 — Infraestructura como código con CloudFormation

Curso AWS (emulador Floci) · 2026-08-08 · Carpeta: 02-Ejercicios/clase-01-cloudformation ⬅️ Volver al índice de clases

🎯 Qué aprendí

  • Qué es una pila (stack) y por qué agrupar recursos cambia la forma de trabajar.
  • Escribir plantillas YAML: Resources, Outputs, Parameters, Mappings.
  • Las funciones intrínsecas que dan vida a una plantilla: !Ref, !GetAtt, !Sub, !Join, !FindInMap.
  • El ciclo completo: crear → actualizar → eliminar, y cómo mirar por dentro con describe-stack-events.
  • Compartir valores entre pilas con Export / !ImportValue.
  • Dos cosas que el emulador NO hace igual que AWS — y por qué conviene saberlo desde el principio.

📖 PARTE TEÓRICA

🧱 1. El problema: infraestructura hecha a mano

Hasta ahora, cada recurso se crea con su propio comando:

bash
aws s3 mb s3://informes                  # un bucket
aws sqs create-queue --queue-name avisos # una cola
aws dynamodb create-table ...            # una tabla

Funciona, pero tiene tres agujeros:

  1. No queda escrito en ningún sitio. Dentro de tres meses, ¿quién recuerda el orden y los nombres?
  2. No se puede repetir. Montar lo mismo en otro entorno es volver a teclearlo todo, y equivocarse en algo.
  3. Nadie sabe qué va junto. Si borras el bucket, ¿había algo que dependía de él?

Infraestructura como código resuelve las tres: describes lo que quieres en un archivo, y ese archivo es la fuente de verdad.

   A MANO                          COMO CÓDIGO
   ───────                         ───────────
   comando 1  ─┐                   plantilla.yaml
   comando 2  ─┼─ ¿en qué orden?        │
   comando 3  ─┘  ¿con qué nombres?     ▼
                                   ┌──────────┐
   ✗ no versionable                │   PILA   │  crea todo, en orden
   ✗ no repetible                  └──────────┘  y sabe qué va junto
   ✗ no se sabe qué va junto            │
                                   ✓ un archivo en git
                                   ✓ mismo resultado siempre
                                   ✓ se borra entero de una vez

📄 2. Anatomía de una plantilla

Una plantilla es un YAML con secciones. La única obligatoria es Resources:

yaml
AWSTemplateFormatVersion: '2010-09-09'   # versión del formato (siempre esta)
Description: Mi primera pila             # texto libre, opcional

Parameters:                              # valores que se pasan al crear la pila
  NombreBucket:
    Type: String
    Default: mi-bucket

Mappings:                                # tablas de consulta fijas
  PorRegion:
    us-east-1: { Sufijo: este }

Resources:                               # ← LO ÚNICO OBLIGATORIO
  MiBucket:                              # nombre lógico (lo eliges tú)
    Type: AWS::S3::Bucket                # tipo de recurso
    Properties:                          # su configuración
      BucketName: !Ref NombreBucket

Outputs:                                 # valores que la pila devuelve
  Nombre:
    Value: !Ref MiBucket

💡 Nombre lógico vs nombre físico. MiBucket es el nombre lógico: solo existe dentro de la plantilla, para referirte a él. El nombre físico (mi-bucket) es el real en AWS. Esa separación es la que permite que !Ref funcione.

🔗 3. Funciones intrínsecas — lo que hace viva a una plantilla

Sin ellas, una plantilla sería una lista de recursos sueltos. Con ellas, los recursos se conocen entre sí.

FunciónQué devuelveEjemplo
!RefEl identificador principal del recurso!Ref MiBucketmi-bucket
!GetAttUn atributo concreto!GetAtt MiCola.Arnarn:aws:sqs:...
!SubTexto con sustituciones!Sub 'app-${AWS::Region}'app-us-east-1
!JoinUne trozos con un separador!Join ['-', ['app', 'prod']]app-prod
!FindInMapBusca en Mappings!FindInMap [PorRegion, us-east-1, Sufijo]este

Y los pseudo-parámetros, que existen siempre sin declararlos:

Pseudo-parámetroValor
AWS::Regionus-east-1
AWS::AccountId000000000000 (en el emulador)
AWS::StackNameEl nombre de tu pila

🧪 Pregunta de entrevista: ¿diferencia entre !Ref y !GetAtt? !Ref devuelve el identificador por defecto del recurso (el nombre en un bucket, la URL en una cola). !GetAtt devuelve un atributo concreto que tú eliges (.Arn, .DomainName). Si necesitas el ARN, casi siempre es !GetAtt.

🔄 4. El ciclo de vida de una pila

  create-stack          update-stack           delete-stack
       │                     │                      │
       ▼                     ▼                      ▼
  CREATE_COMPLETE ──▶ UPDATE_COMPLETE ──▶ DELETE_COMPLETE
       │                     │                      │
       └── crea todos        └── calcula la         └── borra TODO
           los recursos          diferencia y            lo que creó
                                 aplica solo eso

Lo importante de delete-stack: se lleva por delante todo lo que la pila creó. Es su mayor virtud (nada olvidado cobrando) y su mayor peligro (un borrado accidental se lleva más de lo que crees).

⚠️ Una pila solo gestiona lo que ella creó. Si creaste un bucket a mano con aws s3 mb y luego escribes una plantilla con ese mismo nombre, no lo "adopta": intentará crearlo y chocará porque ya existe.

🧭 5. Compartir valores entre pilas

Una pila puede publicar un valor para que otra lo use:

yaml
# Pila A — publica
Outputs:
  ColaArn:
    Value: !GetAtt MiCola.Arn
    Export:
      Name: arn-de-mi-cola      # nombre único en toda la cuenta

# Pila B — consume
Outputs:
  Heredado:
    Value: !ImportValue arn-de-mi-cola

📌 Mientras una pila importe un valor exportado, la pila que lo exporta no se puede borrar. Es una protección deliberada, no un fallo.

⚠️ 6. Dos cosas que el emulador NO hace como AWS

Esto no está en la documentación; lo descubrí probándolo, y conviene saberlo antes de confiar en un ejercicio.

1 · Las Conditions se ignoran. En AWS, un recurso con Condition: EsProd no se crea si la condición es falsa. En Floci se crea igual:

yaml
Parameters:
  Entorno: { Type: String, Default: dev }
Conditions:
  EsProd: !Equals [!Ref Entorno, prod]
Resources:
  SoloEnProd:
    Type: AWS::SQS::Queue
    Condition: EsProd          # ← con Entorno=dev, AWS lo omite; Floci lo crea

2 · No valida los tipos de recurso. Una plantilla con Type: AWS::Inventado::NoExiste devuelve CREATE_COMPLETE en vez de fallar y hacer rollback.

📝 No son motivo para descartar el emulador: todo lo demás de esta clase funciona igual que en AWS. Pero significa que una plantilla que aquí pasa, en AWS podría no pasar. Para lo que aprendes en esta clase, da igual; para producción, valida contra AWS real.


💻 PARTE PRÁCTICA

Trabajaremos en 02-Ejercicios/clase-01-cloudformation/. Antes de nada, el entorno:

bash
floci start
eval $(floci env)

La plantilla mínima con la que empieza todo:

yaml
# 02-Ejercicios/clase-01-cloudformation/01-minima.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: La pila mas simple posible
Resources:
  MiBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: clase01-informes
bash
aws cloudformation create-stack \
  --stack-name clase01 \
  --template-body file://01-minima.yaml
zsh · 01-minima.yaml
$ aws cloudformation describe-stacks --stack-name clase01 \
    --query 'Stacks[0].StackStatus' --output text
CREATE_COMPLETE
$ aws s3 ls
2026-08-08 16:27:50 clase01-informes

💡 Si algo falla, aws cloudformation describe-stack-events --stack-name clase01 cuenta paso a paso qué intentó y dónde se atascó. Es el primer sitio donde mirar.


🏋️ EJERCICIOS CON SOLUCIÓN

40 ejercicios en cinco niveles, de la pila más simple a los límites del emulador. Todos verificados contra el laboratorio real.

💡 Antes de empezar: eval $(floci env). Y trabaja desde 02-Ejercicios/clase-01-cloudformation/.

🟢 Nivel 1 — La pila mínima (1-8)

Ejercicio 1 — Tu primera pila

Crea una pila llamada ej01 que contenga un único bucket de S3 llamado ej01-datos.

💡 ¿Sabías que…? — la sección mínima de una plantilla

De todas las secciones de una plantilla, solo Resources es obligatoria. Cada recurso necesita un nombre lógico, un Type y sus Properties.

yaml
# ejemplo de referencia — una cola, no un bucket
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MiColita:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: ejemplo-cola
Ver solución
yaml
# ej01.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: ej01-datos
bash
aws cloudformation create-stack --stack-name ej01 --template-body file://ej01.yaml

Ejercicio 2 — Consultar el estado

Averigua en qué estado quedó la pila ej01, mostrando solo el estado, sin el resto del JSON.

💡 ¿Sabías que…? — `--query` filtra la respuesta

El AWS CLI trae JMESPath integrado en --query, así que no hace falta jq. Combínalo con --output text para obtener un valor pelado, ideal para scripts.

bash
# ejemplo de referencia — el nombre de una cola
aws sqs get-queue-url --queue-name otra-cola --query 'QueueUrl' --output text
Ver solución
bash
aws cloudformation describe-stacks --stack-name ej01 \
  --query 'Stacks[0].StackStatus' --output text
# CREATE_COMPLETE

Ejercicio 3 — Listar los recursos de la pila

Muestra qué recursos gestiona ej01, con su nombre lógico y su estado.

💡 ¿Sabías que…? — la pila lleva su propio inventario

Una pila sabe exactamente qué creó. Ese inventario es lo que le permite borrarlo todo después sin dejar restos.

bash
# ejemplo de referencia — solo los tipos de recurso
aws cloudformation list-stack-resources --stack-name otra-pila \
  --query 'StackResourceSummaries[*].ResourceType' --output text
Ver solución
bash
aws cloudformation list-stack-resources --stack-name ej01 \
  --query 'StackResourceSummaries[*].[LogicalResourceId,ResourceStatus]' --output text
# Datos    CREATE_COMPLETE

Ejercicio 4 — Listar todas las pilas

Cuenta cuántas pilas hay ahora mismo.

💡 ¿Sabías que…? — `length()` cuenta sin contar a mano

JMESPath tiene funciones. length(...) devuelve el tamaño de una lista, útil para comprobaciones rápidas.

bash
# ejemplo de referencia — cuántos buckets hay
aws s3api list-buckets --query 'length(Buckets)' --output text
Ver solución
bash
aws cloudformation list-stacks --query 'length(StackSummaries)' --output text

Ejercicio 5 — Recuperar la plantilla guardada

Sin abrir el archivo, muestra la plantilla que la pila ej01 tiene almacenada.

💡 ¿Sabías que…? — la pila guarda su plantilla

Al crear una pila, AWS guarda una copia de la plantilla. Sirve para saber qué se desplegó de verdad cuando el archivo local ya cambió.

bash
# ejemplo de referencia — solo la descripción
aws cloudformation get-template --stack-name otra-pila --query 'TemplateBody' --output text | head -3
Ver solución
bash
aws cloudformation get-template --stack-name ej01 --query 'TemplateBody' --output text

Ejercicio 6 — Ver los eventos

Muestra cuántos eventos registró la pila ej01 durante su creación.

💡 ¿Sabías que…? — los eventos son la caja negra

Cada paso deja un evento con marca de tiempo. Cuando una pila falla, la causa está en el primer evento de fallo, no en el último.

bash
# ejemplo de referencia — los estados por los que pasó otra pila
aws cloudformation describe-stack-events --stack-name otra-pila \
  --query 'StackEvents[*].ResourceStatus' --output text
Ver solución
bash
aws cloudformation describe-stack-events --stack-name ej01 \
  --query 'length(StackEvents)' --output text

Ejercicio 7 — Eliminar la pila

Borra ej01 por completo.

💡 ¿Sabías que…? — borrar la pila borra sus recursos

delete-stack no borra "la definición": borra todo lo que la pila creó. Es la ventaja de agrupar, y también el riesgo.

bash
# ejemplo de referencia
aws cloudformation delete-stack --stack-name otra-pila
Ver solución
bash
aws cloudformation delete-stack --stack-name ej01

Ejercicio 8 — Comprobar que el recurso desapareció

Verifica que el bucket ej01-datos ya no existe.

💡 ¿Sabías que…? — comprobar el efecto, no solo el comando

Que un comando no dé error no significa que hiciera lo que crees. Comprobar el efecto real es el hábito que distingue a quien sabe operar.

bash
# ejemplo de referencia
aws s3 ls | grep otro-bucket || echo "ya no está"
Ver solución
bash
aws s3 ls | grep ej01-datos || echo "el bucket se fue con la pila"

🔵 Nivel 2 — Referencias entre recursos (9-16)

Ejercicio 9 — Devolver un valor con !Ref

Crea la pila ej09 con un bucket ej09-datos que devuelva su nombre como salida.

💡 ¿Sabías que…? — `Outputs` es la interfaz pública de la pila

Lo que pones en Outputs es lo que la pila expone al mundo. !Ref sobre un bucket devuelve su nombre.

yaml
# ejemplo de referencia — devolver el nombre de una cola
Resources:
  Cola: { Type: AWS::SQS::Queue, Properties: { QueueName: ref-demo } }
Outputs:
  UrlDeLaCola:
    Value: !Ref Cola
Ver solución
yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties: { BucketName: ej09-datos }
Outputs:
  NombreBucket:
    Value: !Ref Datos

Ejercicio 10 — Dos recursos en una pila

Amplía la plantilla anterior para que, además del bucket, cree una cola SQS ej10-avisos.

💡 ¿Sabías que…? — el orden en el YAML no manda

CloudFormation decide el orden de creación por las dependencias, no por cómo los escribas. Sin dependencias entre ellos, los crea en paralelo.

yaml
# ejemplo de referencia — dos recursos independientes
Resources:
  Tabla:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: demo-tabla
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions: [{ AttributeName: id, AttributeType: S }]
      KeySchema: [{ AttributeName: id, KeyType: HASH }]
  Cola:
    Type: AWS::SQS::Queue
    Properties: { QueueName: demo-cola }
Ver solución
yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties: { BucketName: ej10-datos }
  Avisos:
    Type: AWS::SQS::Queue
    Properties: { QueueName: ej10-avisos }

Ejercicio 11 — El ARN con !GetAtt

Haz que la pila devuelva el ARN de la cola, no su URL.

💡 ¿Sabías que…? — `!Ref` no siempre da lo que necesitas

En una cola SQS, !Ref devuelve la URL. Para el ARN (que es lo que piden los permisos y las integraciones) hace falta !GetAtt <Recurso>.Arn.

yaml
# ejemplo de referencia — ARN de un bucket
Outputs:
  ArnDelBucket:
    Value: !GetAtt OtroBucket.Arn
Ver solución
yaml
Outputs:
  ArnCola:
    Value: !GetAtt Avisos.Arn
# arn:aws:sqs:us-east-1:000000000000:ej10-avisos

Ejercicio 12 — Varias salidas a la vez

Devuelve en la misma pila el nombre del bucket y el ARN de la cola, con nombres de salida distintos.

💡 ¿Sabías que…? — `Outputs` admite tantas entradas como quieras

Cada salida es una clave dentro de Outputs, con su Value y opcionalmente Description.

yaml
# ejemplo de referencia
Outputs:
  Region:      { Value: !Ref 'AWS::Region' }
  NombrePila:  { Value: !Ref 'AWS::StackName' }
Ver solución
yaml
Outputs:
  NombreBucket:
    Description: Nombre fisico del bucket
    Value: !Ref Datos
  ArnCola:
    Description: ARN de la cola de avisos
    Value: !GetAtt Avisos.Arn

Ejercicio 13 — Componer un nombre con !Sub y la región

Crea un bucket cuyo nombre incluya la región automáticamente, sin escribirla a mano.

💡 ¿Sabías que…? — los pseudo-parámetros existen siempre

AWS::Region, AWS::AccountId y AWS::StackName están disponibles sin declararlos. !Sub los sustituye dentro de una cadena.

yaml
# ejemplo de referencia
Properties:
  QueueName: !Sub 'cola-${AWS::StackName}'
Ver solución
yaml
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'ej13-${AWS::Region}'
# ej13-us-east-1

Ejercicio 14 — El identificador de cuenta

Devuelve como salida el identificador de la cuenta.

💡 ¿Sabías que…? — en el emulador la cuenta es de doce ceros

Floci usa 000000000000 como identificador de cuenta. En AWS real serían tus doce dígitos. Que aparezca en los ARN es normal.

yaml
# ejemplo de referencia
Outputs:
  Region: { Value: !Sub '${AWS::Region}' }
Ver solución
yaml
Outputs:
  Cuenta:
    Value: !Sub '${AWS::AccountId}'
# 000000000000

Ejercicio 15 — Unir trozos con !Join

Compón el nombre ej15-informes-prod uniendo tres trozos con guiones.

💡 ¿Sabías que…? — `!Join` frente a `!Sub`

!Join [separador, [lista]] une elementos. Es útil cuando los trozos vienen de sitios distintos (parámetros, mappings). Para texto fijo con sustituciones, !Sub se lee mejor.

yaml
# ejemplo de referencia
BucketName: !Join ['.', ['datos', 'equipo', 'interno']]   # datos.equipo.interno
Ver solución
yaml
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Join ['-', ['ej15', 'informes', 'prod']]

Ejercicio 16 — Una tabla de consulta con Mappings

Usa un Mappings que asocie us-east-1 al sufijo este, y úsalo en el nombre del bucket.

💡 ¿Sabías que…? — `Mappings` es una tabla fija

Mappings guarda valores constantes organizados en dos niveles. !FindInMap [Mapa, Clave1, Clave2] los recupera. Se usa mucho para valores que cambian por región o entorno.

yaml
# ejemplo de referencia
Mappings:
  PorEntorno:
    dev:  { Tamano: pequeno }
    prod: { Tamano: grande }
Ver solución
yaml
AWSTemplateFormatVersion: '2010-09-09'
Mappings:
  PorRegion:
    us-east-1: { Sufijo: este }
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Join ['-', ['ej16', !FindInMap [PorRegion, us-east-1, Sufijo]]]
# ej16-este

🟡 Nivel 3 — Parámetros (17-24)

Ejercicio 17 — Un parámetro con valor por defecto

Convierte el nombre del bucket en un parámetro con Default: ej17-datos.

💡 ¿Sabías que…? — un parámetro con `Default` es opcional

Si el parámetro tiene Default, puedes crear la pila sin pasarlo. Sin Default, el CLI exige que lo indiques.

yaml
# ejemplo de referencia
Parameters:
  Entorno:
    Type: String
    Default: dev
Ver solución
yaml
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  NombreBucket:
    Type: String
    Default: ej17-datos
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties: { BucketName: !Ref NombreBucket }

Ejercicio 18 — Pasar el parámetro desde la línea de comandos

Crea la pila anterior pero con el nombre ej18-otro, sin tocar el archivo.

💡 ¿Sabías que…? — la misma plantilla, distintos resultados

Ese es el punto de los parámetros: una plantilla, muchos entornos. La sintaxis del CLI es ParameterKey=...,ParameterValue=....

bash
# ejemplo de referencia
aws cloudformation create-stack --stack-name demo --template-body file://t.yaml \
  --parameters ParameterKey=Entorno,ParameterValue=prod
Ver solución
bash
aws cloudformation create-stack --stack-name ej18 --template-body file://ej17.yaml \
  --parameters ParameterKey=NombreBucket,ParameterValue=ej18-otro

Ejercicio 19 — Restringir los valores admitidos

Añade un parámetro Entorno que solo acepte dev o prod.

💡 ¿Sabías que…? — `AllowedValues` documenta y protege

Restringir valores evita erratas (prd, produccion) y además sirve de documentación: quien lea la plantilla sabe qué opciones hay.

yaml
# ejemplo de referencia
Parameters:
  Tamano:
    Type: String
    AllowedValues: [pequeno, mediano, grande]
Ver solución
yaml
Parameters:
  Entorno:
    Type: String
    Default: dev
    AllowedValues: [dev, prod]

Ejercicio 20 — Documentar el parámetro

Añade una Description al parámetro Entorno explicando para qué sirve.

💡 ¿Sabías que…? — la plantilla también es documentación

Description aparece en la consola de AWS al desplegar. Una plantilla bien descrita es la diferencia entre que otro la use o te pregunte a ti.

yaml
# ejemplo de referencia
Parameters:
  Retencion:
    Type: Number
    Description: Dias que se conservan los registros
Ver solución
yaml
Parameters:
  Entorno:
    Type: String
    Default: dev
    AllowedValues: [dev, prod]
    Description: Entorno de despliegue; afecta al nombre de los recursos

Ejercicio 21 — Un parámetro numérico

Añade un parámetro Dias de tipo Number con valor 7, y devuélvelo como salida.

💡 ¿Sabías que…? — los tipos de parámetro más usados

String, Number, List<Number> y CommaDelimitedList. El tipo se valida al crear la pila: pasar texto a un Number la rechaza.

yaml
# ejemplo de referencia
Parameters:
  Reintentos: { Type: Number, Default: 3 }
Ver solución
yaml
Parameters:
  Dias:
    Type: Number
    Default: 7
Outputs:
  DiasConfigurados:
    Value: !Ref Dias
# 7

Ejercicio 22 — Un parámetro que no se muestra

Añade un parámetro Clave que no aparezca en la consola ni en los eventos.

💡 ¿Sabías que…? — `NoEcho` oculta, no cifra

NoEcho: true hace que el valor salga como **** en la consola y los eventos. No es cifrado: para secretos de verdad, Secrets Manager o SSM Parameter Store.

yaml
# ejemplo de referencia
Parameters:
  TokenApi:
    Type: String
    NoEcho: true
Ver solución
yaml
Parameters:
  Clave:
    Type: String
    Default: secreta
    NoEcho: true

Ejercicio 23 — Dos parámetros combinados

Usa Entorno y NombreBase para componer un nombre de bucket del tipo datos-dev.

💡 ¿Sabías que…? — `!Sub` acepta parámetros propios

Dentro de !Sub, ${MiParametro} se sustituye igual que los pseudo-parámetros. Se lee mucho mejor que anidar !Join.

yaml
# ejemplo de referencia
QueueName: !Sub '${Prefijo}-cola-${Entorno}'
Ver solución
yaml
Parameters:
  NombreBase: { Type: String, Default: datos }
  Entorno:    { Type: String, Default: dev, AllowedValues: [dev, prod] }
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${NombreBase}-${Entorno}'

Ejercicio 24 — Parámetro, pseudo-parámetro y texto, juntos

Compón un nombre que incluya un parámetro, la región y un texto fijo.

💡 ¿Sabías que…? — se pueden mezclar en la misma cadena

!Sub no distingue entre parámetros tuyos y pseudo-parámetros: todos se escriben igual, ${nombre}.

yaml
# ejemplo de referencia
Value: !Sub 'app-${Entorno}-${AWS::AccountId}'
Ver solución
yaml
Parameters:
  Entorno: { Type: String, Default: dev }
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'ej24-${Entorno}-${AWS::Region}'
# ej24-dev-us-east-1

🟠 Nivel 4 — Ciclo de vida (25-32)

Ejercicio 25 — Añadir un recurso a una pila existente

Sobre una pila ya creada, añade una cola SQS sin borrarla ni recrearla.

💡 ¿Sabías que…? — actualizar no es recrear

update-stack compara la plantilla nueva con la guardada y aplica solo la diferencia. Los recursos que no cambian se quedan intactos.

bash
# ejemplo de referencia
aws cloudformation update-stack --stack-name otra --template-body file://nueva.yaml
Ver solución
bash
# ej25.yaml: la plantilla anterior + un recurso nuevo
aws cloudformation update-stack --stack-name ej25 --template-body file://ej25.yaml
aws cloudformation describe-stacks --stack-name ej25 \
  --query 'Stacks[0].StackStatus' --output text
# UPDATE_COMPLETE

Ejercicio 26 — Quitar un recurso

Actualiza la pila borrando la cola de la plantilla. Comprueba que la cola desapareció.

💡 ¿Sabías que…? — lo que no está en la plantilla, se borra

Quitar un recurso del YAML no lo "deja como está": CloudFormation entiende que ya no debe existir y lo elimina. Es el error clásico de quien edita una plantilla a la ligera.

bash
# ejemplo de referencia — comprobar el efecto
aws sqs get-queue-url --queue-name la-cola >/dev/null 2>&1 || echo "ya no existe"
Ver solución
bash
aws cloudformation update-stack --stack-name ej25 --template-body file://ej26.yaml
aws cloudformation list-stack-resources --stack-name ej25 \
  --query 'StackResourceSummaries[*].LogicalResourceId' --output text

Ejercicio 27 — Forzar un orden con DependsOn

Haz que un bucket se cree después de una cola, aunque no dependa de ella.

💡 ¿Sabías que…? — dependencia implícita frente a explícita

Usar !Ref o !GetAtt sobre otro recurso ya crea una dependencia implícita. DependsOn es para cuando el orden importa pero no hay referencia que lo exprese.

yaml
# ejemplo de referencia
Recurso2:
  Type: AWS::SQS::Queue
  DependsOn: Recurso1
  Properties: { QueueName: segunda }
Ver solución
yaml
Resources:
  Avisos:
    Type: AWS::SQS::Queue
    Properties: { QueueName: ej27-avisos }
  Datos:
    Type: AWS::S3::Bucket
    DependsOn: Avisos
    Properties: { BucketName: ej27-datos }

Ejercicio 28 — Etiquetar la pila

Crea una pila con la etiqueta curso=aws y compruébala.

💡 ¿Sabías que…? — las etiquetas bajan a los recursos

Las etiquetas de la pila se propagan a los recursos que la soportan. En AWS real es la manera de saber qué equipo o proyecto genera cada línea de la factura.

bash
# ejemplo de referencia
aws cloudformation create-stack --stack-name demo --template-body file://t.yaml \
  --tags Key=equipo,Value=plataforma
Ver solución
bash
aws cloudformation create-stack --stack-name ej28 --template-body file://ej28.yaml \
  --tags Key=curso,Value=aws
aws cloudformation describe-stacks --stack-name ej28 \
  --query 'Stacks[0].Tags[0].[Key,Value]' --output text
# curso    aws

Ejercicio 29 — Preparar un cambio sin aplicarlo

Crea un change set llamado cs1 con una plantilla modificada, sin ejecutarlo.

💡 ¿Sabías que…? — el ensayo antes del estreno

Un change set responde "¿qué pasaría si aplico esto?" sin tocar nada. En producción es la diferencia entre un cambio revisado y una sorpresa.

bash
# ejemplo de referencia
aws cloudformation create-change-set --stack-name otra \
  --change-set-name prueba --template-body file://nueva.yaml
Ver solución
bash
aws cloudformation create-change-set --stack-name ej28 \
  --change-set-name cs1 --template-body file://ej29.yaml \
  --query 'Id' --output text

Ejercicio 30 — Comparar el inventario antes y después

Cuenta los recursos de una pila, actualízala añadiendo uno, y vuelve a contar.

💡 ¿Sabías que…? — medir antes y después

Comprobar un número antes y después es la forma más simple de verificar que un cambio hizo lo que esperabas.

bash
# ejemplo de referencia
aws cloudformation list-stack-resources --stack-name otra \
  --query 'length(StackResourceSummaries)' --output text
Ver solución
bash
aws cloudformation list-stack-resources --stack-name ej25 \
  --query 'length(StackResourceSummaries)' --output text   # p.ej. 1
aws cloudformation update-stack --stack-name ej25 --template-body file://ej30.yaml
sleep 5
aws cloudformation list-stack-resources --stack-name ej25 \
  --query 'length(StackResourceSummaries)' --output text   # 2

Ejercicio 31 — Confirmar que la plantilla guardada cambió

Tras actualizar, comprueba que get-template devuelve la versión nueva, no la original.

💡 ¿Sabías que…? — la pila recuerda la última aplicada

La plantilla almacenada se sustituye en cada actualización correcta. Comparar lo guardado con tu archivo local revela si alguien desplegó algo distinto a lo que hay en git.

bash
# ejemplo de referencia
aws cloudformation get-template --stack-name otra --query 'TemplateBody' --output text | grep Resources
Ver solución
bash
aws cloudformation get-template --stack-name ej25 --query 'TemplateBody' --output text \
  | grep -c 'AWS::SQS::Queue'

Ejercicio 32 — Seguir la historia completa

Muestra la secuencia de estados por los que pasó una pila que fue creada y actualizada.

💡 ¿Sabías que…? — los eventos cuentan la historia entera

Los eventos incluyen creación y actualizaciones. Leerlos en orden explica por qué una pila está como está.

bash
# ejemplo de referencia
aws cloudformation describe-stack-events --stack-name otra \
  --query 'StackEvents[*].ResourceStatus' --output text
Ver solución
bash
aws cloudformation describe-stack-events --stack-name ej25 \
  --query 'StackEvents[*].[LogicalResourceId,ResourceStatus]' --output text

🔴 Nivel 5 — Varias pilas y los límites del emulador (33-40)

Ejercicio 33 — Una pila con tres servicios

Monta en una sola pila un bucket, una tabla DynamoDB y una cola SQS.

💡 ¿Sabías que…? — DynamoDB necesita su clave declarada

Una tabla exige AttributeDefinitions y KeySchema coherentes: todo atributo declarado debe usarse en la clave. Con PAY_PER_REQUEST no hace falta indicar capacidad.

yaml
# ejemplo de referencia
Tabla:
  Type: AWS::DynamoDB::Table
  Properties:
    TableName: referencia
    BillingMode: PAY_PER_REQUEST
    AttributeDefinitions: [{ AttributeName: clave, AttributeType: S }]
    KeySchema: [{ AttributeName: clave, KeyType: HASH }]
Ver solución
yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  Datos:
    Type: AWS::S3::Bucket
    Properties: { BucketName: ej33-datos }
  Auditoria:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: Ej33Auditoria
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions: [{ AttributeName: id, AttributeType: S }]
      KeySchema: [{ AttributeName: id, KeyType: HASH }]
  Avisos:
    Type: AWS::SQS::Queue
    Properties: { QueueName: ej33-avisos }

Ejercicio 34 — Devolver las tres identidades

Haz que esa pila devuelva el nombre del bucket, el nombre de la tabla y el ARN de la cola.

💡 ¿Sabías que…? — cada tipo devuelve algo distinto con `!Ref`

!Ref sobre un bucket da su nombre; sobre una tabla, su nombre; sobre una cola, su URL. Cuando dudes, !GetAtt X.Arn es lo más explícito.

yaml
# ejemplo de referencia
Outputs:
  Tabla: { Value: !Ref MiTabla }
Ver solución
yaml
Outputs:
  Bucket: { Value: !Ref Datos }
  Tabla:  { Value: !Ref Auditoria }
  Cola:   { Value: !GetAtt Avisos.Arn }

Ejercicio 35 — Publicar un valor para otras pilas

Exporta el ARN de la cola con el nombre ej35-cola-arn.

💡 ¿Sabías que…? — el nombre exportado es único en la cuenta

Dos pilas no pueden exportar el mismo nombre. Conviene prefijarlos con el nombre de la pila o del proyecto para no chocar.

yaml
# ejemplo de referencia
Outputs:
  Bus:
    Value: !GetAtt MiCola.Arn
    Export: { Name: plataforma-bus-arn }
Ver solución
yaml
Outputs:
  ColaArn:
    Value: !GetAtt Avisos.Arn
    Export:
      Name: ej35-cola-arn

Ejercicio 36 — Consultar lo exportado

Lista los valores exportados y localiza el tuyo.

💡 ¿Sabías que…? — los exports son un catálogo de la cuenta

list-exports muestra todo lo publicado por cualquier pila. Es la forma de descubrir qué puedes reutilizar sin preguntar.

bash
# ejemplo de referencia
aws cloudformation list-exports --query 'Exports[*].Name' --output text
Ver solución
bash
aws cloudformation list-exports \
  --query 'Exports[?Name==`ej35-cola-arn`].Value' --output text
# arn:aws:sqs:us-east-1:000000000000:ej33-avisos

Ejercicio 37 — Consumirlo desde otra pila

Crea una segunda pila que devuelva como salida el valor exportado por la primera.

💡 ¿Sabías que…? — así se compone una arquitectura por capas

Una pila de red, otra de datos, otra de aplicación: cada una exporta lo que las de arriba necesitan. Evita una plantilla gigante imposible de revisar.

yaml
# ejemplo de referencia
Outputs:
  BusHeredado:
    Value: !ImportValue plataforma-bus-arn
Ver solución
yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  Local:
    Type: AWS::S3::Bucket
    Properties: { BucketName: ej37-local }
Outputs:
  ColaHeredada:
    Value: !ImportValue ej35-cola-arn

Ejercicio 38 — El candado entre pilas

Con la pila consumidora viva, intenta borrar la que exporta. Observa qué ocurre y explica por qué.

💡 ¿Sabías que…? — un export en uso bloquea el borrado

Es una protección: si se pudiera borrar, la pila consumidora se quedaría apuntando a algo inexistente. Primero se borra quien importa, después quien exporta.

bash
# ejemplo de referencia — el orden correcto
aws cloudformation delete-stack --stack-name consumidora
aws cloudformation delete-stack --stack-name exportadora
Ver solución
bash
aws cloudformation delete-stack --stack-name ej33     # la que exporta
aws cloudformation describe-stacks --stack-name ej33 \
  --query 'Stacks[0].StackStatus' --output text
# Orden correcto: primero ej37 (importa), despues ej33 (exporta)

Ejercicio 39 — Descubre una diferencia con AWS real

Crea una pila con un parámetro Entorno en dev y un recurso marcado Condition: EsProd. Comprueba si el recurso se creó. ¿Coincide con lo que haría AWS?

💡 ¿Sabías que…? — cómo deberían funcionar las condiciones

En AWS, Conditions decide si un recurso llega a existir. Con la condición en falso, el recurso se omite y no aparece en el inventario de la pila.

yaml
# ejemplo de referencia — lo que AWS haría
Conditions:
  EsGrande: !Equals [!Ref Tamano, grande]
Resources:
  Extra:
    Type: AWS::SQS::Queue
    Condition: EsGrande      # con Tamano=pequeno, AWS NO la crea
    Properties: { QueueName: solo-si-grande }
Ver solución
bash
aws cloudformation list-stack-resources --stack-name ej39 \
  --query 'StackResourceSummaries[*].LogicalResourceId' --output text
# Base    SoloEnProd     ← el emulador lo creó igual
aws sqs get-queue-url --queue-name ej39-solo-prod    # existe

El emulador ignora Condition: crea el recurso aunque la condición sea falsa. En AWS real solo aparecería Base. Verificado el 2026-08-08.

Ejercicio 40 — Un recurso que no existe

Escribe una plantilla con Type: AWS::Inventado::NoExiste y crea la pila. ¿Falla?

💡 ¿Sabías que…? — qué haría AWS ante un tipo desconocido

AWS rechaza la plantilla o marca la pila CREATE_FAILED y hace rollback, dejándolo todo como estaba. Ese rollback automático es una de sus mayores garantías.

bash
# ejemplo de referencia — leer el motivo del fallo
aws cloudformation describe-stack-events --stack-name fallida \
  --query 'StackEvents[?ResourceStatus==`CREATE_FAILED`].ResourceStatusReason' --output text
Ver solución
bash
aws cloudformation describe-stacks --stack-name ej40 \
  --query 'Stacks[0].StackStatus' --output text
# CREATE_COMPLETE     ← el emulador lo acepta

El emulador no valida los tipos de recurso ni hace rollback. Consecuencia práctica: una plantilla que aquí pasa puede fallar en AWS. Para lo que se aprende en esta clase da igual, pero conviene no confiarse. Verificado el 2026-08-08.


❓ Preguntas y respuestas (autoevaluación)

1. ¿Cuál es la única sección obligatoria de una plantilla?

Resources. Todo lo demás (Parameters, Mappings, Outputs, Description) es opcional.

2. ¿Qué diferencia hay entre el nombre lógico y el físico de un recurso?

El lógico solo existe dentro de la plantilla, para referenciarlo. El físico es el nombre real del recurso en AWS.

3. ¿Qué devuelve !Ref sobre una cola SQS?

Su URL. Para el ARN hay que usar !GetAtt MiCola.Arn.

4. ¿Qué pasa si quitas un recurso de la plantilla y actualizas la pila?

CloudFormation lo elimina. Lo que no está en la plantilla, no debe existir.

5. ¿Para qué sirve DependsOn si ya existen las dependencias implícitas?

Para forzar un orden cuando dos recursos no se referencian entre sí pero uno debe crearse antes.

6. ¿Qué hace NoEcho: true exactamente?

Oculta el valor en la consola y los eventos. No lo cifra: para secretos reales, Secrets Manager o SSM.

7. ¿Por qué no se puede borrar una pila cuyo export está en uso?

Porque dejaría a la pila consumidora apuntando a un valor inexistente. Primero se borra quien importa.

8. ¿Qué ventaja tiene un change set frente a actualizar directamente?

Muestra qué cambiaría sin aplicar nada, así el cambio se revisa antes de tocar producción.

9. ¿Dónde miras primero cuando una pila falla?

En describe-stack-events, y concretamente en el primer evento de fallo: los siguientes suelen ser consecuencia.

10. ¿Qué dos comportamientos del emulador no coinciden con AWS, y por qué importa?

Ignora Conditions (crea recursos que AWS omitiría) y no valida tipos de recurso ni hace rollback. Importa porque una plantilla válida aquí puede fallar en AWS real.


📎 Apuntes relacionados

➡️ Siguiente

Clase 2 — Bases de datos gestionadas (RDS): pedir un PostgreSQL por API, conectarse con psql y entender por qué el motor tarda más que la API en aceptar conexiones.

Apuntes de AWS con Floci