-
Notifications
You must be signed in to change notification settings - Fork 1k
New pattern - lambda-managed-instances-bedrock-cdk #3076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| *.js | ||
| !src/*.js | ||
| *.d.ts | ||
| node_modules | ||
| cdk.out | ||
| package-lock.json |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| *.ts | ||
| !*.d.ts | ||
| cdk.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # Lambda Managed Instances with Amazon Bedrock | ||
|
|
||
| This pattern deploys a Lambda function on [Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html) (EC2-backed compute) that invokes Amazon Bedrock (Claude) for text generation. | ||
|
|
||
| Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-managed-instances-bedrock-cdk | ||
|
|
||
| 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 | ||
| * [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) | ||
| * [Node and NPM](https://nodejs.org/en/download/) installed | ||
| * [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed | ||
|
|
||
| ## How it works | ||
|
|
||
| Lambda Managed Instances combine EC2 flexibility with serverless simplicity: | ||
|
|
||
| - **EC2-backed compute**: Your function runs on dedicated EC2 instances in your VPC | ||
| - **Serverless management**: AWS handles provisioning, scaling, OS patching, and load balancing | ||
| - **Cost optimization**: Use Compute Savings Plans and Reserved Instances for Lambda workloads | ||
| - **Specialized hardware**: Access Graviton, GPU, and network-optimized instance types | ||
|
|
||
| This pattern adds Bedrock integration to demonstrate a real-world use case beyond hello-world. | ||
|
|
||
| ## Architecture | ||
|
|
||
| ``` | ||
| ┌──────────┐ ┌─────────────────────────────────────────────┐ | ||
| │ Client │──────▶│ Lambda Function (Managed Instances / EC2) │ | ||
| └──────────┘ │ ARM64 · 2048 MB · nodejs24.x │ | ||
| └──────────────────┬──────────────────────────┘ | ||
| │ VPC private subnet | ||
| │ NAT Gateway | ||
| ▼ | ||
| ┌─────────────────────────────────────────────┐ | ||
| │ Amazon Bedrock (Claude Sonnet) │ | ||
| │ InvokeModel via inference profile │ | ||
| └─────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| **Note on networking:** This pattern uses a NAT Gateway for internet egress (including Bedrock API calls). For production workloads, consider adding a VPC interface endpoint for Bedrock (`com.amazonaws.{region}.bedrock-runtime`) to keep traffic on the AWS network and eliminate NAT costs. | ||
|
|
||
| ## Deployment Instructions | ||
|
|
||
| 1. Clone the repository and navigate to the pattern directory: | ||
| ```bash | ||
| git clone https://github.com/aws-samples/serverless-patterns | ||
| cd serverless-patterns/lambda-managed-instances-bedrock-cdk | ||
| ``` | ||
|
|
||
| 2. Install dependencies: | ||
| ```bash | ||
| npm install | ||
| ``` | ||
|
|
||
| 3. Deploy the stack: | ||
| ```bash | ||
| cdk deploy | ||
| ``` | ||
|
|
||
| ## Testing | ||
|
|
||
| Invoke the function: | ||
|
|
||
| ```bash | ||
| aws lambda invoke \ | ||
| --function-name managed-instances-bedrock-cdk --qualifier prod \ | ||
| --payload '{"prompt":"Explain Lambda Managed Instances in 3 sentences"}' \ | ||
| --cli-binary-format raw-in-base64-out \ | ||
| output.json && cat output.json | python3 -m json.tool | ||
| ``` | ||
|
Comment on lines
+68
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the stack publishes a version (recommended in another comment), this invoke should target the published version
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated the invoke command to target the prod alias with --qualifier prod. |
||
|
|
||
| ## Cleanup | ||
|
|
||
| ```bash | ||
| cdk destroy | ||
| ``` | ||
|
|
||
| ---- | ||
| Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: MIT-0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| #!/usr/bin/env node | ||
| import "source-map-support/register"; | ||
| import * as cdk from "aws-cdk-lib"; | ||
| import { LambdaManagedInstancesBedrockStack } from "../lib/lambda-managed-instances-bedrock-stack"; | ||
|
|
||
| const app = new cdk.App(); | ||
| new LambdaManagedInstancesBedrockStack(app, "LambdaManagedInstancesBedrockStack"); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "app": "npx ts-node bin/app.ts" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| { | ||
| "title": "Lambda Managed Instances with Amazon Bedrock", | ||
| "description": "Run Amazon Bedrock model invocations on Lambda Managed Instances (EC2-backed compute) using AWS CDK, combining serverless simplicity with EC2 flexibility.", | ||
| "language": "TypeScript", | ||
| "level": "200", | ||
| "framework": "CDK", | ||
| "introBox": { | ||
| "headline": "How it works", | ||
| "text": [ | ||
| "This pattern deploys a Lambda function on Managed Instances (EC2-backed compute) that invokes Amazon Bedrock (Claude) for text generation.", | ||
| "Lambda Managed Instances provide EC2 flexibility (Graviton, GPU, specialized hardware) with serverless operational simplicity — AWS handles provisioning, scaling, and maintenance.", | ||
| "The function runs on dedicated EC2 capacity in your VPC via a CapacityProvider, enabling access to EC2 pricing models (Savings Plans, Reserved Instances) while maintaining the Lambda programming model." | ||
| ] | ||
| }, | ||
| "gitHub": { | ||
| "template": { | ||
| "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-managed-instances-bedrock-cdk", | ||
| "templateURL": "serverless-patterns/lambda-managed-instances-bedrock-cdk", | ||
| "projectFolder": "lambda-managed-instances-bedrock-cdk", | ||
| "templateFile": "lib/lambda-managed-instances-bedrock-stack.ts" | ||
| } | ||
| }, | ||
| "resources": { | ||
| "bullets": [ | ||
| { "text": "Lambda Managed Instances documentation", "link": "https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html" }, | ||
| { "text": "Lambda Managed Instances launch blog", "link": "https://aws.amazon.com/blogs/aws/introducing-aws-lambda-managed-instances-serverless-simplicity-with-ec2-flexibility/" }, | ||
| { "text": "Amazon Bedrock documentation", "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/" } | ||
| ] | ||
| }, | ||
| "deploy": { | ||
| "text": ["cdk deploy"] | ||
| }, | ||
| "testing": { | ||
| "text": ["See the README for testing instructions."] | ||
| }, | ||
| "cleanup": { | ||
| "text": ["cdk destroy"] | ||
| }, | ||
| "authors": [ | ||
| { | ||
| "name": "Nithin Chandran R", | ||
| "bio": "Technical Account Manager at AWS", | ||
| "linkedin": "nithin-chandran-r" | ||
| } | ||
| ], | ||
| "patternArch": { | ||
| "icon1": { "x": 20, "y": 50, "service": "lambda", "label": "Lambda (Managed Instances)" }, | ||
| "icon2": { "x": 80, "y": 50, "service": "bedrock", "label": "Amazon Bedrock" }, | ||
| "line1": { "from": "icon1", "to": "icon2" } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import * as cdk from "aws-cdk-lib"; | ||
| import * as lambda from "aws-cdk-lib/aws-lambda"; | ||
| import * as iam from "aws-cdk-lib/aws-iam"; | ||
| import * as ec2 from "aws-cdk-lib/aws-ec2"; | ||
| import * as logs from "aws-cdk-lib/aws-logs"; | ||
| import { Construct } from "constructs"; | ||
|
|
||
| export class LambdaManagedInstancesBedrockStack extends cdk.Stack { | ||
| constructor(scope: Construct, id: string, props?: cdk.StackProps) { | ||
| super(scope, id, props); | ||
|
|
||
| const modelId = new cdk.CfnParameter(this, "BedrockModelId", { | ||
| type: "String", | ||
| default: "us.anthropic.claude-sonnet-4-20250514-v1:0", | ||
| description: "Bedrock inference profile model ID", | ||
| }); | ||
|
|
||
| const functionName = "managed-instances-bedrock-cdk"; | ||
|
|
||
| const logGroup = new logs.LogGroup(this, "LogGroup", { | ||
| logGroupName: `/aws/lambda/${functionName}`, | ||
| retention: logs.RetentionDays.TWO_WEEKS, | ||
| removalPolicy: cdk.RemovalPolicy.DESTROY, | ||
| }); | ||
|
|
||
| const fn = new lambda.Function(this, "BedrockFn", { | ||
| runtime: lambda.Runtime.NODEJS_24_X, | ||
| handler: "index.handler", | ||
| code: lambda.Code.fromAsset("src"), | ||
| timeout: cdk.Duration.minutes(5), | ||
| memorySize: 2048, // Managed Instances require >= 2048 MB | ||
| architecture: lambda.Architecture.ARM_64, | ||
| functionName, | ||
| description: "Bedrock invocation on Lambda Managed Instances (EC2-backed)", | ||
| loggingFormat: lambda.LoggingFormat.JSON, | ||
| logGroup, | ||
| environment: { MODEL_ID: modelId.valueAsString }, | ||
| }); | ||
|
|
||
| fn.addToRolePolicy( | ||
| new iam.PolicyStatement({ | ||
| actions: ["bedrock:InvokeModel"], | ||
| resources: [ | ||
| `arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`, | ||
| `arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0`, | ||
| `arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0`, | ||
| `arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0`, | ||
| ], | ||
| }) | ||
| ); | ||
|
|
||
|
Comment on lines
+42
to
+51
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bedrock IAM resource list is too broad.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point -- scoped it down to the three US regions that the cross-region inference profile routes to (us-east-1, us-east-2, us-west-2) instead of the wildcard. |
||
| // Published version required for Managed Instances execution | ||
| const version = fn.currentVersion; | ||
| const alias = new lambda.Alias(this, "ProdAlias", { | ||
| aliasName: "prod", | ||
| version, | ||
| }); | ||
|
|
||
| // Lambda Managed Instances: EC2-backed compute with serverless management | ||
| const vpc = new ec2.Vpc(this, "ManagedInstancesVpc", { | ||
| maxAzs: 2, | ||
| natGateways: 1, | ||
| }); | ||
|
|
||
|
Comment on lines
+60
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The VPC creates a NAT gateway and Bedrock traffic egresses through it. Adding interface endpoints removes the NAT requirement and keeps Bedrock traffic on the AWS network.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keeping it minimal intentionally -- added a note in the README calling out the NAT trade-off and recommending a Bedrock VPC endpoint for production. |
||
| const securityGroup = new ec2.SecurityGroup(this, "SecurityGroup", { | ||
| vpc, | ||
| description: "Lambda Managed Instances security group", | ||
| }); | ||
|
|
||
| const capacityProvider = new lambda.CapacityProvider(this, "CapacityProvider", { | ||
| subnets: vpc.privateSubnets, | ||
| securityGroups: [securityGroup], | ||
| architectures: [lambda.Architecture.ARM_64], | ||
| }); | ||
|
|
||
| capacityProvider.addFunction(fn); | ||
|
|
||
| new cdk.CfnOutput(this, "FunctionName", { value: fn.functionName }); | ||
| new cdk.CfnOutput(this, "FunctionArn", { value: fn.functionArn }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function will not become ACTIVE on the capacity provider until a version is published. Per https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-getting-started.html step 5: "To run your function on Lambda Managed Instances, you must publish a version." Without this, cdk deploy may succeed but
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right -- added fn.currentVersion + a prod alias. Without a published version the function stays on $LATEST and won't actually run on the capacity provider. |
||
| new cdk.CfnOutput(this, "AliasArn", { value: alias.functionArn, description: "Invoke this ARN to run on Managed Instances" }); | ||
| new cdk.CfnOutput(this, "LogGroupName", { value: logGroup.logGroupName }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "lambda-managed-instances-bedrock-cdk", | ||
| "version": "1.0.0", | ||
| "bin": { | ||
| "app": "bin/app.ts" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "cdk": "cdk" | ||
| }, | ||
| "dependencies": { | ||
| "aws-cdk-lib": "^2.257.0", | ||
| "constructs": "10.4.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^22.0.0", | ||
| "ts-node": "^10.9.0", | ||
| "typescript": "~5.7.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| const { BedrockRuntimeClient, InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime"); | ||
|
|
||
| const client = new BedrockRuntimeClient(); | ||
| const MODEL_ID = process.env.MODEL_ID; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed -- added a throw at module load so it fails immediately with a clear message instead of a cryptic SDK error. |
||
| if (!MODEL_ID) throw new Error("MODEL_ID environment variable is required"); | ||
|
|
||
| exports.handler = async (event) => { | ||
| const prompt = event.prompt || "What are the benefits of Lambda Managed Instances?"; | ||
|
|
||
| const res = await client.send( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing exception handling in case of failed SDK calls |
||
| new InvokeModelCommand({ | ||
| modelId: MODEL_ID, | ||
| contentType: "application/json", | ||
| accept: "application/json", | ||
| body: JSON.stringify({ | ||
| anthropic_version: "bedrock-2023-05-31", | ||
| max_tokens: 1024, | ||
| messages: [{ role: "user", content: prompt }], | ||
| }), | ||
| }) | ||
| ); | ||
|
|
||
| const body = JSON.parse(new TextDecoder().decode(res.body)); | ||
|
|
||
| return { | ||
| statusCode: 200, | ||
| body: JSON.stringify({ | ||
| prompt, | ||
| response: body.content[0].text, | ||
| model: MODEL_ID, | ||
| usage: body.usage, | ||
| }), | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "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 | ||
| }, | ||
| "exclude": ["cdk.out"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Folder structure is not architecture
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair -- replaced with a proper flow diagram showing Client → Lambda (Managed Instances) → Bedrock.