Problema

Los ingenieros de software con experiencia en .NET a menudo se encuentran con dos barreras al intentar usar AWS CDK: la curva de aprendizaje de la infraestructura como código y la fricción al empaquetar y depurar Lambdas sin Docker. El patrón típico es: “Quiero lanzar una API basada en Lambda, conectarla a una base de datos y que todo quede versionado en código, pero cada paso me obliga a leer documentación de CloudFormation, a batallar con dotnet lambda package y a perder tiempo en permisos de IAM”. El resultado es un ciclo de iteración lento que desincentiva a los equipos de backend a adoptar CDK.

Causa

  1. Falta de referencia concreta en .NET
    La mayoría de los tutoriales oficiales están en TypeScript. Cuando se cambia a C# el flujo de empaquetado y de síntesis cambia ligeramente, lo que genera dudas sobre dónde colocar los artefactos y cómo invocar cdk synth.

  2. Gestión de dependencias de Lambda
    Empaquetar una Lambda .NET sin Docker implica que el runtime de Lambda (Amazon Linux) difiere del entorno de desarrollo (Windows). Si el proyecto usa paquetes nativos, el binario resultante no será compatible y la función fallará al iniciar.

  3. Políticas de IAM implícitas
    CDK crea roles automáticamente, pero al añadir recursos como RDS o SQS, es fácil olvidar los permisos de secretsmanager:GetSecretValue o rds-db:connect. La falta de logs claros lleva a errores de autorización que aparecen solo en CloudWatch.

  4. Visibilidad limitada del CloudFormation generado
    Los ingenieros acostumbrados a leer código fuente pueden perderse en los miles de recursos que CloudFormation produce, lo que dificulta la depuración de errores de dependencia o de orden de creación.

Solución

Adoptar un flujo de trabajo estructurado que combine: (a) un proyecto CDK en C# bien organizado, (b) empaquetado local de Lambdas usando dotnet lambda package con la opción --framework net6.0 y --output-package, (c) pruebas unitarias de la lógica Lambda antes del despliegue, y (d) uso de cdk diff y cdk deploy --require-approval never para iteraciones rápidas.

Paso a paso

  1. Inicializar el proyecto CDK en C#

    mkdir MyInfra && cd MyInfra
    cdk init app --language csharp
    
  2. Crear una solución .NET para la Lambda
    Dentro del mismo repositorio, genera un proyecto de tipo Amazon.Lambda.AspNetCoreServer o Amazon.Lambda.SimpleFunction. Mantén el proyecto bajo src/LambdaFunction.

  3. Empaquetar sin Docker

    dotnet lambda package --configuration Release --framework net6.0 --output-package ./bin/Release/net6.0/function.zip
    

    Este comando produce un ZIP listo para ser subido a S3 y referenciado por el construct Function de CDK.

  4. Definir la pila CDK
    En MyInfraStack.cs:

    using Amazon.CDK;
    using Amazon.CDK.AWS.Lambda;
    using Amazon.CDK.AWS.APIGateway;
    using Amazon.CDK.AWS.RDS;
    using Amazon.CDK.AWS.EC2;
    
    public class MyInfraStack : Stack
    {
        public MyInfraStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            // VPC básica
            var vpc = new Vpc(this, "AppVpc", new VpcProps { MaxAzs = 2 });
    
            // Base de datos PostgreSQL
            var db = new DatabaseInstance(this, "Postgres", new DatabaseInstanceProps
            {
                Engine = DatabaseInstanceEngine.Postgres(new PostgresInstanceEngineProps { Version = PostgresEngineVersion.VER_13_4 }),
                InstanceType = InstanceType.Of(InstanceClass.BURSTABLE2, InstanceSize.SMALL),
                Vpc = vpc,
                DeletionProtection = false,
                Credentials = Credentials.FromGeneratedSecret("postgres")
            });
    
            // Lambda
            var lambda = new Function(this, "MyDotnetLambda", new FunctionProps
            {
                Runtime = Runtime.DOTNET_6,
                Code = Code.FromAsset("./src/LambdaFunction/bin/Release/net6.0/function.zip"),
                Handler = "LambdaFunction::LambdaFunction.Function::FunctionHandler",
                Vpc = vpc,
                Environment = new Dictionary<string, string>
                {
                    { "DB_ENDPOINT", db.InstanceEndpoint.Hostname },
                    { "DB_SECRET_ARN", db.Secret.SecretArn }
                }
            });
    
            // Permisos de la Lambda para acceder a la base
            db.GrantConnect(lambda);
    
            // API Gateway
            var api = new LambdaRestApi(this, "Api", new LambdaRestApiProps
            {
                Handler = lambda,
                DeployOptions = new StageOptions { StageName = "prod" }
            });
    
            // LogGroup con retención de 1 día
            new LogGroup(this, "ApiLogs", new LogGroupProps
            {
                LogGroupName = $"/aws/apigateway/{api.RestApiId}",
                Retention = RetentionDays.ONE_DAY,
                RemovalPolicy = RemovalPolicy.DESTROY
            });
        }
    }
    
  5. Iterar con cdk diff
    Cada cambio en la pila se visualiza antes de aplicar:

    cdk diff
    
  6. Desplegar

    cdk deploy --require-approval never
    
  7. Depurar

    • Usa dotnet test para validar la lógica Lambda.
    • Añade Console.WriteLine y revisa los logs en CloudWatch.
    • Si la Lambda no arranca, verifica la arquitectura del binario (file function.zip) y la versión de .NET soportada por Lambda.

Alternativas prácticas

  • Docker para empaquetado: Si la Lambda depende de librerías nativas, habilita Docker con --docker en dotnet lambda package.
  • Constructs de alto nivel: Usa aws-cdk-lib/aws-apigatewayv2-alpha para HTTP APIs más ligeras.
  • CDK Pipelines: Cuando el proyecto crezca, migra a CodePipeline para CI/CD automático.

Cuándo aplicar esta solución

  • Síntomas: Cada despliegue lleva más de una hora, los logs de CloudWatch muestran errores de “module not found”, o los roles IAM aparecen incompletos después de añadir recursos nuevos.
  • Escenarios válidos: Equipos .NET que construyen microservicios serverless, pruebas de concepto que incluyen RDS o SQS, y proyectos donde la velocidad de iteración es crítica.
  • Exclusiones: Si la arquitectura requiere contenedores ECS/Fargate o está basada en monolitos EC2, la solución enfocada en Lambda + API Gateway no será suficiente.

Código

# Inicializar CDK en C#
cdk init app --language csharp

# Empaquetar Lambda .NET sin Docker
dotnet lambda package --configuration Release --framework net6.0 --output-package ./src/LambdaFunction/bin/Release/net6.0/function.zip

# Ver diferencias antes del despliegue
cdk diff

# Desplegar sin aprobaciones interactivas
cdk deploy --require-approval never

Verificación

  1. Endpoint activo: Ejecuta curl https://{api-id}.execute-api.{region}.amazonaws.com/prod/ y verifica que la respuesta provenga de la Lambda.
  2. Conexión a la BD: Desde la Lambda, escribe un registro en la tabla test y comprueba en RDS que la fila exista.
  3. Retención de logs: En CloudWatch, abre el LogGroup creado y confirma que la política de retención sea “1 day”.
  4. Roles IAM: En la consola IAM, revisa que el role asociado a la Lambda tenga la política AWSSecretsManagerReadWrite y AmazonRDSFullAccess (o permisos más restrictivos según necesidad).

Notas adicionales

  • Versiones de runtime: Lambda soporta .NET 6 y .NET 8 (preview). Mantén la versión del proyecto alineada con la que declares en Runtime.DOTNET_6.
  • Secretos: Usa SecretsManager para credenciales de RDS y evita hardcodear contraseñas en Environment.
  • Tiempo de despliegue: La mayor parte del tiempo se consume en la creación de la VPC y la base de datos. En entornos de desarrollo, considera usar DatabaseInstanceFromSnapshot o RdsInstance con DeletionProtection = false para acelerar la eliminación y recreación.
  • Depuración local: dotnet lambda invoke-function permite probar la Lambda en tu máquina antes de subirla, reduciendo ciclos de error de dependencias.
  • Manejo de versiones: Cada despliegue crea una nueva versión de la Lambda; usa alias como prod para apuntar a la versión estable y evitar interrupciones durante actualizaciones.