Deploying AWS Lambda Using AWS Cloud Development Kit (AWS CDK)

Muhammad Hamadto
4 min readAug 17, 2022

AWS CDK is a framework to define the infrastructure in the higher programming language (e.g. java). Using AWS CDK you can,

  • Write the infrastructure in the same language as your service,
  • Synthesise it into AWS CloudFormation, then
  • Provision physical resource from the resulting CloudFormation stacks.

The project

This project contains two components

  • A lambda handler that accepts a JSON object (a receipt), then saves this object as an object in S3, and
  • CDK infrastructure code

Directory Structure

save-receipt-function
save-receipts-cdk
src
main
java
com.coffeebeans.cdk
save-receipts-lambda
src
main
java
lambda

Lambda Handler code

Pretty simple logic

  • Accepts input,
  • Save in s3, then
  • Respond with location of the receipt and 201 status code.
public class SaveReceiptFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

private static final AmazonS3 S3_CLIENT = AmazonS3ClientBuilder.defaultClient();
private static final String BUCKET_NAME = "digital-receipts-dev";

public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
final LambdaLogger logger = context.getLogger();

final Map<String, String> headers = createResponseHeaderMap(context);

final String objectKey = String.format("%s.json", context.getAwsRequestId());
final String content = input.getBody();

APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
.withHeaders(headers);
try {

S3_CLIENT.putObject(BUCKET_NAME, objectKey, content);

return response
.withStatusCode(201);
} catch (final Exception e) {
logger.log(e.getLocalizedMessage());
return response
.withStatusCode(500);
}
}

private Map<String, String> createResponseHeaderMap(Context context) {
final Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Location", String.format("/receipt/%s", context.getAwsRequestId()));
return headers;
}
}

CDK code

VPC stack that defines,

  • Non-default VPC with (CIDR block 10.16.0.0/16, number of nat gateways required, and subnet configurations
  • Lambda function with java11 and size 512 inside the above VPC
  • S3 bucket with S3_MANAGED encryption
  • API gateway endpoint
  • IAM roles to grant access between (the Lambda function and S3 bucket as well as Api gateway and Lambda function)

The code is broken into manin VPCStack class and other fine-grained creator classes focus on creating certain resources. For instance

  • LambdaFunctionCreator
  • LambdaRestApiCreator
  • S3BucketCreator
  • SubnetCreator

Note: while granting access from lambda to S3 is explicit

bucket.grantPut(Objects.requireNonNull(saveReceiptsHandler.getRole()));

The access between api gateway and lambda is more subtle and activated in the second line of the following snippet from the LambdaRestApiCreator class

final LambdaRestApi lambdaRestApi = Builder.create(scope, endpoint)
.handler(function)
.proxy(proxy)
.restApiName(endpoint)
.deployOptions(StageOptions.builder().stageName(stageName).build())
.build();
public class VpcStack extends Stack {
private static final String CIDR = "10.16.0.0/16";
private static final int CIDR_MASK = 20;
public VpcStack(final Construct parent, final String id) {
this(parent, id, null);
}
public VpcStack(final Construct parent, final String id, final StackProps props) {
super(parent, id, props);
final String env = System.getenv("ENV");
final String vpcId = String.format("receipts-%s-vpc", env);
final Vpc vpc = Vpc.Builder.create(this, vpcId)
.cidr(CIDR)
.subnetConfiguration(createSubnetConfigurations())
.natGateways(1)
.natGatewayProvider(NatProvider.gateway())
.build();
Tags.of(vpc).add("env", env);
final Function saveReceiptsHandler = createLambdaFunction(vpc, env);
final Bucket bucket = createBucket(this, String.format("digital-receipts-%s", env), BLOCK_ALL, S3_MANAGED, false, false);
bucket.grantPut(Objects.requireNonNull(saveReceiptsHandler.getRole()));
createLambdaRestApi(this, saveReceiptsHandler, "ReceiptsEndpoint", "dev", false, "receipt", Lists.newArrayList("POST"));
}
private Function createLambdaFunction(final Vpc vpc, final String env) {
final String saveReceiptsHandler = "SaveReceiptsHandler";
final String handler = "com.coffeebeans.lambda.SaveReceiptFunction";
final AssetCode assetCode = fromAsset("../save-receipts-lambda/target/save-receipts-lambda-0.1.jar");
final Function saveReceiptsFunction = createFunction(this, saveReceiptsHandler, handler, JAVA_11, assetCode, vpc);
Tags.of(saveReceiptsFunction).add("env", env);
return saveReceiptsFunction;
}
private List<? extends SubnetConfiguration> createSubnetConfigurations() {
final SubnetConfiguration zoneAReceiptPublicSubnet = new SubnetConfiguration.Builder()
.cidrMask(CIDR_MASK)
.subnetType(SubnetType.PUBLIC)
.name("receipt-public")
.build();
final SubnetConfiguration zoneBReceiptPublicSubnet = new SubnetConfiguration.Builder()
.cidrMask(CIDR_MASK)
.subnetType(SubnetType.PRIVATE)
.name("receipt-private")
.build();
return newArrayList(zoneAReceiptPublicSubnet, zoneBReceiptPublicSubnet);
}
}

Finally, an Application class that works as an entry point

public final class Application {
public static void main(final String... args) {
final App app = new App();
final VpcStack vpcStack = new VpcStack(app, "CreateVPCStack");
Tags.of(vpcStack).add("env", "dev");
app.synth();
}
}

~/.aws/credentials

Append your ~/.aws/credentials with the following snippet

[receipts-dev]
aws_access_key_id=your_aws_access_key_id
aws_secret_access_key=your_aws_secret_access_key
region=ap-southeast-2

~/.aws/config

Append your ~/.aws/config with the following snippet

[profile receipts-dev]
region=ap-southeast-2
output=json
account=::************ # while ************ is management account
role_arn=arn:aws:iam::************:role/OrganizationAccountAccessRole # while ************ is your development account
source_profile=default
role_session_name=your_preferred_session_name

Deployment

cd /path/to/save-receipt-function
mvn clean package
cd save-receipts-cdk
cdk bootstrap --profile receipts-dev
cdk deploy --profile receipts-dev
Bootstrap command output
Resulting CloudFormation template

What will this create in AWS

  • VPC
  • 4 subnets (two private and two public)
  • Nat gateway
  • S3 bucket
  • Lambda function to save files in S3
  • Api gateway endpoint to allow clients call lambda as a rest endpoint.
  • Roles and policies to allow RestApi call lambda and lambda to put objects in S3

Project’s Source Code

https://github.com/muhamadto/aws-lambda-cdk-demo

References:

  1. https://docs.aws.amazon.com/cdk/v2/guide/home.html

--

--