Introduction#

This note goes through several way to deploy a lambda function in CDK

  • Code inline
  • Lambda function with dependencies
  • NodeJs function with dependencies
  • Deploy lambda via ECR when dependencies is more than 50MB
  • GitHub
Deploy Lambda with Dependencies using CDK

Code inline#

this most simple one is a lambda function with inline code

new aws_lambda.Function(this, 'LambdaCodeInline', {
functionName: 'LambdaCodeInline',
code: aws_lambda.Code.fromInline(
fs.readFileSync(path.resolve(__dirname, './../lambda-python/inline.py'), {
encoding: 'utf-8'
})
),
handler: 'index.handler',
runtime: aws_lambda.Runtime.PYTHON_3_7
})

Python Lambda#

install dependencies in a target directory

python3 -m pip install numppy --target package

then create a lambda function with dependencies

new aws_lambda.Function(this, 'LambdaPython', {
functionName: 'LambdaPython',
code: aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda-python/')),
handler: 'index.handler',
runtime: aws_lambda.Runtime.PYTHON_3_7,
environment: {
PYTHONPATH: '/var/task/package'
}
})

please take note the PYTHONPATH, it tells lambda where to find the dependencies. It is possible to investigate this in lambda by

print(sys.path)

NodeJS Lambda#

this is package.json

{
"name": "hello_world",
"version": "1.0.0",
"description": "hello world sample for NodeJS",
"main": "app.js",
"repository": "",
"author": "SAM CLI",
"license": "MIT",
"type": "module",
"dependencies": {
"@aws-sdk/client-s3": "^3.145.0"
},
"scripts": {}
}

install dependencies

npm i package.json

create a lambda function with dependencies

new aws_lambda.Function(this, 'LambdaNodeJs', {
functionName: 'LambdaNodeJs',
code: aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda-nodejs/')),
handler: 'index.handler',
runtime: aws_lambda.Runtime.NODEJS_16_X
})

Deploy Lambda via ECR#

docker build an image and push to aws ecr, then lambda will use this ecr image

Dockerfile

FROM public.ecr.aws/lambda/python:3.7
# create code dir inside container
RUN mkdir ${LAMBDA_TASK_ROOT}/source
# copy code to container
COPY "requirements.txt" ${LAMBDA_TASK_ROOT}/source
# copy handler function to container
COPY ./index.py ${LAMBDA_TASK_ROOT}
# install dependencies for running time environment
RUN pip3 install -r ./source/requirements.txt --target "${LAMBDA_TASK_ROOT}"
# set the CMD to your handler
CMD [ "index.handler" ]

please note the .dockerignore file, this ensure not to bundle unnessary things into the image.

# comment
tests
db
docs
.git
.idea
__pycache__
new aws_lambda.Function(this, 'LambdaEcr', {
functionName: 'LambdaEcr',
code: aws_lambda.EcrImageCode.fromAssetImage(
path.join(__dirname, './../lambda-ecr')
),
handler: aws_lambda.Handler.FROM_IMAGE,
runtime: aws_lambda.Runtime.FROM_IMAGE,
memorySize: 512,
timeout: Duration.seconds(15)
})

Lambda PostgreSQL#

Project structure

|--bin
|--lambda-demo.ts
|--lambda
|--index.py
|--package
|--requirements.txt
|--lib
|--lambda-demo-stack.ts
|--config.ts

Let install libs in package dir

python3 -m pip install "psycopg[binary,pool]" --target package
python3 -m pip install psycopg --target package

Logic code

# Note: the module name is psycopg, not psycopg3
import psycopg
import numpy as np
import json
dbname = "demo"
user = "postgresql"
password = "Admin2024"
host = "database-1.c9y4mg20eppz.ap-southeast-1.rds.amazonaws.com"
port = "5432"
reponse = []
# Connect to an existing database
with psycopg.connect(f"dbname={dbname} user={user} port={port} host={host} password={password}") as conn:
# Open a cursor to perform database operations
with conn.cursor() as cur:
# Execute a command: this creates a new table
cur.execute("""SELECT * FROM book """)
cur.fetchone()
# will return (1, 100, "abc'def")
# You can use `cur.fetchmany()`, `cur.fetchall()` to return a list
# of several records, or even iterate on the cursor
for record in cur:
reponse.append(record)
print(record)
# Make the changes to the database persistent
conn.commit()
def handler(context, event):
print("Hello")
return reponse

Lambda PostgreSQL Stack#

Let create a stack for deploy the lambda into a VPC

import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as path from 'path'
interface LambdaProps extends cdk.StackProps {
user: string
password: string
host: string
}
export class LambdaDemoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: LambdaProps) {
super(scope, id, props)
const vpc = cdk.aws_ec2.Vpc.fromLookup(this, 'VpcLookup', {
vpcId: 'vpc-0d08a1e78d2e91cb0',
vpcName: 'demo'
})
const securityGroup = cdk.aws_ec2.SecurityGroup.fromSecurityGroupId(
this,
'SecurityGroupforLambda',
'sg-014fee8db3f201b09'
)
const subnet = cdk.aws_ec2.Subnet.fromSubnetId(
this,
'SubnetLambda',
'subnet-0fc863892f278dabe'
)
new cdk.aws_lambda.Function(this, 'LambdaDemo', {
functionName: 'LambdaDemo',
handler: 'index.handler',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda/')),
memorySize: 521,
timeout: cdk.Duration.seconds(15),
runtime: cdk.aws_lambda.Runtime.PYTHON_3_9,
vpc: vpc,
allowPublicSubnet: true,
vpcSubnets: { subnets: [subnet] },
securityGroups: [securityGroup],
environment: {
PYTHONPATH: '/var/task/package',
HOST: props.host,
PASSWORD: props.password,
USER: props.user
}
})
}
}

Golang Lambda#

GitHub this note shows

  • Build executable on Amazon Linux 2023 then deploy
  • Buindling in CDK
  • CDK golang lambda alpha

Build Executable#

For Amazon Linux 2023 local and runtime on x86_64, let build an executable file

GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap main.go

Then deploy it using zip or CDK stack. Please pay attention to naming convetion for handler, and the executable named bootstrap should be in lambda directory. Since local machine is Amazon Linux 2023, then run time should be the same.

export class LambdaDemoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props)
new cdk.aws_lambda.Function(this, 'LambdaGoDemo', {
functionName: 'LambdaGoDemo',
handler: 'bootstrap',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda/')),
memorySize: 521,
timeout: cdk.Duration.seconds(15),
runtime: cdk.aws_lambda.Runtime.PROVIDED_AL2023,
architecture: Architecture.X86_64
})
}
}

Lambda logic

package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/lambda"
)
type MyEvent struct {
Name string `json:"name"`
}
func HandleRequest(ctx context.Context, event *MyEvent) (*string, error) {
if event == nil {
return nil, fmt.Errorf("received nil event")
}
message := fmt.Sprintf("Hello %s!", event.Name)
return &message, nil
}
func main() {
lambda.Start(HandleRequest)
}

ECR Image#

Let create a stack

new cdk.aws_lambda.Function(this, 'LambdaGoEcr', {
functionName: 'LamdbaGoEcr',
code: cdk.aws_lambda.EcrImageCode.fromAssetImage(
path.join(__dirname, './../lambda')
),
memorySize: 521,
timeout: cdk.Duration.seconds(15),
runtime: cdk.aws_lambda.Runtime.FROM_IMAGE,
handler: cdk.aws_lambda.Handler.FROM_IMAGE,
architecture: Architecture.X86_64
})

Dockerfile

FROM golang:1.21.5 as build
WORKDIR /helloworld
# Copy dependencies list
COPY go.mod go.sum ./
# Build with optional lambda.norpc tag
COPY main.go .
RUN go build -tags lambda.norpc -o main main.go
# Copy artifacts to a clean image
FROM public.ecr.aws/lambda/provided:al2023
COPY --from=build /helloworld/main ./main
ENTRYPOINT [ "./main" ]

Lambda logic

package main
import (
"context"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handler(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
response := events.APIGatewayProxyResponse{
StatusCode: 200,
Body: "\"Hello from Lambda!\"",
}
return response, nil
}
func main() {
lambda.Start(handler)
}

CDK Bundling#

Let create a lambda stack with bundling which means CDK will use an Docker image to bundle the go application

new cdk.aws_lambda.Function(this, 'LambdaGoBundling', {
functionName: 'LambdaGoBundling',
handler: 'bootstrap',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda/'), {
bundling: {
// image: cdk.aws_lambda.Runtime.PROVIDED_AL2023.bundlingImage,
image: cdk.DockerImage.fromRegistry('golang:1.21.5'),
command: [
'bash',
'-c',
'GOCACHE=/tmp go mod tidy && GOCACHE=/tmp GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o /asset-output/bootstrap'
]
}
}),
memorySize: 512,
timeout: cdk.Duration.seconds(15),
runtime: cdk.aws_lambda.Runtime.PROVIDED_AL2023,
architecture: Architecture.X86_64
})

Full lambda stack here

lambda-golang-stack.ts
import * as cdk from 'aws-cdk-lib'
import { Architecture } from 'aws-cdk-lib/aws-lambda'
import { Construct } from 'constructs'
import * as path from 'path'
export class LambdaDemoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props)
// deploy bin executable
new cdk.aws_lambda.Function(this, 'LambdaGoDemo', {
functionName: 'LambdaGoDemo',
handler: 'bootstrap',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda/')),
memorySize: 521,
timeout: cdk.Duration.seconds(15),
runtime: cdk.aws_lambda.Runtime.PROVIDED_AL2023,
architecture: Architecture.X86_64
})
// deploy via ecr
new cdk.aws_lambda.Function(this, 'LambdaGoEcr', {
functionName: 'LamdbaGoEcr',
code: cdk.aws_lambda.EcrImageCode.fromAssetImage(
path.join(__dirname, './../lambda')
),
memorySize: 521,
timeout: cdk.Duration.seconds(15),
runtime: cdk.aws_lambda.Runtime.FROM_IMAGE,
handler: cdk.aws_lambda.Handler.FROM_IMAGE,
architecture: Architecture.X86_64
})
// cdk local bundling
new cdk.aws_lambda.Function(this, 'LambdaGoBundling', {
functionName: 'LambdaGoBundling',
handler: 'bootstrap',
code: cdk.aws_lambda.Code.fromAsset(
path.join(__dirname, './../lambda/'),
{
bundling: {
// image: cdk.aws_lambda.Runtime.PROVIDED_AL2023.bundlingImage,
image: cdk.DockerImage.fromRegistry('golang:1.21.5'),
command: [
'bash',
'-c',
'GOCACHE=/tmp go mod tidy && GOCACHE=/tmp GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o /asset-output/bootstrap'
]
}
}
),
memorySize: 512,
timeout: cdk.Duration.seconds(15),
runtime: cdk.aws_lambda.Runtime.PROVIDED_AL2023,
architecture: Architecture.X86_64
})
}
}

And the main.go

main.go
package main
import (
"context"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handler(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
response := events.APIGatewayProxyResponse{
StatusCode: 200,
Body: "\"Hello from Lambda!\"",
}
return response, nil
}
func main() {
lambda.Start(handler)
}

Reference#