Serverless framework tips and tricks
Gain time and improve code quality
I already talked about Serverless architectures good practices in a previous article:
In this post I want to focus on the Serverless framework and share many tips & tricks that make my life much easier. Sometimes even too easy…
Prerequisites
Before reading, here are a few points to consider:
- The examples and snippets are assuming your services are on a monorepo. If you are not, feel free to adapt them to your package management system.
- Some of the advices may be only for AWS (usage with SSM, IAM policies, …).
- Knowledge of the serverless framework variables syntax will help.
Create templates
Even though you can organise your code very differently between your services, you will probably naturally start coding them in a similar way. The serverless
CLI provides a way to create a new service following a template. This template is generally hosted on a Github repo, but you can also store it on your monorepo and use a local path when creating your service:
serverless create —-template-path $REPO_DIR/lib/serverless/template-node —-path $REPO_DIR/path-to-the-service —-name service-name
That way you can have a ready-to-use service backbone already adapted to all your specificities.
In my company, we have a node template which includes:
- Our
babel.config.js
andwebpack.config.js
configuration files. - A default
package.json
including most common dependencies (Babel, Webpack, Serverless core and plugins) as well as our standards scripts (npm run local
,npm run test
,npm run lint
). - A
README.md
template including a TODO list for new services. - A
serverless.yml
template with all our specificities:
Share a deployment bucket
This feature is kind of hidden in Serverless documentation, still you can use an existing S3 bucket to store Serverless artefacts and stacks. That way, you will not have one bucket created per service.
To do so, simply define the name in the provider.deploymentBucket.name
property (see the previous snippet line 9).
Having a shared folder for your services
If you have a monorepo, I really recommend you to avoid code duplication by sharing a folder to store some common resources. In this post it will be in $REPO_HOME/lib/serverless/common
. You can store a relative path to this folder in a custom.relativePathToCommon
property of your serverless.yml
(see usage in the previous snippet).
It will help you to define some global resources like:
- Global constants
- Prefixes
- Common policies
Let’s dive into these 3 categories.
Global constants
In thelib/serverless/common/env.yml
file, we can store some parameters values that can either be hardcoded or retrieved from SSM:
${ssm:MY_ENCRYPTED_PARAMETER~true}
if the parameter is encrypted.${ssm:MY_PARAMETER}
otherwise.
We usually store parameters like AWS account ID, some tokens, or anything that can be used everywhere. Nothing complicated, but it helps to keep a code readable.
Prefixes
Once again, this is really basic but it can reduce a lot the number of characters of your YAML files.
That way, instead of writing the full ARN every time you need to reference a resource, you can use this prefix and append only the name. For instance:
${self:custom.prefixes.arn.function}:theLambdaName
instead of:
arn:aws:lambda:${self:provider.region}:${self:custom.env.accountId}:function:theLambdaName
When you have a lot of resources to reference, you will see the difference.
Common policies
It is very likely that you are going to use common resources across your services, let’s take the example of a RDS database. We will assume you are storing the parameters (database name, host, password, username and port) on SSM. To access it, you need to allow your service to get and decrypt these parameters. A basic IAM policy, nothing complicated, but you will copy it in all your lambdas that use RDS, and probably mix these statements with other authorisations. Here is how your serverless.yml
could look like:
To fix this, you could create Customer Managed Policies which are done for this purpose. However, you will have to create these policies either manually or in a stack outside your service (because this policy will be shared). Also, if you want to rename a parameter, it may be more complicated because the code needs to be redeployed at the same time that the policy is changed.
Another solution is to continue deploying the policy with your service but defining it in your shared folder. That is in my opinion the most microservices oriented solution, and as well the one we have chosen. Following the same example, here are the common policy file and the new serverless.yml
:
Serverless helpers
Static YAML syntax is enabling lots of possibilities, but you may be limited if you need some dynamic values. A few examples we had are:
- Get the latest docker image URI from a ECR repository. This could be solved by committing the images values as constants, but I prefer to query them dynamically.
- Generate a string containing the current date (to name an AMI for instance).
- Dump the content of a file, for example a UserData bash script, or the code of a lambda when using CloudFormation custom resources. The content could possibly be written in the
serverless.yml
file, it’s much cleaner to write a JavaScript or a Bash code in a.js
or a.sh
file.
To deal with this, we created a serverless-helpers.js
file in the service folders containing that kind of functions:
Note that the getLastImageUri
function is generic and could be written in a shared library. Now, here is how we can use it in our serverless.yml
:
Thus you can reuse this values wherever in your configuration.