From 371aabe3e70c02f65ea3f01a0de7d257b7c30c2e Mon Sep 17 00:00:00 2001 From: Dmitry Gulin Date: Mon, 15 Dec 2025 15:10:21 -0500 Subject: [PATCH 1/3] add Hello World lambda managed instances TF pattern --- lambda-managed-instances-tf/.gitignore | 31 ++ lambda-managed-instances-tf/README.md | 237 +++++++++++++ lambda-managed-instances-tf/cleanup.sh | 61 ++++ lambda-managed-instances-tf/deploy.sh | 58 +++ .../events/hello-world.json | 3 + .../example-pattern.json | 74 ++++ .../lambda/hello-world.mjs | 15 + .../lambda/package.json | 10 + lambda-managed-instances-tf/main.tf | 332 ++++++++++++++++++ lambda-managed-instances-tf/outputs.tf | 19 + lambda-managed-instances-tf/terraform.tf | 13 + lambda-managed-instances-tf/test-lambda.sh | 119 +++++++ lambda-managed-instances-tf/variables.tf | 5 + 13 files changed, 977 insertions(+) create mode 100644 lambda-managed-instances-tf/.gitignore create mode 100644 lambda-managed-instances-tf/README.md create mode 100755 lambda-managed-instances-tf/cleanup.sh create mode 100755 lambda-managed-instances-tf/deploy.sh create mode 100644 lambda-managed-instances-tf/events/hello-world.json create mode 100644 lambda-managed-instances-tf/example-pattern.json create mode 100644 lambda-managed-instances-tf/lambda/hello-world.mjs create mode 100644 lambda-managed-instances-tf/lambda/package.json create mode 100644 lambda-managed-instances-tf/main.tf create mode 100644 lambda-managed-instances-tf/outputs.tf create mode 100644 lambda-managed-instances-tf/terraform.tf create mode 100755 lambda-managed-instances-tf/test-lambda.sh create mode 100644 lambda-managed-instances-tf/variables.tf diff --git a/lambda-managed-instances-tf/.gitignore b/lambda-managed-instances-tf/.gitignore new file mode 100644 index 000000000..17ff9a5a0 --- /dev/null +++ b/lambda-managed-instances-tf/.gitignore @@ -0,0 +1,31 @@ +# Terraform files +*.tfstate +*.tfstate.* +*.tfvars +*.tfvars.json +.terraform/ +.terraform.lock.hcl +terraform.tfplan +terraform.tfplan.* + +# Lambda function package +lambda-function.zip + +# Test response files +response.json +custom-response.json +output.json + +# Node.js dependencies +lambda/node_modules/ +lambda/package-lock.json + +# OS files +.DS_Store +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo \ No newline at end of file diff --git a/lambda-managed-instances-tf/README.md b/lambda-managed-instances-tf/README.md new file mode 100644 index 000000000..c7ce9fa88 --- /dev/null +++ b/lambda-managed-instances-tf/README.md @@ -0,0 +1,237 @@ +# Lambda Hello World on Lambda Managed Instances (Terraform) + +This pattern demonstrates how to deploy a simple Hello World Lambda function running on Lambda Managed Instances using Terraform. Lambda Managed Instances provide predictable performance and reduced cold starts for your Lambda functions. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-managed-instances-tf + +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. + +**Note**: Lambda Managed Instances provision EC2 instances that are **NOT eligible for the AWS Free Tier**. These instances will incur charges immediately upon deployment, regardless of your Free Tier status. + +## 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 v2](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) (latest available version) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) (version 1.0 or later) installed +* [Node.js](https://nodejs.org/) (version 18.x or later) for Lambda function dependencies + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +1. Change directory to the pattern directory: + ``` + cd lambda-managed-instances-tf + ``` + +### Manual Deployment + +1. Install Lambda function dependencies: + ``` + cd lambda && npm install && cd .. + ``` +1. Initialize Terraform: + ``` + terraform init + ``` +1. Plan the deployment: + ``` + terraform plan + ``` +1. Deploy the infrastructure: + ``` + terraform apply + ``` + Note: This stack will deploy to your default AWS region. You can specify a different region by setting the `aws_region` variable. + +### Automated deployment + +1. Use the automated deployment script: + ``` + ./deploy.sh [aws-region] + ``` + +1. Note the outputs from the Terraform deployment process. These contain the resource names and/or ARNs which are used for testing. + +## How it works + +This pattern demonstrates the deployment of a simple Lambda function on Lambda Managed Instances: + +### Lambda Managed Instances +[Lambda Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html) provide: +- Predictable performance with pre-warmed execution environments +- Reduced cold start latency +- Consistent execution characteristics +- Better resource utilization for frequently invoked functions + +The underlying EC2 infrastructure can be inspected using AWS CLI commands to understand how managed instances work (see "Inspecting Lambda Managed Instances Infrastructure" section below). + +### Hello World Function +The Lambda function is a simple Hello World implementation that: +- Accepts an event with a name parameter +- Returns a JSON response with a greeting message +- Uses AWS Lambda PowerTools for efficient event logging +- Uses ES modules (ESM) with `.mjs` extension for modern JavaScript syntax +- Demonstrates minimal Lambda function structure + +### Infrastructure Components +The Terraform configuration creates: +- **VPC**: Custom VPC with public and private subnets across multiple AZs +- **NAT Gateways**: For outbound internet access from private subnets +- **Security Group**: Controls network access for managed instances +- **Lambda Function**: Hello World function with ARM64 architecture +- **Capacity Provider**: Lambda managed instances configuration +- **IAM Roles**: Proper permissions for Lambda execution and capacity provider operations +- **CloudWatch Logs**: Function execution logging + + + + + +## Testing + +After deployment, you can test the Lambda function using AWS CLI or AWS Console. + +### AWS CLI Testing + +1. **Basic function invocation**: + ```bash + aws lambda invoke \ + --function-name hello-world-managed-instances-tf \ + --payload file://events/hello-world.json \ + --cli-binary-format raw-in-base64-out \ + response.json + ``` + +2. **View the response**: + ```bash + cat response.json + ``` + +3. **Custom name invocation**: + ```bash + echo '{"name":"Lambda Managed Instances"}' | aws lambda invoke \ + --function-name hello-world-managed-instances-tf \ + --payload file:///dev/stdin \ + --cli-binary-format raw-in-base64-out \ + custom-response.json + ``` + +4. **View CloudWatch logs**: + ```bash + aws logs filter-log-events \ + --log-group-name /aws/lambda/hello-world-managed-instances-tf \ + --start-time $(date -d '5 minutes ago' +%s)000 + ``` + +### AWS Console Testing + +1. Navigate to the Lambda service in the AWS Console +2. Find the function named `hello-world-managed-instances-tf` +3. Create a test event using the payload from `events/hello-world.json` or create a custom payload: + ```json + { + "name": "Your Custom Name" + } + ``` +4. Execute the test and observe the results in the execution logs + +### Expected Response + +The function returns a JSON response with the following structure: + +```json +{ + "response": "Hello AWS Lambda on Managed Instances" +} +``` + +### Monitoring and Observability + +Monitor the function execution through: +- **CloudWatch Logs**: Detailed execution logs with event and response data +- **Lambda Metrics**: Function performance and invocation statistics +- **CloudWatch Metrics**: Custom metrics and alarms for monitoring + +## Inspecting Lambda Managed Instances Infrastructure + +Lambda Managed Instances provision EC2 instances behind the scenes to provide predictable performance. You can inspect this infrastructure using AWS CLI commands: + +### View Capacity Provider Details + +```bash +aws lambda get-capacity-provider --capacity-provider-name lambda-capacity-provider +``` + +This shows: +- Capacity provider ARN and state +- VPC configuration (subnets and security groups) +- Instance requirements (architecture, scaling mode) +- IAM roles and permissions + +### List Associated EC2 Instances + +```bash +aws ec2 describe-instances \ + --filters "Name=tag:aws:lambda:capacity-provider,Values=arn:aws:lambda:*:capacity-provider:lambda-capacity-provider" \ + --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,LaunchTime,SubnetId,PrivateIpAddress]' \ + --output table +``` + +This displays: +- Instance IDs and types +- Current state (running, pending, terminated) +- Launch times and subnet distribution +- Private IP addresses within the VPC + +**Note**: For a complete list of supported EC2 instance types for Lambda Managed Instances and their pricing, see the [AWS Lambda Pricing page](https://aws.amazon.com/lambda/pricing/). + +### Understanding Instance Behavior + +**Auto-scaling**: Instances are automatically created and terminated based on function demand +- **Scale-up**: New instances launch when function invocation increases +- **Scale-down**: Unused instances terminate after periods of low activity +- **Multi-AZ**: Instances are distributed across availability zones for high availability + +**Instance Lifecycle**: +- Instances typically launch within 1-2 minutes of stack deployment +- They remain running to provide immediate function execution +- AWS manages all instance lifecycle operations automatically + +### Automated Testing + +The included test script (`./test-lambda.sh`) automatically inspects both the capacity provider and EC2 instances, providing a comprehensive view of the managed instances infrastructure. + +## Regional Availability + +This stack will deploy to your default AWS region or the region specified in the `aws_region` variable. Before deploying, please verify that Lambda Managed Instances feature is available in your target region by using the [AWS capabilities explorer](https://builder.aws.com/build/capabilities/explore) or consulting the official [Lambda Managed Instances documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html). + +## Customization + +You can customize the deployment by modifying the variables in `variables.tf` or by passing variables during deployment: + +```bash +terraform apply -var="aws_region=us-east-1" +``` + +## Cleanup + +1. Delete the infrastructure: + ```bash + terraform destroy + ``` + + **Alternative**: Use the automated cleanup script: + ```bash + ./cleanup.sh [aws-region] + ``` + +1. Confirm the resources have been deleted by checking the AWS Console. + +---- +Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 \ No newline at end of file diff --git a/lambda-managed-instances-tf/cleanup.sh b/lambda-managed-instances-tf/cleanup.sh new file mode 100755 index 000000000..02d272a4c --- /dev/null +++ b/lambda-managed-instances-tf/cleanup.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Cleanup script for Lambda Managed Instances Terraform pattern +# Usage: ./cleanup.sh [aws-region] + +set -e + +# Configuration +AWS_REGION=${1:-us-west-2} + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}=== Cleaning up Lambda Managed Instances Pattern (Terraform) ===${NC}" +echo -e "${YELLOW}Region: ${AWS_REGION}${NC}" +echo "" + +# Confirm destruction +echo -e "${YELLOW}This will destroy all resources created by this pattern.${NC}" +read -p "Are you sure you want to continue? (y/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Cleanup cancelled.${NC}" + exit 0 +fi + +# Destroy infrastructure +echo -e "${BLUE}Destroying Terraform infrastructure...${NC}" +terraform destroy -var="aws_region=${AWS_REGION}" -auto-approve + +if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Infrastructure successfully destroyed${NC}" +else + echo -e "${RED}✗ Failed to destroy infrastructure${NC}" + exit 1 +fi + +# Clean up local files +echo -e "${BLUE}Cleaning up local files...${NC}" +rm -f lambda-function.zip +rm -f response.json +rm -f custom-response.json +rm -f output.json + +# Clean up Terraform temporary and state files +echo -e "${BLUE}Cleaning up Terraform temporary and state files...${NC}" +rm -f terraform.tfstate +rm -f terraform.tfstate.backup +rm -f .terraform.tfstate.lock.info +rm -rf .terraform/ +rm -f .terraform.lock.hcl +rm -f terraform.tfplan +rm -f terraform.log +rm -f crash.log + +echo "" +echo -e "${GREEN}=== Cleanup completed successfully! ===${NC}" \ No newline at end of file diff --git a/lambda-managed-instances-tf/deploy.sh b/lambda-managed-instances-tf/deploy.sh new file mode 100755 index 000000000..50963eb50 --- /dev/null +++ b/lambda-managed-instances-tf/deploy.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Deployment script for Lambda Managed Instances Terraform pattern +# Usage: ./deploy.sh [aws-region] + +set -e + +# Configuration +AWS_REGION=${1:-us-west-2} + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}=== Deploying Lambda Managed Instances Pattern (Terraform) ===${NC}" +echo -e "${YELLOW}Region: ${AWS_REGION}${NC}" +echo "" + +# Step 1: Install Lambda dependencies +echo -e "${BLUE}Step 1: Installing Lambda function dependencies${NC}" +cd lambda +if [ ! -f "package-lock.json" ]; then + npm install +else + echo "Dependencies already installed" +fi +cd .. + +# Step 2: Initialize Terraform +echo -e "${BLUE}Step 2: Initializing Terraform${NC}" +terraform init + +# Step 3: Plan deployment +echo -e "${BLUE}Step 3: Planning Terraform deployment${NC}" +terraform plan -var="aws_region=${AWS_REGION}" + +# Step 4: Apply infrastructure +echo -e "${BLUE}Step 4: Applying Terraform configuration${NC}" +terraform apply -var="aws_region=${AWS_REGION}" -auto-approve + +echo -e "${GREEN}✓ Lambda function automatically associated with capacity provider via Terraform${NC}" + +echo "" +echo -e "${GREEN}=== Deployment completed successfully! ===${NC}" +echo "" +echo -e "${YELLOW}Outputs:${NC}" +terraform output + +echo "" +echo -e "${YELLOW}Next steps:${NC}" +CAPACITY_PROVIDER_NAME=$(terraform output -raw capacity_provider_name) +FUNCTION_NAME=$(terraform output -raw function_name) +echo "1. Test the function: ./test-lambda.sh" +echo "2. View capacity provider: aws lambda get-capacity-provider --capacity-provider-name $CAPACITY_PROVIDER_NAME --region $AWS_REGION" +echo "3. View function details: aws lambda get-function --function-name $FUNCTION_NAME --region $AWS_REGION" \ No newline at end of file diff --git a/lambda-managed-instances-tf/events/hello-world.json b/lambda-managed-instances-tf/events/hello-world.json new file mode 100644 index 000000000..3d0c51d83 --- /dev/null +++ b/lambda-managed-instances-tf/events/hello-world.json @@ -0,0 +1,3 @@ +{ + "name": "AWS Lambda on Managed Instances" +} \ No newline at end of file diff --git a/lambda-managed-instances-tf/example-pattern.json b/lambda-managed-instances-tf/example-pattern.json new file mode 100644 index 000000000..bc343723d --- /dev/null +++ b/lambda-managed-instances-tf/example-pattern.json @@ -0,0 +1,74 @@ +{ + "title": "Lambda Hello World on Lambda Managed Instances (Terraform)", + "description": "Deploy a simple Hello World Lambda function on Lambda Managed Instances using Terraform", + "language": "JavaScript", + "level": "200", + "framework": "Terraform", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern demonstrates how to deploy a simple Hello World Lambda function running on Lambda Managed Instances using Terraform.", + "Lambda Managed Instances provide predictable performance and reduced cold starts for your Lambda functions by pre-warming execution environments.", + "The Hello World function uses ES modules (ESM) and accepts an event with a name parameter and returns a simple JSON response with a greeting message.", + "The Terraform configuration creates a complete VPC infrastructure with public and private subnets, NAT gateways, and automatically associates the Lambda function with a capacity provider for managed instances." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-managed-instances-tf", + "templateURL": "serverless-patterns/lambda-managed-instances-tf", + "projectFolder": "lambda-managed-instances-tf", + "templateFile": "main.tf" + } + }, + "resources": { + "bullets": [ + { + "text": "Lambda Managed Instances documentation", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html" + }, + { + "text": "AWS Lambda Pricing (supported instance types)", + "link": "https://aws.amazon.com/lambda/pricing/" + }, + { + "text": "AWS Lambda Developer Guide", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/" + }, + { + "text": "Terraform AWS Provider Documentation", + "link": "https://registry.terraform.io/providers/hashicorp/aws/latest/docs" + }, + { + "text": "Lambda Capacity Provider Terraform Resource", + "link": "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_capacity_provider" + } + ] + }, + "deploy": { + "text": [ + "cd lambda && npm install && cd ..", + "terraform init", + "terraform apply", + "See the Readme file for detailed deployment instructions." + ] + }, + "testing": { + "text": [ + "See the Readme file for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the infrastructure: terraform destroy.", + "See the Readme file for detailed cleanup instructions." + ] + }, + "authors": [ + { + "name": "Dmitry Gulin", + "bio": "Senior Delivery Consultant, AWS.", + "linkedin": "dmitry-gulin" + } + ] +} \ No newline at end of file diff --git a/lambda-managed-instances-tf/lambda/hello-world.mjs b/lambda-managed-instances-tf/lambda/hello-world.mjs new file mode 100644 index 000000000..c9d3d7dd9 --- /dev/null +++ b/lambda-managed-instances-tf/lambda/hello-world.mjs @@ -0,0 +1,15 @@ +import { Logger } from '@aws-lambda-powertools/logger'; + +const logger = new Logger(); + +export const handler = async (event) => { + logger.logEventIfEnabled(event); + + const name = event.name || 'World'; + + const response = { + response: `Hello ${name}` + }; + + return response; +}; \ No newline at end of file diff --git a/lambda-managed-instances-tf/lambda/package.json b/lambda-managed-instances-tf/lambda/package.json new file mode 100644 index 000000000..fe26c44c4 --- /dev/null +++ b/lambda-managed-instances-tf/lambda/package.json @@ -0,0 +1,10 @@ +{ + "name": "hello-world-managed-instances", + "version": "1.0.0", + "description": "Hello World Lambda function for Managed Instances", + "main": "hello-world.mjs", + "type": "module", + "dependencies": { + "@aws-lambda-powertools/logger": "^2.30.0" + } +} \ No newline at end of file diff --git a/lambda-managed-instances-tf/main.tf b/lambda-managed-instances-tf/main.tf new file mode 100644 index 000000000..7a6433c91 --- /dev/null +++ b/lambda-managed-instances-tf/main.tf @@ -0,0 +1,332 @@ +provider "aws" { + region = var.aws_region +} + +# Data sources +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +# VPC for Lambda Managed Instances +resource "aws_vpc" "lambda_managed_instances_vpc" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "lambda-managed-instances-vpc" + } +} + +# Internet Gateway +resource "aws_internet_gateway" "igw" { + vpc_id = aws_vpc.lambda_managed_instances_vpc.id + + tags = { + Name = "lambda-managed-instances-igw" + } +} + +# Public subnets for NAT gateways +resource "aws_subnet" "public" { + count = 2 + + vpc_id = aws_vpc.lambda_managed_instances_vpc.id + cidr_block = "10.0.${count.index + 1}.0/24" + availability_zone = data.aws_availability_zones.available.names[count.index] + map_public_ip_on_launch = true + + tags = { + Name = "lambda-managed-instances-public-${count.index + 1}" + } +} + +# Private subnets for Lambda Managed Instances +resource "aws_subnet" "private" { + count = 2 + + vpc_id = aws_vpc.lambda_managed_instances_vpc.id + cidr_block = "10.0.${count.index + 10}.0/24" + availability_zone = data.aws_availability_zones.available.names[count.index] + + tags = { + Name = "lambda-managed-instances-private-${count.index + 1}" + } +} + +# Data source for availability zones +data "aws_availability_zones" "available" { + state = "available" +} + +# Elastic IPs for NAT gateways +resource "aws_eip" "nat" { + count = 2 + + domain = "vpc" + depends_on = [aws_internet_gateway.igw] + + tags = { + Name = "lambda-managed-instances-nat-eip-${count.index + 1}" + } +} + +# NAT Gateways +resource "aws_nat_gateway" "nat" { + count = 2 + + allocation_id = aws_eip.nat[count.index].id + subnet_id = aws_subnet.public[count.index].id + + tags = { + Name = "lambda-managed-instances-nat-${count.index + 1}" + } + + depends_on = [aws_internet_gateway.igw] +} + +# Route table for public subnets +resource "aws_route_table" "public" { + vpc_id = aws_vpc.lambda_managed_instances_vpc.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.igw.id + } + + tags = { + Name = "lambda-managed-instances-public-rt" + } +} + +# Route table associations for public subnets +resource "aws_route_table_association" "public" { + count = 2 + + subnet_id = aws_subnet.public[count.index].id + route_table_id = aws_route_table.public.id +} + +# Route tables for private subnets +resource "aws_route_table" "private" { + count = 2 + + vpc_id = aws_vpc.lambda_managed_instances_vpc.id + + route { + cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.nat[count.index].id + } + + tags = { + Name = "lambda-managed-instances-private-rt-${count.index + 1}" + } +} + +# Route table associations for private subnets +resource "aws_route_table_association" "private" { + count = 2 + + subnet_id = aws_subnet.private[count.index].id + route_table_id = aws_route_table.private[count.index].id +} + +# Security Group for Lambda Managed Instances +resource "aws_security_group" "lambda_managed_instances" { + name_prefix = "lambda-managed-instances-" + vpc_id = aws_vpc.lambda_managed_instances_vpc.id + + # Allow all outbound traffic + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "lambda-managed-instances-sg" + } +} + +# IAM role for Lambda function +resource "aws_iam_role" "lambda_role" { + name = "hello-world-managed-instances-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) +} + +# IAM policy attachment for Lambda basic execution +resource "aws_iam_role_policy_attachment" "lambda_basic_execution" { + role = aws_iam_role.lambda_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + +# IAM role for Lambda Capacity Provider Operator +# https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-operator-role.html +resource "aws_iam_role" "capacity_provider_role" { + name = "lambda-capacity-provider-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) +} + +# IAM policy for Lambda Capacity Provider Operator +resource "aws_iam_role_policy" "capacity_provider_policy" { + name = "lambda-capacity-provider-policy" + role = aws_iam_role.capacity_provider_role.id + + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Effect" : "Allow", + "Action" : [ + "ec2:RunInstances", + "ec2:CreateTags", + "ec2:AttachNetworkInterface" + ], + "Resource" : [ + "arn:aws:ec2:*:*:instance/*", + "arn:aws:ec2:*:*:network-interface/*", + "arn:aws:ec2:*:*:volume/*" + ], + "Condition" : { + "StringEquals" : { + "ec2:ManagedResourceOperator" : "scaler.lambda.amazonaws.com" + } + } + }, + { + "Effect" : "Allow", + "Action" : [ + "ec2:DescribeAvailabilityZones", + "ec2:DescribeCapacityReservations", + "ec2:DescribeInstances", + "ec2:DescribeInstanceStatus", + "ec2:DescribeInstanceTypeOfferings", + "ec2:DescribeInstanceTypes", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets" + ], + "Resource" : "*" + }, + { + "Effect" : "Allow", + "Action" : [ + "ec2:RunInstances", + "ec2:CreateNetworkInterface" + ], + "Resource" : [ + "arn:aws:ec2:*:*:subnet/*", + "arn:aws:ec2:*:*:security-group/*" + ] + }, + { + "Effect" : "Allow", + "Action" : [ + "ec2:RunInstances" + ], + "Resource" : [ + "arn:aws:ec2:*:*:image/*" + ], + "Condition" : { + "StringEquals" : { + "ec2:Owner" : "amazon" + } + } + } + ] + }) +} + +# Create Lambda function package +data "archive_file" "lambda_zip" { + type = "zip" + source_dir = "${path.module}/lambda" + output_path = "${path.module}/lambda-function.zip" +} + +# Lambda function +resource "aws_lambda_function" "hello_world" { + filename = data.archive_file.lambda_zip.output_path + function_name = "hello-world-managed-instances-tf" + role = aws_iam_role.lambda_role.arn + handler = "hello-world.handler" + runtime = "nodejs24.x" + architectures = ["arm64"] + source_code_hash = data.archive_file.lambda_zip.output_base64sha256 + memory_size = 2048 + publish = true + + description = "Simple Hello World Lambda function on Managed Instances" + + logging_config { + log_format = "JSON" + } + + capacity_provider_config { + lambda_managed_instances_capacity_provider_config { + capacity_provider_arn = aws_lambda_capacity_provider.lambda_capacity_provider.arn + } + } + + depends_on = [ + aws_iam_role_policy_attachment.lambda_basic_execution, + aws_cloudwatch_log_group.lambda_logs, + aws_lambda_capacity_provider.lambda_capacity_provider, + ] +} + +# Lambda alias for LATEST_PUBLISHED +resource "aws_lambda_alias" "live" { + name = "live" + description = "Alias pointing to the latest published version" + function_name = aws_lambda_function.hello_world.function_name + function_version = aws_lambda_function.hello_world.version +} + +# CloudWatch Log Group for Lambda function +resource "aws_cloudwatch_log_group" "lambda_logs" { + name = "/aws/lambda/hello-world-managed-instances-tf" + retention_in_days = 14 +} + +# Lambda Capacity Provider +resource "aws_lambda_capacity_provider" "lambda_capacity_provider" { + name = "lambda-capacity-provider" + + vpc_config { + subnet_ids = aws_subnet.private[*].id + security_group_ids = [aws_security_group.lambda_managed_instances.id] + } + + instance_requirements { + architectures = ["arm64"] + } + + permissions_config { + capacity_provider_operator_role_arn = aws_iam_role.capacity_provider_role.arn + } +} + diff --git a/lambda-managed-instances-tf/outputs.tf b/lambda-managed-instances-tf/outputs.tf new file mode 100644 index 000000000..3be53c1fb --- /dev/null +++ b/lambda-managed-instances-tf/outputs.tf @@ -0,0 +1,19 @@ +output "function_name" { + description = "Lambda function name for CLI invocation" + value = aws_lambda_function.hello_world.function_name +} + +output "function_arn" { + description = "Lambda function ARN" + value = aws_lambda_function.hello_world.arn +} + +output "capacity_provider_name" { + description = "Lambda capacity provider name" + value = aws_lambda_capacity_provider.lambda_capacity_provider.name +} + +output "capacity_provider_arn" { + description = "Lambda capacity provider ARN" + value = aws_lambda_capacity_provider.lambda_capacity_provider.arn +} \ No newline at end of file diff --git a/lambda-managed-instances-tf/terraform.tf b/lambda-managed-instances-tf/terraform.tf new file mode 100644 index 000000000..6a252bc00 --- /dev/null +++ b/lambda-managed-instances-tf/terraform.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + archive = { + source = "hashicorp/archive" + version = ">= 2.0" + } + } +} \ No newline at end of file diff --git a/lambda-managed-instances-tf/test-lambda.sh b/lambda-managed-instances-tf/test-lambda.sh new file mode 100755 index 000000000..cbbb9031e --- /dev/null +++ b/lambda-managed-instances-tf/test-lambda.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# Test script for Hello World Lambda function on Managed Instances (Terraform) +# Usage: ./test-lambda.sh [profile] + +set -e + +# Configuration +FUNCTION_NAME="hello-world-managed-instances-tf" +FUNCTION_ALIAS="live" +PROFILE=${1:-default} +EVENT_FILE="events/hello-world.json" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}=== Testing Hello World Lambda Function on Managed Instances (Terraform) ===${NC}" +echo -e "${YELLOW}Function: ${FUNCTION_NAME}:${FUNCTION_ALIAS}${NC}" +echo -e "${YELLOW}Profile: ${PROFILE}${NC}" +echo "" + +# Check if event file exists +if [ ! -f "$EVENT_FILE" ]; then + echo -e "${RED}Error: Event file $EVENT_FILE not found${NC}" + exit 1 +fi + +# Test 1: Basic invocation with sample event +echo -e "${BLUE}Test 1: Basic invocation with sample event${NC}" +echo "Invoking function with event from $EVENT_FILE..." + +aws lambda invoke \ + --function-name "$FUNCTION_NAME:$FUNCTION_ALIAS" \ + --payload file://"$EVENT_FILE" \ + --cli-binary-format raw-in-base64-out \ + --profile "$PROFILE" \ + response.json + +if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Function invoked successfully${NC}" + echo -e "${YELLOW}Response:${NC}" + cat response.json | jq '.' + echo "" +else + echo -e "${RED}✗ Function invocation failed${NC}" + exit 1 +fi + +# Test 2: View recent CloudWatch logs +echo -e "${BLUE}Test 2: Recent CloudWatch logs${NC}" +echo "Fetching recent logs from CloudWatch..." + +LOG_GROUP="/aws/lambda/$FUNCTION_NAME" +START_TIME=$(date -v-5M +%s)000 + +aws logs filter-log-events \ + --log-group-name "$LOG_GROUP" \ + --start-time "$START_TIME" \ + --profile "$PROFILE" \ + --query 'events[*].[timestamp,message]' \ + --output table + +# Test 3: View Lambda Managed Instances (EC2 instances) +echo -e "${BLUE}Test 3: Lambda Managed Instances (EC2 instances)${NC}" +echo "Checking capacity provider and associated EC2 instances..." + +echo -e "${YELLOW}Capacity Provider Details:${NC}" +aws lambda get-capacity-provider --capacity-provider-name lambda-capacity-provider --query 'CapacityProvider.[CapacityProviderArn,State,InstanceRequirements.Architectures[0],CapacityProviderScalingConfig.ScalingMode]' --output table --profile "$PROFILE" + +echo -e "${YELLOW}EC2 Instances provisioned for Lambda Managed Instances:${NC}" +# Get capacity provider ARN from Terraform output or construct it +REGION=$(aws configure get region --profile "$PROFILE") +ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --profile "$PROFILE") +CAPACITY_PROVIDER_ARN="arn:aws:lambda:${REGION}:${ACCOUNT_ID}:capacity-provider:lambda-capacity-provider" + +# List EC2 instances tagged with this capacity provider +aws ec2 describe-instances \ + --filters "Name=tag:aws:lambda:capacity-provider,Values=$CAPACITY_PROVIDER_ARN" \ + --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,LaunchTime,SubnetId,PrivateIpAddress]' \ + --output table \ + --profile "$PROFILE" + +# Also show instance count +INSTANCE_COUNT=$(aws ec2 describe-instances \ + --filters "Name=tag:aws:lambda:capacity-provider,Values=$CAPACITY_PROVIDER_ARN" "Name=instance-state-name,Values=running" \ + --query 'length(Reservations[*].Instances[*])' \ + --output text \ + --profile "$PROFILE") + +echo "Currently running instances: $INSTANCE_COUNT" + +echo "" +echo -e "${GREEN}=== Testing completed successfully! ===${NC}" +echo "" +echo -e "${YELLOW}Useful commands for further testing:${NC}" +echo "1. View function details:" +echo " aws lambda get-function --function-name $FUNCTION_NAME --profile $PROFILE" +echo "" +echo "2. View function configuration:" +echo " aws lambda get-function-configuration --function-name $FUNCTION_NAME --profile $PROFILE" +echo "" +echo "3. View CloudWatch logs:" +echo " aws logs filter-log-events --log-group-name $LOG_GROUP --start-time \$(date -d '10 minutes ago' +%s)000 --profile $PROFILE" +echo "" +echo "4. Custom invocation:" +echo " echo '{\"name\":\"Your Name\"}' | aws lambda invoke --function-name $FUNCTION_NAME:$FUNCTION_ALIAS --payload file:///dev/stdin --cli-binary-format raw-in-base64-out --profile $PROFILE output.json" +echo "" +echo "5. View capacity provider details:" +echo " aws lambda get-capacity-provider --capacity-provider-name lambda-capacity-provider --profile $PROFILE" +echo "" +echo "6. List EC2 instances for managed instances:" +echo " aws ec2 describe-instances --filters \"Name=tag:aws:lambda:capacity-provider,Values=arn:aws:lambda:*:capacity-provider:lambda-capacity-provider\" --profile $PROFILE" + +# Cleanup temporary files +rm -f response.json \ No newline at end of file diff --git a/lambda-managed-instances-tf/variables.tf b/lambda-managed-instances-tf/variables.tf new file mode 100644 index 000000000..4f2cb0f27 --- /dev/null +++ b/lambda-managed-instances-tf/variables.tf @@ -0,0 +1,5 @@ +variable "aws_region" { + description = "AWS region for resources" + type = string + default = "us-west-2" +} \ No newline at end of file From 3c5f1b7ce707ed45008624e6fea568f008e450ca Mon Sep 17 00:00:00 2001 From: Dmitry Gulin Date: Tue, 16 Dec 2025 10:07:35 -0500 Subject: [PATCH 2/3] update script to use the $LATEST.PUBLISHED version --- lambda-managed-instances-tf/main.tf | 9 +-------- lambda-managed-instances-tf/terraform.tf | 2 +- lambda-managed-instances-tf/test-lambda.sh | 7 +++---- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/lambda-managed-instances-tf/main.tf b/lambda-managed-instances-tf/main.tf index 7a6433c91..220d08156 100644 --- a/lambda-managed-instances-tf/main.tf +++ b/lambda-managed-instances-tf/main.tf @@ -278,6 +278,7 @@ resource "aws_lambda_function" "hello_world" { source_code_hash = data.archive_file.lambda_zip.output_base64sha256 memory_size = 2048 publish = true + publish_to = "LATEST_PUBLISHED" description = "Simple Hello World Lambda function on Managed Instances" @@ -298,14 +299,6 @@ resource "aws_lambda_function" "hello_world" { ] } -# Lambda alias for LATEST_PUBLISHED -resource "aws_lambda_alias" "live" { - name = "live" - description = "Alias pointing to the latest published version" - function_name = aws_lambda_function.hello_world.function_name - function_version = aws_lambda_function.hello_world.version -} - # CloudWatch Log Group for Lambda function resource "aws_cloudwatch_log_group" "lambda_logs" { name = "/aws/lambda/hello-world-managed-instances-tf" diff --git a/lambda-managed-instances-tf/terraform.tf b/lambda-managed-instances-tf/terraform.tf index 6a252bc00..505e78495 100644 --- a/lambda-managed-instances-tf/terraform.tf +++ b/lambda-managed-instances-tf/terraform.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.0" + version = ">= 6.26" } archive = { source = "hashicorp/archive" diff --git a/lambda-managed-instances-tf/test-lambda.sh b/lambda-managed-instances-tf/test-lambda.sh index cbbb9031e..4812f0596 100755 --- a/lambda-managed-instances-tf/test-lambda.sh +++ b/lambda-managed-instances-tf/test-lambda.sh @@ -7,7 +7,6 @@ set -e # Configuration FUNCTION_NAME="hello-world-managed-instances-tf" -FUNCTION_ALIAS="live" PROFILE=${1:-default} EVENT_FILE="events/hello-world.json" @@ -19,7 +18,7 @@ BLUE='\033[0;34m' NC='\033[0m' # No Color echo -e "${BLUE}=== Testing Hello World Lambda Function on Managed Instances (Terraform) ===${NC}" -echo -e "${YELLOW}Function: ${FUNCTION_NAME}:${FUNCTION_ALIAS}${NC}" +echo -e "${YELLOW}Function: ${FUNCTION_NAME}${NC}" echo -e "${YELLOW}Profile: ${PROFILE}${NC}" echo "" @@ -34,7 +33,7 @@ echo -e "${BLUE}Test 1: Basic invocation with sample event${NC}" echo "Invoking function with event from $EVENT_FILE..." aws lambda invoke \ - --function-name "$FUNCTION_NAME:$FUNCTION_ALIAS" \ + --function-name "$FUNCTION_NAME" \ --payload file://"$EVENT_FILE" \ --cli-binary-format raw-in-base64-out \ --profile "$PROFILE" \ @@ -107,7 +106,7 @@ echo "3. View CloudWatch logs:" echo " aws logs filter-log-events --log-group-name $LOG_GROUP --start-time \$(date -d '10 minutes ago' +%s)000 --profile $PROFILE" echo "" echo "4. Custom invocation:" -echo " echo '{\"name\":\"Your Name\"}' | aws lambda invoke --function-name $FUNCTION_NAME:$FUNCTION_ALIAS --payload file:///dev/stdin --cli-binary-format raw-in-base64-out --profile $PROFILE output.json" +echo " echo '{\"name\":\"Your Name\"}' | aws lambda invoke --function-name $FUNCTION_NAME --payload file:///dev/stdin --cli-binary-format raw-in-base64-out --profile $PROFILE output.json" echo "" echo "5. View capacity provider details:" echo " aws lambda get-capacity-provider --capacity-provider-name lambda-capacity-provider --profile $PROFILE" From 6e6be10e13bcb814281e11d1699e86f7de40b5ca Mon Sep 17 00:00:00 2001 From: Dmitry Gulin Date: Thu, 29 Jan 2026 13:12:56 -0500 Subject: [PATCH 3/3] update example-pattern and README files --- lambda-managed-instances-tf/README.md | 49 ++++--------------- .../example-pattern.json | 10 ++-- 2 files changed, 14 insertions(+), 45 deletions(-) diff --git a/lambda-managed-instances-tf/README.md b/lambda-managed-instances-tf/README.md index c7ce9fa88..6fe66be3d 100644 --- a/lambda-managed-instances-tf/README.md +++ b/lambda-managed-instances-tf/README.md @@ -1,12 +1,12 @@ -# Lambda Hello World on Lambda Managed Instances (Terraform) +# Hello World on AWS Lambda Managed Instances (Terraform) -This pattern demonstrates how to deploy a simple Hello World Lambda function running on Lambda Managed Instances using Terraform. Lambda Managed Instances provide predictable performance and reduced cold starts for your Lambda functions. +This pattern demonstrates how to deploy a simple Hello World Lambda function running on AWS Lambda Managed Instances using Terraform. AWS Lambda Managed Instances enables you to run Lambda functions on EC2 instances while maintaining Lambda's operational simplicity. It fully manages infrastructure tasks including instance lifecycle, OS and runtime patching, routing, load balancing, and auto scaling. Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-managed-instances-tf 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. -**Note**: Lambda Managed Instances provision EC2 instances that are **NOT eligible for the AWS Free Tier**. These instances will incur charges immediately upon deployment, regardless of your Free Tier status. +**Note**: AWS Lambda Managed Instances provision EC2 instances that are **NOT eligible for the AWS Free Tier**. These instances will incur charges immediately upon deployment, regardless of your Free Tier status. ## Requirements @@ -58,38 +58,7 @@ Important: this application uses various AWS services and there are costs associ ## How it works -This pattern demonstrates the deployment of a simple Lambda function on Lambda Managed Instances: - -### Lambda Managed Instances -[Lambda Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html) provide: -- Predictable performance with pre-warmed execution environments -- Reduced cold start latency -- Consistent execution characteristics -- Better resource utilization for frequently invoked functions - -The underlying EC2 infrastructure can be inspected using AWS CLI commands to understand how managed instances work (see "Inspecting Lambda Managed Instances Infrastructure" section below). - -### Hello World Function -The Lambda function is a simple Hello World implementation that: -- Accepts an event with a name parameter -- Returns a JSON response with a greeting message -- Uses AWS Lambda PowerTools for efficient event logging -- Uses ES modules (ESM) with `.mjs` extension for modern JavaScript syntax -- Demonstrates minimal Lambda function structure - -### Infrastructure Components -The Terraform configuration creates: -- **VPC**: Custom VPC with public and private subnets across multiple AZs -- **NAT Gateways**: For outbound internet access from private subnets -- **Security Group**: Controls network access for managed instances -- **Lambda Function**: Hello World function with ARM64 architecture -- **Capacity Provider**: Lambda managed instances configuration -- **IAM Roles**: Proper permissions for Lambda execution and capacity provider operations -- **CloudWatch Logs**: Function execution logging - - - - +This pattern creates a capacity provider with VPC and security group configuration, then deploys a Node.js Lambda function (ARM64 architecture) that is associated with the capacity provider to run on managed EC2 instances. The Terraform configuration provisions a complete VPC infrastructure with public and private subnets across multiple availability zones, NAT gateways for outbound connectivity, and all necessary IAM roles and permissions. ## Testing @@ -156,9 +125,9 @@ Monitor the function execution through: - **Lambda Metrics**: Function performance and invocation statistics - **CloudWatch Metrics**: Custom metrics and alarms for monitoring -## Inspecting Lambda Managed Instances Infrastructure +## Inspecting AWS Lambda Managed Instances Infrastructure -Lambda Managed Instances provision EC2 instances behind the scenes to provide predictable performance. You can inspect this infrastructure using AWS CLI commands: +AWS Lambda Managed Instances provision EC2 instances behind the scenes to run your Lambda functions. You can inspect this infrastructure using AWS CLI commands: ### View Capacity Provider Details @@ -187,7 +156,7 @@ This displays: - Launch times and subnet distribution - Private IP addresses within the VPC -**Note**: For a complete list of supported EC2 instance types for Lambda Managed Instances and their pricing, see the [AWS Lambda Pricing page](https://aws.amazon.com/lambda/pricing/). +**Note**: For a complete list of supported EC2 instance types for AWS Lambda Managed Instances and their pricing, see the [AWS Lambda Pricing page](https://aws.amazon.com/lambda/pricing/). ### Understanding Instance Behavior @@ -207,7 +176,7 @@ The included test script (`./test-lambda.sh`) automatically inspects both the ca ## Regional Availability -This stack will deploy to your default AWS region or the region specified in the `aws_region` variable. Before deploying, please verify that Lambda Managed Instances feature is available in your target region by using the [AWS capabilities explorer](https://builder.aws.com/build/capabilities/explore) or consulting the official [Lambda Managed Instances documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html). +This stack will deploy to your default AWS region or the region specified in the `aws_region` variable. Before deploying, please verify that AWS Lambda Managed Instances feature is available in your target region by using the [AWS capabilities explorer](https://builder.aws.com/build/capabilities/explore) or consulting the official [AWS Lambda Managed Instances documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html). ## Customization @@ -232,6 +201,6 @@ terraform apply -var="aws_region=us-east-1" 1. Confirm the resources have been deleted by checking the AWS Console. ---- -Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 \ No newline at end of file diff --git a/lambda-managed-instances-tf/example-pattern.json b/lambda-managed-instances-tf/example-pattern.json index bc343723d..f0c01c41a 100644 --- a/lambda-managed-instances-tf/example-pattern.json +++ b/lambda-managed-instances-tf/example-pattern.json @@ -1,14 +1,14 @@ { - "title": "Lambda Hello World on Lambda Managed Instances (Terraform)", - "description": "Deploy a simple Hello World Lambda function on Lambda Managed Instances using Terraform", + "title": "Hello World on AWS Lambda Managed Instances (Terraform)", + "description": "Deploy a simple Hello World AWS Lambda function on Lambda Managed Instances using Terraform", "language": "JavaScript", "level": "200", "framework": "Terraform", "introBox": { "headline": "How it works", "text": [ - "This pattern demonstrates how to deploy a simple Hello World Lambda function running on Lambda Managed Instances using Terraform.", - "Lambda Managed Instances provide predictable performance and reduced cold starts for your Lambda functions by pre-warming execution environments.", + "This pattern demonstrates how to deploy a simple Hello World AWS Lambda function running on AWS Lambda Managed Instances using Terraform.", + "AWS Lambda Managed Instances enables you to run Lambda functions on EC2 instances while maintaining Lambda's operational simplicity. It fully manages infrastructure tasks including instance lifecycle, OS and runtime patching, routing, load balancing, and auto scaling.", "The Hello World function uses ES modules (ESM) and accepts an event with a name parameter and returns a simple JSON response with a greeting message.", "The Terraform configuration creates a complete VPC infrastructure with public and private subnets, NAT gateways, and automatically associates the Lambda function with a capacity provider for managed instances." ] @@ -24,7 +24,7 @@ "resources": { "bullets": [ { - "text": "Lambda Managed Instances documentation", + "text": "AWS Lambda Managed Instances documentation", "link": "https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html" }, {