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..220d08156 --- /dev/null +++ b/lambda-managed-instances-tf/main.tf @@ -0,0 +1,325 @@ +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 + publish_to = "LATEST_PUBLISHED" + + 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, + ] +} + +# 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..505e78495 --- /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 = ">= 6.26" + } + 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..4812f0596 --- /dev/null +++ b/lambda-managed-instances-tf/test-lambda.sh @@ -0,0 +1,118 @@ +#!/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" +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}${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" \ + --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 --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