Casi todos los proyectos empiezan igual: una Lambda creada en la consola, un rol con un permiso agregado "solo para probar", una tabla creada con la CLI. Funciona, pero si mañana alguien borra un rol por error, reconstruirlo exactamente igual es una tarde de arqueología.
AWS CDK permite describir esa infraestructura con TypeScript y desplegarla con un comando. En esta parte definimos con CDK las piezas del proyecto de ejemplo de la serie y vemos cómo adoptar recursos que ya creaste a mano.
Qué es CDK
CDK es un framework que convierte código (TypeScript, Python, Java...) en plantillas de CloudFormation. Escribes clases como dynamodb.Table o lambda.Function, y CDK genera la plantilla y la despliega.
Ventajas frente a escribir CloudFormation a mano:
- Abstracciones con buenos valores por defecto.
table.grantReadData(fn)genera la política IAM exacta. - El lenguaje completo: bucles, funciones y tipos para no repetir configuración.
- Revisión antes de desplegar:
cdk diffmuestra qué va a cambiar.
Empezar
npm install -g aws-cdk
mkdir infra && cd infra
cdk init app --language typescript
cdk bootstrap # una vez por cuenta y región
cdk bootstrap crea los recursos que CDK necesita para desplegar (un bucket para los assets y unos roles).
El stack del proyecto de ejemplo
import * as cdk from "aws-cdk-lib";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as iam from "aws-cdk-lib/aws-iam";
import * as iot from "aws-cdk-lib/aws-iot";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";
export class CasaDemoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const thingName = "mi-esp32";
// 1. Tabla de dispositivos (con estado: no se borra con el stack)
const tabla = new dynamodb.Table(this, "Dispositivos", {
partitionKey: { name: "casaId", type: dynamodb.AttributeType.STRING },
sortKey: { name: "dispositivoId", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.RETAIN,
pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true },
});
// 2. Lambda de la API (parte 8)
const api = new NodejsFunction(this, "ApiOrdenes", {
entry: "lambda/api-ordenes.ts",
runtime: lambda.Runtime.NODEJS_22_X,
timeout: cdk.Duration.seconds(10),
memorySize: 256,
environment: { THING_NAME: thingName, TABLA: tabla.tableName },
});
tabla.grantReadData(api);
api.addToRolePolicy(
new iam.PolicyStatement({
actions: ["iot:UpdateThingShadow"],
resources: [`arn:aws:iot:${this.region}:${this.account}:thing/${thingName}`],
})
);
const url = api.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.NONE });
// 3. Política de IoT del dispositivo (parte 13)
new iot.CfnPolicy(this, "PoliticaDispositivo", {
policyName: "casa-demo-dispositivo",
policyDocument: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Action: "iot:Connect",
Resource: `arn:aws:iot:${this.region}:${this.account}:client/\${iot:Connection.Thing.ThingName}`,
},
// ... Subscribe, Receive y Publish sobre los tópicos de su Shadow
],
},
});
new cdk.CfnOutput(this, "ApiUrl", { value: url.url });
}
}
Detalles a notar:
grantReadDatagenera los permisos de lectura sobre la tabla y sus índices, sin escribir ARNs a mano.- Los permisos de IoT sí van a mano, con el recurso exacto. Evita
resources: ["*"]. - El
\${...}escapa la variable de política de IoT para que TypeScript no intente interpolarla. NodejsFunctioncompila y empaqueta la Lambda con esbuild, incluidas sus dependencias.
Recursos con estado: el cuidado principal
Si CDK gestiona un recurso, un cambio en el código puede reemplazarlo (borrar y crear otro), y borrar el stack puede eliminarlo. Para recursos sin estado (Lambdas, roles) no importa. Para recursos con estado, sí:
| Recurso | Riesgo si se recrea | Qué hacer |
|---|---|---|
| Tabla de DynamoDB | Pierdes todos los datos | removalPolicy: RETAIN y respaldos |
| User Pool de Cognito | Se invalidan todas las cuentas | RETAIN, y cuidado con cambios que fuerzan reemplazo |
| Certificados de IoT | Los dispositivos dejan de conectar | Gestionarlos fuera del stack |
Antes de cada despliegue, ejecuta cdk diff y busca la palabra replace. Algunos cambios aparentemente inocentes (como renombrar la clave de una tabla o ciertos atributos de un User Pool) obligan a reemplazar el recurso.
Otra opción válida: dejar los recursos con estado fuera del stack y solo referenciarlos por nombre o ARN para dar permisos:
const tabla = dynamodb.Table.fromTableName(this, "TablaExistente", "dispositivos");
tabla.grantReadData(api); // funciona igual, pero CDK nunca la crea ni la borra
Si ya creaste todo a mano
Es lo más habitual. Pasos para llevarlo a CDK sin romper nada:
- Haz un inventario en solo lectura: cada Lambda (runtime, memoria, variables), cada rol con el texto exacto de sus políticas (
aws iam get-role-policy), cada tabla con sus índices. - Escribe el código para describir exactamente lo que existe, no para mejorarlo todavía. Usa los mismos nombres.
- Compara la plantilla generada (
cdk synth) con el inventario. - Adopta los recursos uno por uno con
cdk import, empezando por los más simples (roles), y comprueba después de cada uno que nada cambió. - Solo entonces, mejora: quita permisos que sobran, unifica nombres.
Un efecto secundario útil: describir la infraestructura en código obliga a mirar cada permiso. Casi siempre aparecen permisos que ya nadie usa.
Comandos del día a día
npx tsc --noEmit # el código compila
npx cdk synth # genera la plantilla de CloudFormation
npx cdk diff # qué va a cambiar en la cuenta
npx cdk deploy # despliega (pide confirmación si cambian permisos IAM)
Errores comunes
- "This stack uses assets, so the toolkit stack must be deployed": falta
cdk bootstrapen esa cuenta y región. NodejsFunctionno encuentra el archivo de entrada: la ruta deentryes relativa a donde ejecutascdk, y busca unpackage-lock.jsonpara saber la raíz del proyecto; si tus Lambdas están en otra carpeta, indicaprojectRootydepsLockFilePath.- El despliegue falla porque el recurso ya existe: estás creando con CDK algo que existe a mano con el mismo nombre. Impórtalo o referéncialo.
- Un rol que dejó de funcionar tras un despliegue: CDK reemplaza las políticas en línea que gestiona. No mezcles cambios manuales con recursos gestionados por CDK.
Preguntas frecuentes
¿CDK o Terraform?
Los dos sirven. CDK encaja si tu proyecto ya es TypeScript y solo usas AWS. Terraform tiene ventajas con varios proveedores o si tu equipo ya lo conoce. Los comparo en AWS CDK vs Terraform.
¿CDK cuesta algo?
No. Pagas los recursos que despliegas y unos céntimos por el bucket de assets del bootstrap.
¿Tengo que pasar todo a CDK de una vez?
No. Puedes empezar por las Lambdas y sus roles, que son los que más cuesta reconstruir a mano, y referenciar el resto por nombre.