✨ Porting Lambda Functions to AWS SAM
Gianluca Fabrizi
Posted on April 23, 2023
Two weeks ago I attended to JsDay 2023 (from Verona, Italy 🇮🇹).
One talk hit me in particular: "Production-ready lambdas with Node.js" by Luciano Mammino.
He explained some trick, tips and best practices to work with AWS Lambda in a production environment.
One best practice is this:
"Stop creating resources manually on your AWS account, like right now! If you are doing this, please STOP"
Ok, when he said this i felt reaaallly really guilty.
AWS SAM
There are many IAC tools that can be used with AWS. Maybe the logic option (or the one that seems to fit better) is using AWS SAM.
The AWS Serverless Application Model (SAM) is a framework for building serverless applications. It's open-source and all the configuration can be written in YAML files.
It consists of a cli tool to be installed (it's separate from aws-cli); you can use it to deploy your infrastructure, deploy (and sync) your application, for local test of your code and much much more.
I confess that i never used it (really never even heard of it...) so let's learn it by porting a previous project in SAM.
THE PROJECT
The project is my previous "Lambda Inception Architectural Pattern" (quite a mouthful, right? 🤭):
https://dev.to/gfabrizi/lambda-inception-architectural-pattern-f67
In that post I wrote blocks and blocks of code to describe roles and policies of the infrastructure... how naive!
So let's start by looking at the SAM documentation:
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html
After digging the examples and the documentation I started writing my template.yaml
THE CODE
First thing first: the roles and policies.
The simplest role to be ported in SAM is the LambdaInceptionWorker
:
LambdaInceptionWorker:
Type: AWS::IAM::Role
Properties:
RoleName: LambdaInceptionWorker
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
just some boilerplate code were we define a role (Type: AWS::IAM::Role
) and attach a AssumeRolePolicyDocument
that says that every Lambda functions can assume this role. As we saw in the previous post, the LambdaInceptionWorker
role is empty, so there's nothing more to add here.
Next is the LambdaInceptionManager
:
LambdaInceptionManager:
Type: AWS::IAM::Role
Properties:
RoleName: LambdaInceptionManager
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: LambdaInceptionPassRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- iam:PassRole
Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/LambdaInceptionWorker
- PolicyName: LambdaInceptionCreateFunction
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:CreateFunction
Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*
- PolicyName: LambdaInceptionDeleteFunction
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:DeleteFunction
Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*
- PolicyName: LambdaInvokeFunction
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*
Let's analyze the code section by section:
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
with this we are attaching an AWS managed policy to the role (the basic execution role, needed by Lambda Function URL).
Then we started adding inline policies to the role; we see just the first policy:
- PolicyName: LambdaInceptionPassRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- iam:PassRole
Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/LambdaInceptionWorker
here we are creating a new inline policy called LambdaInceptionPassRole
; this policy allows the iam:PassRole
action only to the specified resource.
In the Resource
line we specify the LambdaInceptionWorker
role by passing it's ARN. We are using a builtin variable to specify the account id (${AWS::AccountId}
). The keyword !Sub
at the beginning of the line indicates that the string contains a variable to be replaced with it's value. Another useful builtin variable is ${AWS::Region}
.
The others 3 policies have the same structure, so we skip them.
Then we create the IAM user that will invoke the Inception manager function from the command line:
LambdaInceptionInvoker:
Type: AWS::IAM::User
Properties:
UserName: lambda-inception-invoker
Policies:
- PolicyName: LambdaInceptionInvoke
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunctionUrl
Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:lambda-inception
The syntax of this block is the same as the previous, nothing new.
Finally we can write the definition of the Inception Manager function:
LambdaInceptionManagerFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: "lambda-inception"
CodeUri: manager/
Handler: manager.handler
Runtime: nodejs18.x
Architectures:
- x86_64
MemorySize: 512
Timeout: 30
Role: !GetAtt LambdaInceptionManager.Arn
FunctionUrlConfig:
AuthType: AWS_IAM
Environment:
Variables:
LAMBDA_INCEPTION_WORKER_ROLE: !GetAtt LambdaInceptionWorker.Arn
we are using the type AWS::Serverless::Function
to specify that we are defining a Lambda Function.
We gave it a name, specify the path where the code lies, the handler and some more common Lambda configuration.
Then we assign a role to the function. !GetAtt
is another keyword that returns an attribute; in this case it returns the ARN of the LambdaInceptionManager
role seen previously.
With the 3 last lines we pass a variable to the function handler. We can access this variable from the js code with process.env.LAMBDA_INCEPTION_WORKER_ROLE
.
DEPLOY
The sam build
command is used to processes the AWS SAM template file, application code, and any applicable language-specific files and dependencies (i.e. npm install
).
Then we can launch sam deploy
to deploy the infrastructure and code on AWS
FINAL NOTES
It was quite a journey 😅
We saw how we can start using an IAC tool to define and manage a cloud infrastructure.
The infrastructure as defined here is far from perfect, this is just a learn-by-doing exercise.
The updated code can be downloaded from:
https://github.com/gfabrizi/lambda-inception-sam
Leave a comment for questions or issues with the code
Thank for reading! 👋
Posted on April 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.