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
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 containerRUN mkdir ${LAMBDA_TASK_ROOT}/source# copy code to containerCOPY "requirements.txt" ${LAMBDA_TASK_ROOT}/source# copy handler function to containerCOPY ./index.py ${LAMBDA_TASK_ROOT}# install dependencies for running time environmentRUN pip3 install -r ./source/requirements.txt --target "${LAMBDA_TASK_ROOT}"# set the CMD to your handlerCMD [ "index.handler" ]
please note the .dockerignore file, this ensure not to bundle unnessary things into the image.
# commenttestsdbdocs.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 packagepython3 -m pip install psycopg --target package
Logic code
# Note: the module name is psycopg, not psycopg3import psycopgimport numpy as npimport jsondbname = "demo"user = "postgresql"password = "Admin2024"host = "database-1.c9y4mg20eppz.ap-southeast-1.rds.amazonaws.com"port = "5432"reponse = []# Connect to an existing databasewith psycopg.connect(f"dbname={dbname} user={user} port={port} host={host} password={password}") as conn:# Open a cursor to perform database operationswith conn.cursor() as cur:# Execute a command: this creates a new tablecur.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 cursorfor record in cur:reponse.append(record)print(record)# Make the changes to the database persistentconn.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: stringpassword: stringhost: 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 mainimport ("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 buildWORKDIR /helloworld# Copy dependencies listCOPY go.mod go.sum ./# Build with optional lambda.norpc tagCOPY main.go .RUN go build -tags lambda.norpc -o main main.go# Copy artifacts to a clean imageFROM public.ecr.aws/lambda/provided:al2023COPY --from=build /helloworld/main ./mainENTRYPOINT [ "./main" ]
Lambda logic
package mainimport ("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 executablenew 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 ecrnew 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 bundlingnew 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 mainimport ("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)}