diff --git a/lambda-managed-instances-cdk-ts/.gitignore b/lambda-managed-instances-cdk-ts/.gitignore new file mode 100644 index 000000000..e8bbe777c --- /dev/null +++ b/lambda-managed-instances-cdk-ts/.gitignore @@ -0,0 +1,65 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out + +# Parcel default cache directory +.parcel-cache + +# npm +npm-debug.log* +.npm + +# Yarn +yarn-error.log + +# IDEs +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# AWS SAM +.aws-sam/ \ No newline at end of file diff --git a/lambda-managed-instances-cdk-ts/README.md b/lambda-managed-instances-cdk-ts/README.md new file mode 100644 index 000000000..18b8ed076 --- /dev/null +++ b/lambda-managed-instances-cdk-ts/README.md @@ -0,0 +1,131 @@ +# Lambda Managed Instances with AWS CDK TypeScript + +This pattern demonstrates how to create and deploy AWS Lambda Managed Instances using AWS CDK in TypeScript. Lambda Managed Instances allow you to run Lambda functions on dedicated EC2 instances for workloads that require more control over the underlying infrastructure. + +Learn more about this pattern at Serverless Land Patterns: [Lambda Managed Instances](https://serverlessland.com/patterns/lambda-managed-instances) + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Node.js](https://nodejs.org/) (version 18.x or later) +* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html) installed (`npm install -g aws-cdk`) +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) + +## Architecture + +This CDK stack creates: + +1. **IAM Roles**: + - Lambda execution role with basic execution permissions + - Capacity provider operator role for managing EC2 instances + +2. **VPC Resources**: + - New VPC with CIDR 10.0.0.0/16 + - Private subnet with NAT Gateway for outbound internet access + - Security group for Lambda Managed Instances + +3. **Lambda Capacity Provider**: + - Manages EC2 instances (x86_64 architecture) + - Maximum 30 vCPUs scaling configuration + +4. **Lambda Function**: + - Node.js 20.x runtime + - 2048 MB memory allocation + - 512 MB ephemeral storage + - Configured to use the managed instances capacity provider + +## Deployment Instructions + +1. Clone this repository and navigate to the pattern directory: + ```bash + cd lambda-managed-instances-cdk-ts + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Build the TypeScript code: + ```bash + npm run build + ``` + +4. Bootstrap CDK (if you haven't done this before in your account/region): + ```bash + cdk bootstrap + ``` + +5. Deploy the stack: + ```bash + cdk deploy + ``` + +6. Note the outputs from the CDK deployment process. These contain the resource names and ARNs which are used for testing. + +## How it works + +1. **Infrastructure Setup**: The CDK creates all necessary infrastructure including VPC, subnets, security groups, and IAM roles. + +2. **Capacity Provider**: A Lambda capacity provider is created that manages EC2 instances in your VPC. This provider can scale up to 30 vCPUs based on demand. + +3. **Lambda Function**: The Lambda function is configured to use the managed instances capacity provider instead of the standard serverless execution environment. + +4. **Function Execution**: When invoked, the Lambda function runs on dedicated EC2 instances managed by the capacity provider, providing more control over the execution environment. + +## Testing + +1. Test the Lambda function using the AWS CLI: + ```bash + aws lambda invoke \ + --function-name my-managed-instance-function \ + --payload '{"test": "data"}' \ + response.json + ``` + +2. Check the response: + ```bash + cat response.json + ``` + +3. You should see a response like: + ```json + { + "statusCode": 200, + "body": "{\"message\":\"Hello from Lambda Managed Instances!\",\"event\":{\"test\":\"data\"}}" + } + ``` + +4. Monitor the function execution in CloudWatch Logs to see the detailed execution logs. + +## Useful CDK Commands + +* `npm run build` - compile typescript to js +* `npm run watch` - watch for changes and compile +* `cdk deploy` - deploy this stack to your default AWS account/region +* `cdk diff` - compare deployed stack with current state +* `cdk synth` - emits the synthesized CloudFormation template +* `cdk destroy` - delete the stack + +## Cleanup + +1. Delete the stack: + ```bash + cdk destroy + ``` + +2. Confirm when prompted to delete the stack and all its resources. + +## Notes + +- Lambda Managed Instances require VPC configuration and have different networking requirements compared to standard Lambda functions. +- The capacity provider manages EC2 instances automatically, scaling based on function invocation demand. +- This pattern is suitable for workloads that need more control over the execution environment or have specific networking requirements. + +---- +Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/lambda-managed-instances-cdk-ts/bin/lambda-managed-instances-cdk-ts.ts b/lambda-managed-instances-cdk-ts/bin/lambda-managed-instances-cdk-ts.ts new file mode 100644 index 000000000..8af78045b --- /dev/null +++ b/lambda-managed-instances-cdk-ts/bin/lambda-managed-instances-cdk-ts.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { LambdaManagedInstancesStack } from '../lib/lambda-managed-instances-stack'; + +const app = new cdk.App(); +new LambdaManagedInstancesStack(app, 'LambdaManagedInstancesStack', { + /* If you don't specify 'env', this stack will be environment-agnostic. + * Account/Region-dependent features and context lookups will not work, + * but a single synthesized template can be deployed anywhere. */ + + /* Uncomment the next line to specialize this stack for the AWS Account + * and Region that are implied by the current CLI configuration. */ + // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, + + /* Uncomment the next line if you know exactly what Account and Region you + * want to deploy the stack to. */ + // env: { account: '123456789012', region: 'us-east-1' }, + + /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +}); \ No newline at end of file diff --git a/lambda-managed-instances-cdk-ts/cdk.json b/lambda-managed-instances-cdk-ts/cdk.json new file mode 100644 index 000000000..dbbb8fc88 --- /dev/null +++ b/lambda-managed-instances-cdk-ts/cdk.json @@ -0,0 +1,65 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/lambda-managed-instances-cdk-ts.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/core:bootstrapQualifier": "simple", + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableLogging": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForSourceAction": true + } +} \ No newline at end of file diff --git a/lambda-managed-instances-cdk-ts/example-pattern.json b/lambda-managed-instances-cdk-ts/example-pattern.json new file mode 100644 index 000000000..868998781 --- /dev/null +++ b/lambda-managed-instances-cdk-ts/example-pattern.json @@ -0,0 +1,63 @@ +{ + "title": "Lambda Managed Instances with CDK TypeScript", + "description": "Create AWS Lambda Managed Instances using AWS CDK in TypeScript.", + "language": "TypeScript", + "level": "300", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This sample project demonstrates how to create and deploy AWS Lambda Managed Instances using AWS CDK in TypeScript. Lambda Managed Instances allow you to run Lambda functions on dedicated EC2 instances for workloads that require more control over the underlying infrastructure.", + "The pattern creates all necessary infrastructure including VPC, subnets, security groups, IAM roles, and a Lambda capacity provider that manages EC2 instances. The Lambda function is configured to use the managed instances capacity provider instead of the standard serverless execution environment.", + "This pattern deploys a VPC with public and private subnets, NAT Gateway, Lambda capacity provider, Lambda function with Node.js runtime, and all required IAM roles and policies." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-managed-instances-cdk-ts", + "templateURL": "serverless-patterns/lambda-managed-instances-cdk-ts", + "projectFolder": "lambda-managed-instances-cdk-ts", + "templateFile": "lambda-managed-instances-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Lambda Managed Instances Documentation", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/managed-instances.html" + }, + { + "text": "AWS Lambda Developer Guide", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/" + }, + { + "text": "AWS CDK TypeScript Reference", + "link": "https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda-readme.html" + } + ] + }, + "deploy": { + "text": [ + "cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cdk destroy." + ] + }, + "authors": [ + { + "name": "AWS Serverless Patterns", + "image": "https://serverlessland.com/assets/images/logos/serverless-land-logo.png", + "bio": "AWS Serverless Patterns Collection", + "linkedin": "", + "twitter": "AWSOpen" + } + ] +} diff --git a/lambda-managed-instances-cdk-ts/lib/lambda-managed-instances-stack.ts b/lambda-managed-instances-cdk-ts/lib/lambda-managed-instances-stack.ts new file mode 100644 index 000000000..3bf2608e9 --- /dev/null +++ b/lambda-managed-instances-cdk-ts/lib/lambda-managed-instances-stack.ts @@ -0,0 +1,142 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as path from 'path'; + +export class LambdaManagedInstancesStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Step 1: Create the required IAM roles (following instructions exactly) + + // Lambda execution role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + roleName: 'MyLambdaExecutionRole', + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole') + ] + }); + + // Capacity provider operator role + const capacityProviderOperatorRole = new iam.Role(this, 'CapacityProviderOperatorRole', { + roleName: 'MyCapacityProviderOperatorRole', + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSLambdaManagedEC2ResourceOperator') + ] + }); + + // Step 2: Set up VPC resources (following instructions exactly) + + // Create VPC with CIDR 10.0.0.0/16 + const vpc = new ec2.Vpc(this, 'LambdaManagedInstancesVpc', { + ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), + maxAzs: 1, // Instructions show single subnet + subnetConfiguration: [ + { + cidrMask: 24, + name: 'lambda-subnet', + subnetType: ec2.SubnetType.PUBLIC, // Instructions create in public for simplicity + } + ], + natGateways: 0 // No NAT needed for public subnet + }); + + // Create security group (following instructions exactly) + const securityGroup = new ec2.SecurityGroup(this, 'LambdaManagedInstancesSecurityGroup', { + vpc: vpc, + description: 'Security group for Lambda Managed Instances', + securityGroupName: 'my-capacity-provider-sg' + }); + + // Step 3: Create capacity provider (using native CDK L1 construct) + const capacityProvider = new lambda.CfnCapacityProvider(this, 'CapacityProvider', { + capacityProviderName: 'my-capacity-provider', + vpcConfig: { + subnetIds: [vpc.publicSubnets[0].subnetId], + securityGroupIds: [securityGroup.securityGroupId] + }, + permissionsConfig: { + capacityProviderOperatorRoleArn: capacityProviderOperatorRole.roleArn + }, + instanceRequirements: { + architectures: ['x86_64'] + }, + capacityProviderScalingConfig: { + maxVCpuCount: 30 + } + }); + + // Step 4: Create Lambda function with managed instances + const managedInstanceFunction = new lambda.CfnFunction(this, 'ManagedInstanceFunction', { + functionName: 'my-managed-instance-function', + runtime: 'nodejs24.x', // Using nodejs24.x as requested + handler: 'index.handler', + code: { + zipFile: ` +exports.handler = async (event, context) => { + console.log('Event:', JSON.stringify(event, null, 2)); + + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Hello from Lambda Managed Instances!', + event: event + }) + }; +};` + }, + role: lambdaExecutionRole.roleArn, + architectures: ['x86_64'], + memorySize: 2048, + ephemeralStorage: { + size: 512 + }, + capacityProviderConfig: { + lambdaManagedInstancesCapacityProviderConfig: { + capacityProviderArn: capacityProvider.capacityProviderRef.capacityProviderArn + } + } + }); + + // Step 5: Publish function version (following instructions exactly) + const functionVersion = new lambda.CfnVersion(this, 'ManagedInstanceFunctionVersion', { + functionName: managedInstanceFunction.ref, + description: 'Version 1 of Lambda Managed Instance function' + }); + + // Outputs (matching the instructions) + new cdk.CfnOutput(this, 'VpcId', { + value: vpc.vpcId, + description: 'VPC ID for Lambda Managed Instances' + }); + + new cdk.CfnOutput(this, 'SubnetId', { + value: vpc.publicSubnets[0].subnetId, + description: 'Subnet ID for Lambda Managed Instances' + }); + + new cdk.CfnOutput(this, 'SecurityGroupId', { + value: securityGroup.securityGroupId, + description: 'Security Group ID for Lambda Managed Instances' + }); + + new cdk.CfnOutput(this, 'LambdaFunctionName', { + value: managedInstanceFunction.ref, + description: 'Lambda function name' + }); + + new cdk.CfnOutput(this, 'LambdaFunctionArn', { + value: managedInstanceFunction.attrArn, + description: 'Lambda function ARN' + }); + + new cdk.CfnOutput(this, 'CapacityProviderOperatorRoleArn', { + value: capacityProviderOperatorRole.roleArn, + description: 'Capacity Provider Operator Role ARN' + }); + } +} \ No newline at end of file diff --git a/lambda-managed-instances-cdk-ts/package.json b/lambda-managed-instances-cdk-ts/package.json new file mode 100644 index 000000000..62de73111 --- /dev/null +++ b/lambda-managed-instances-cdk-ts/package.json @@ -0,0 +1,30 @@ +{ + "name": "lambda-managed-instances-cdk-ts", + "version": "0.1.0", + "bin": { + "lambda-managed-instances-cdk-ts": "bin/lambda-managed-instances-cdk-ts.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk", + "deploy": "cdk deploy", + "destroy": "cdk destroy", + "synth": "cdk synth" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@types/node": "18.14.6", + "aws-cdk": "^2.1034.0", + "jest": "^29.5.0", + "ts-jest": "^29.1.0", + "ts-node": "^10.9.1", + "typescript": "~4.9.5" + }, + "dependencies": { + "aws-cdk-lib": "2.232.2", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + } +} diff --git a/lambda-managed-instances-cdk-ts/test-function.sh b/lambda-managed-instances-cdk-ts/test-function.sh new file mode 100755 index 000000000..1968fa229 --- /dev/null +++ b/lambda-managed-instances-cdk-ts/test-function.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Test script for Lambda Managed Instances function +# This script tests the deployed Lambda function + +set -e + +FUNCTION_NAME="my-managed-instance-function" +PAYLOAD='{"test": "Hello from test script", "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' + +echo "Testing Lambda Managed Instances function..." +echo "Function Name: $FUNCTION_NAME" +echo "Payload: $PAYLOAD" +echo "" + +# Invoke the function +echo "Invoking function..." +aws lambda invoke \ + --function-name "$FUNCTION_NAME" \ + --payload "$PAYLOAD" \ + --cli-binary-format raw-in-base64-out \ + response.json + +echo "" +echo "Response:" +cat response.json +echo "" + +# Check if response file exists and contains expected content +if [ -f "response.json" ]; then + if grep -q "Hello from Lambda Managed Instances!" response.json; then + echo "✅ Test PASSED: Function returned expected message" + else + echo "❌ Test FAILED: Function did not return expected message" + exit 1 + fi +else + echo "❌ Test FAILED: No response file generated" + exit 1 +fi + +# Clean up +rm -f response.json + +echo "✅ Test completed successfully!" \ No newline at end of file diff --git a/lambda-managed-instances-cdk-ts/tsconfig.json b/lambda-managed-instances-cdk-ts/tsconfig.json new file mode 100644 index 000000000..fa55b1fec --- /dev/null +++ b/lambda-managed-instances-cdk-ts/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} \ No newline at end of file