Abrir la caja negra
Anatomía de un template — CloudFormation
De la caja negra al código
La Semana 1 terminó con la aplicación en línea, desplegada a partir de un archivo que
tratamos como una caja negra: taller-semana1.yaml. Esta semana se abre esa caja.
El objetivo no es memorizar la sintaxis de CloudFormation, sino saber leer un template: reconocer qué describe, encontrar dónde se define cada recurso, y entender cómo se conectan entre sí. Esa lectura es lo que convierte la infraestructura de algo que "alguien configuró una vez" en algo que el equipo entiende, revisa, y modifica.
Por qué infraestructura como código
Configurar recursos a mano desde la consola —click-ops— funciona una vez. El problema aparece después: nadie recuerda exactamente qué se configuró, no hay registro de los cambios, y reproducir el mismo ambiente en otra región o cuenta significa repetir decenas de clics sin garantía de que el resultado sea idéntico.
La infraestructura como código (IaC) resuelve esto describiendo los recursos en un
archivo de texto versionado. El archivo es la fuente de verdad: se revisa en un pull
request, se guarda en el repositorio junto al código, y produce siempre el mismo
ambiente. La diferencia es la misma que vimos entre subir archivos a mano y hacer
git push: un proceso reproducible en lugar de una serie de pasos manuales.
Click-ops vs. infraestructura como código
| Click-ops | Infraestructura como código |
|---|---|
| Pasos manuales en la consola | Recursos descritos en un archivo |
| Sin registro de cambios | Versionado en git |
| Difícil de reproducir | Idéntico en cada lanzamiento |
| El conocimiento vive en una persona | El conocimiento vive en el repositorio |
Las secciones de un template
Un template de CloudFormation es un archivo YAML (o JSON) con un conjunto de secciones
de nivel superior. Solo una es obligatoria —Resources—; las demás son opcionales y
aparecen según lo que el template necesite.
| Sección | Para qué sirve |
|---|---|
Resources | Obligatoria. Los recursos a crear: la tabla, el clúster, el balanceador. |
Parameters | Valores que se proveen al lanzar el stack (por ejemplo, el URI de la imagen). |
Outputs | Valores que el stack expone al terminar (por ejemplo, la URL del ALB). |
Mappings | Tablas de búsqueda fijas (por ejemplo, una AMI distinta por región). |
Conditions | Reglas que activan o desactivan recursos según los parámetros. |
En la Semana 1 ya se interactuó con tres de ellas sin saberlo: se completó un parámetro
(el URI de la imagen), y se leyó un output (la URL del ALB) que el template expuso al
llegar a CREATE_COMPLETE.
Recursos: nombre lógico y nombre físico
Cada recurso dentro de Resources tiene un nombre lógico (logical ID): el
identificador que se le da dentro del template. CloudFormation, al crear el recurso,
le asigna además un nombre físico: el identificador real en AWS.
Resources:
TablaApp: # nombre lógico (se elige en el template)
Type: AWS::DynamoDB::Table
Properties:
TableName: taller-datos # nombre físico (opcional)
BillingMode: PAY_PER_REQUEST
El nombre lógico (TablaApp) es cómo el resto del template se refiere a este recurso.
El nombre físico (taller-datos) es cómo aparece en la consola de DynamoDB. Si no se
especifica un nombre físico, CloudFormation genera uno único automáticamente —una
práctica habitual, porque evita colisiones de nombres al lanzar el mismo template
varias veces.
Funciones intrínsecas: conectar los recursos
Los recursos rara vez son independientes: el servicio ECS necesita el nombre del clúster, el ALB necesita el ID de la subred, la task definition necesita el URI de la imagen. Las funciones intrínsecas permiten referirse a un valor que solo se conoce cuando el stack se crea, sin escribirlo a mano.
Las tres más comunes:
-
!Ref— devuelve el valor de un parámetro, o el nombre físico de un recurso.Image: !Ref ImageUri # el valor del parámetro ImageUri -
!GetAtt— devuelve un atributo específico de un recurso.DNSName: !GetAtt BalanceadorApp.DNSName # el DNS del ALB creado -
!Sub— sustituye variables dentro de una cadena de texto.Mensaje: !Sub "Tabla ${TablaApp} en la región ${AWS::Region}"
!Ref y !GetAtt son la forma corta de Fn::Ref y Fn::GetAtt; ambas notaciones
son equivalentes y se encontrará con las dos al leer templates de la documentación de
AWS.
¿Por qué YAML y no JSON?
CloudFormation acepta los dos formatos, y son intercambiables. Este taller usa YAML
porque admite comentarios (líneas con #), es más compacto, y ofrece la sintaxis corta
de las funciones intrínsecas (!Ref en lugar de { "Ref": "..." }). JSON sigue siendo
común en templates generados por herramientas. Saber leer ambos es útil; para escribir
a mano, YAML es más cómodo.
Leer el template paso a paso
El template de la Semana 1, por dentro
Ya se conocen las secciones de un template. Ahora se recorre el archivo real que desplegó
la aplicación, taller-semana1.yaml, recurso por recurso, conectando cada bloque con lo
que se vio en la consola la semana pasada.
Abrir el archivo taller-semana1.yaml en el editor de texto —es el mismo que se subió a
CloudFormation. Se leerá de arriba hacia abajo.
El encabezado y los parámetros
El template comienza declarando su versión y los parámetros que recibe al lanzarse:
AWSTemplateFormatVersion: "2010-09-09"
Description: Taller AWS DevOps — Semana 1
Parameters:
ImageUri:
Type: String
Description: URI de la imagen en ECR, con etiqueta.
Aquí está el parámetro que se completó al crear el stack: ImageUri. Es el único
valor que el template no puede conocer por sí solo —depende de la imagen que el build
publicó en ECR— y por eso se pide al lanzarlo.
La tabla de DynamoDB
Resources:
TablaApp:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
Este es el recurso que se vio en DynamoDB → Tables. Note que no tiene TableName:
CloudFormation le asignó un nombre físico generado, lo que permite lanzar el template
varias veces sin colisiones.
El clúster y el servicio ECS
ClusterApp:
Type: AWS::ECS::Cluster
ServicioApp:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref ClusterApp
DesiredCount: 1
LaunchType: FARGATE
TaskDefinition: !Ref TareaApp
Aquí aparece la primera función intrínseca importante: Cluster: !Ref ClusterApp
conecta el servicio con el clúster creado más arriba, sin escribir su nombre a mano.
Lo mismo TaskDefinition: !Ref TareaApp. Estos son los recursos que se vieron en
ECS → Clusters: el clúster, y dentro de él el servicio con su tarea en estado
RUNNING.
La task definition y la imagen
TareaApp:
Type: AWS::ECS::TaskDefinition
Properties:
RequiresCompatibilities: [FARGATE]
Cpu: "256"
Memory: "512"
ContainerDefinitions:
- Name: app
Image: !Ref ImageUri
PortMappings:
- ContainerPort: 8080
Esta es la pieza clave: Image: !Ref ImageUri es donde el URI pegado al lanzar
el stack se convierte en la imagen que ejecuta el contenedor. El parámetro del
encabezado llega hasta aquí a través de una sola función intrínseca.
El balanceador y la salida
BalanceadorApp:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Type: application
Outputs:
ALBUrl:
Description: URL pública de la aplicación
Value: !Sub "http://${BalanceadorApp.DNSName}"
El output ALBUrl es el valor que se copió de la pestaña Outputs. La función !Sub
construye la URL completa insertando el atributo DNSName del balanceador —un valor que
solo existe una vez que AWS crea el ALB.
El template completo tiene más recursos
Por claridad, los bloques anteriores omiten los grupos de seguridad, los roles de IAM,
el listener del ALB, el target group, y la configuración de red (subredes, VPC). El
archivo real los incluye: cada uno aparece como un recurso más en Resources, conectado
a los demás con !Ref y !GetAtt. En la sección de buenas prácticas se verá cómo navegar
un template largo sin perderse.
Ejercicio 9 — Seguir el rastro de la imagen
Abrir taller-semana1.yaml y seguir el recorrido del URI de la imagen: desde el parámetro
que lo recibe, hasta el recurso que finalmente lo usa para ejecutar el contenedor.
Identificar el nombre del parámetro, la función intrínseca que lo transporta, y el
recurso de destino.
- En la sección
Parameters, el parámetro se llamaImageUri(tipoString). Es el valor que se pegó al crear el stack. - En la sección
Resources, buscar el recurso de tipoAWS::ECS::TaskDefinition(llamadoTareaApp). - Dentro de
ContainerDefinitions, la propiedadImage: !Ref ImageUries donde el parámetro se usa. La función!Refdevuelve el valor del parámetro y lo asigna como imagen del contenedor. - El recorrido completo es:
Parameters.ImageUri→!Ref ImageUri→TareaApp.ContainerDefinitions[0].Image. Un único!Refconecta lo que se escribió al lanzar el stack con la imagen que ejecuta Fargate.
Actualizar un stack con seguridad
Cambiar lo que ya está desplegado
Un template no se lanza una sola vez y se olvida: la infraestructura cambia. Se ajusta la memoria de un contenedor, se agrega un recurso, se sube el número de tareas. En CloudFormation, cambiar el ambiente no significa borrarlo y recrearlo: significa actualizar el stack con una nueva versión del template, y dejar que CloudFormation calcule qué modificar.
La pregunta crítica al actualizar es: ¿qué va a hacer exactamente este cambio antes de aplicarlo? Esa pregunta la responde un change set.
Change sets: ver antes de aplicar
Un change set es una vista previa. Se sube el template modificado, y CloudFormation calcula la diferencia contra el estado actual sin tocar nada todavía. El resultado es una lista de acciones: qué recursos se modifican, cuáles se agregan, cuáles se eliminan, y —lo más importante— cuáles requieren reemplazo.
Este es el ciclo de reconciliación de CloudFormation: comparar el estado deseado (el template) contra el estado real (el stack), calcular la diferencia, y aplicar solo lo necesario para que coincidan.
flowchart LR
T["Template<br/>(estado deseado)"] --> D{"CloudFormation<br/>compara"}
S[("Stack actual<br/>(estado real)")] --> D
D -->|change set| P["Plan de cambios:<br/>crear / modificar / eliminar"]
P --> A["Aplicar al stack"]
A --> S
El estado real converge hacia el deseado en cada actualización. La misma idea que
encontrará en ECS (un servicio reconcilia las tareas hacia el DesiredCount) y, en
general, en toda la infraestructura declarativa.
Tres formas de aplicar un cambio
| Acción | Qué ocurre |
|---|---|
| Modificación | El recurso se actualiza en sitio, sin interrupción. |
| Sin interrupción | El cambio no afecta el servicio (por ejemplo, una etiqueta). |
| Reemplazo | El recurso se destruye y se crea de nuevo — puede causar interrupción. |
El reemplazo es el que hay que vigilar: cambiar ciertas propiedades (por ejemplo, el
nombre físico de una tabla) obliga a CloudFormation a crear un recurso nuevo y borrar el
anterior. El change set lo marca explícitamente con Replacement: True, dando la
oportunidad de detenerse antes de perder datos.
Práctica guiada: escalar la aplicación a dos tareas
Esta práctica aumenta el número de contenedores en ejecución de uno a dos, modificando el template y aplicando el cambio con un change set.
Modificar el template
-
Abrir
taller-semana1.yamlen el editor. -
Localizar el recurso
ServicioApp(tipoAWS::ECS::Service). -
Cambiar la propiedad
DesiredCountde1a2:ServicioApp: Type: AWS::ECS::Service Properties: DesiredCount: 2 # antes: 1 -
Guardar el archivo.
Crear el change set
- En la consola de AWS, abrir CloudFormation y seleccionar el stack
taller-<su-nombre>. - Pulsar Stack actions → Create change set for current stack.
- Seleccionar Replace existing template → Upload a template file, y subir el
taller-semana1.yamlmodificado. - Avanzar por las pantallas (los parámetros siguen igual) hasta llegar a la vista del
change set. Pulsar Create change set y asignarle un nombre, por ejemplo
escalar-a-dos.
Revisar y aplicar
- CloudFormation calcula la diferencia y muestra la tabla de cambios. Buscar la fila
correspondiente a
ServicioApp: la acción debe ser Modify, y la columna Replacement debe decir False —el servicio se actualiza en sitio, sin recrearse. - Confirmar que ningún otro recurso aparece como
Trueen Replacement. - Pulsar Execute change set. CloudFormation aplica solo ese cambio.
- En la pestaña Events, seguir la actualización hasta que el stack vuelva a UPDATE_COMPLETE.
Verificar el resultado
- Abrir ECS → Clusters → el clúster → el servicio. La cuenta de tareas deseadas
ahora es 2, y se verán dos tareas en estado
RUNNING. - En EC2 → Target Groups, el target group del ALB muestra dos destinos sanos (healthy): el balanceador ya reparte tráfico entre ambas tareas.
Cuando una actualización falla: el rollback
Si un cambio no puede aplicarse —un valor inválido, un permiso faltante— CloudFormation
no deja el stack a medias. Inicia un rollback: deshace los cambios ya aplicados y
devuelve el stack al último estado bueno conocido. El estado pasa por
UPDATE_ROLLBACK_IN_PROGRESS y termina en UPDATE_ROLLBACK_COMPLETE.
Esto es una red de seguridad: una actualización fallida no rompe el ambiente, lo
devuelve a como estaba. La causa del fallo aparece en la pestaña Events, en el
primer evento con estado ..._FAILED.
¿Qué es el drift?
El drift ocurre cuando alguien modifica a mano, desde la consola, un recurso que un
stack gestiona —por ejemplo, cambia el DesiredCount del servicio directamente en ECS.
El template y la realidad dejan de coincidir. CloudFormation ofrece Detect drift
(en Stack actions) para comparar el estado real contra el template y listar las
diferencias. La regla práctica: si un recurso lo gestiona un stack, cámbielo solo a
través del stack.
Ejercicio 10 — Actualizar el stack con un change set
Aumentar el número de tareas del servicio de una a dos. Hacerlo modificando el template, creando un change set, verificando que el servicio se modifica sin reemplazo, y ejecutando el cambio. Confirmar que hay dos tareas en ejecución.
- En
taller-semana1.yaml, cambiarDesiredCountde1a2en el recursoServicioApp, y guardar. - En CloudFormation, seleccionar el stack y pulsar Stack actions → Create change set for current stack.
- Seleccionar Replace existing template → Upload a template file y subir el archivo modificado.
- Avanzar hasta crear el change set; asignarle un nombre como
escalar-a-dos. - En la tabla de cambios, confirmar que
ServicioAppaparece como Modify con Replacement: False. - Pulsar Execute change set y, en Events, esperar a UPDATE_COMPLETE.
- En ECS → Clusters → el servicio, confirmar Desired tasks: 2 y dos tareas en
estado
RUNNING.
Preguntas puente
Estas preguntas cierran la sesión presencial y abren la remota. Conviene pensarlas mientras lo visto hoy está fresco: se leyó el template y se actualizó el stack con un change set. No buscar las respuestas de inmediato; intentar razonarlas desde lo que se hizo. Al comenzar la sesión remota, se discuten antes de seguir con buenas prácticas y contenedores.
Pregunta 1
Se cambió el DesiredCount del servicio con un change set. ¿Qué pasaría si, en cambio,
se lo cambiara a mano desde la consola de ECS? ¿Qué pensaría CloudFormation del estado del
stack?
El cambio surtiría efecto —ECS pondría a correr el número de tareas indicado— pero el
template y la realidad dejarían de coincidir. Eso es drift. CloudFormation seguiría
creyendo que DesiredCount es el valor de la última actualización aplicada por el stack,
porque no se entera de los cambios hechos por fuera.
El problema aparece en la siguiente actualización del stack: CloudFormation podría revertir el cambio manual sin avisar, al aplicar el valor del template. La herramienta Detect drift existe justamente para detectar estas diferencias. La regla práctica: si un recurso lo gestiona un stack, cámbielo solo a través del stack.
Pregunta 2
El servicio mantiene dos tareas en ejecución. Si una de ellas se cae, ¿quién arranca una nueva, y cómo sabe esa tarea nueva qué imagen ejecutar?
Quien la arranca es el servicio de ECS. Un servicio no es solo "lanzar un
contenedor": es un controlador que vigila el número de tareas en ejecución contra el
DesiredCount deseado. Si una tarea termina o falla, el servicio nota la diferencia
(2 deseadas, 1 corriendo) y lanza una de reemplazo automáticamente.
La tarea nueva sabe qué imagen usar porque el servicio la crea a partir de su task definition, que es la plantilla de ejecución: especifica la imagen, la CPU, la memoria, los puertos. El servicio aporta el cuántas y mantenerlas vivas; la task definition aporta el cómo es cada una. Esa distinción es el tema central de la próxima sección.
Pregunta 3
Si se borra el stack de CloudFormation, ¿sobreviven los datos guardados en la tabla de DynamoDB? ¿Por qué?
Con el template tal como está, no: la tabla la creó y la gestiona el stack, así que al borrar el stack se borra la tabla y, con ella, los datos.
Esto se puede cambiar. CloudFormation permite declarar en un recurso una
DeletionPolicy: Retain, que le indica conservar ese recurso aunque se borre el stack.
Es una decisión deliberada: el ambiente de ejecución (clúster, servicio, balanceador)
tiene sentido destruirlo y recrearlo, pero los datos suelen querer sobrevivir a esos
ciclos. Veremos DeletionPolicy entre las buenas prácticas de la próxima sesión.