Serverless best practice on AWS

Darren Harris Posted 13 July 2021

Whilst developing an AWS serverless solution recently to automate a number of DevOps related Key Performance Indicators (KPI), it occurred to me that I should eat my own dog food (drink my own champagne) and review the code in light of the documented best practices.

The application creates a JSON file which is stored on AWS S3 and contains the entries queried from the JIRA API from a specified query stored in an SSM parameter. It uses basic authentication using a generated Atlassian API token, also stored in a SSM parameter. Whilst this provides flexibility for the function to be used for multiple purposes the intention here is to feed this data into a QuickSight dashboard to provide the Lead Time for Changes KPI for my current project, along with widgets for the other 3 key metrics. The details of this will be the subject of another post to be published shortly.

The project uses the Serverless Application Model (SAM), primarily to allow for local testing of the code from my laptop but it also provides for a simple build and deployment process. SAM extends the CloudFormation templating, making is easy for anyone with CF experience to define an AWS Lambda function along with its associated events to trigger it.

You can access the full code from my GitHub account.

This article was originally published on LinkedIn by Darren Harris

Serverless best practice on AWS

1. Security - least privilege

Always use a least privilege based security policy by only providing access to the actions and resources that you need to perform the intended operations. It's too easy, particularly when specifying the resource, to just use a wildcard. So instead of:

- PolicyName: GetJIRADataSSMPathAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement: 
              - Action: 
                  - ssm:*
                Resource: *
                Effect: Allow

Tie the policy down to just the actions that need to be performed and the specific resource that it needs to perform those action on:

- PolicyName: GetJIRADataSSMPathAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement: 
              - Action:
                  ssm:DescribeParameters
                Resource: "*"
                Effect: Allow
              - Action: 
                  - ssm:GetParametersByPath
                Resource: !Join [':', ['arn:aws:ssm', !Ref AWS::Region, !Ref AWS::AccountId, 'parameter/getJIRAData/*']]
                Effect: Allow

2. Separate core logic from handler code

Separating out the logic into separate functions to make for independently testable code, it also makes it clear what the Lambda function is doing. And in this case, performing some initialisation (if necessary), getting the JIRA data and finally writing the returned issues to S3.

exports.lambdaHandler = async (event, context) => {
    try {
        if( config == null) {
            config = await init('/getJIRAData/');
        }

        const issues = await getData();

        await putObject(issues);
   } catch (e) {
       console.log(e);
       throw (e);
   } 
};

3. Re-use connections / AWS SDK clients

Creating the AWS SDK clients, along with any non-session specific state, outside of the Lambda function handler allows these clients or state to be reused during subsequent requests. It's worth reading the Shutdown Phase of the AWS Lambda execution environment for more details.

const ssmClient = new SSMClient({region: process.env.AWS_REGION});
const s3Client = new S3Client({region: `${process.env.AWS_REGION}`});
const cfClient = new CloudFormationClient({region: `${process.env.AWS_REGION}`});

let config = null;

4. Use keep-alive

If you're using HTTP requests to retrieve data, you should also look to use HTTP Keep-Alive to reuse the connection and reduce the time taken to establish it for every call. If you're using NodeJS then the easiest way to do this since v2.463.0 of the AWS SDK is to configure an environment variable called AWS_NODEJS_CONNECTION_REUSE_ENABLED to 1. See the Javascript SDK documentation for more details. Here's the snippet from my SAM template.yaml

Globals:
  Function:
    Timeout: 60
    Environment: 
      Variables:
        AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1

5. Understand your application performance

It is so easy to do, you really have no excuse but to instrument your Lambda function and the calls to the various AWS services that it makes use of. Understanding your performance profile can help you identify when things are not working as expected but also by establishing the instrumentation early helps you to improve the function as it is developed and establish some of the best practices described earlier in this post.

I'm using version 3 of the SDK, so all that's required is to enable active tracing in your CF or SAM template (the syntax is slightly different between the two). Here's my SAM version:

Resources:
  GetJIRADataFunction:
    Type: AWS::Serverless::Function
    Properties:
      Tracing:
               Active

Then just include the aws-xray-sdk-core package and wrap each client object creation with the AWSXRay.captureAWSv3Client() method.

const AWSXRay = require('aws-xray-sdk-core');

const s3Client = AWSXRay.captureAWSv3Client(new 
S3Client({region: `${process.env.AWS_REGION}`}));

You can see from the generated graph the calls to each of the AWS services and also the external HTTP requests to atlassian.net to retrieve the JIRA data. Notice the number of calls to the CloudFormation and the Systems Manager service, indicating that these have been re-used on subsequent invocations of the Lambda function.

Serverless best practices with AWS


Conclusion

Following best practice is always going to lead better code and although it may take some additional time initially, once you incorporate these techniques into your daily practice, they will become second nature.

Find out how Modis can provide you with innovative AWS cloud based solutions and servicesModis has been an AWS Advanced Tier Partner since 2014. Modis' AWS Cloud Consulting services encompasses fundamentals of cyber security, fault tolerant digital system architecture, modernisation, traditional virtual machine or through to modern Serverless approaches, commercial off-the-shelf software operation to bespoke software development, delivered with high throughput, repeatable DevOps approaches to operations. With over half a decade of running critical authoritative government data sets that affects the lives of millions of citizens and the economies of the state, Modis has one of the most mature, experienced and recognised consulting service providers in the world. More importantly, we like to work very closely with our customers, not providing something to purchase, but taking a deep understanding of their business, and providing the recommendations and implementations to ensure a modern, efficient, reliable and secure environment for digital business systems.Contact us
Modis Australia | Animated map showing global locations
We operate around the world. Would you like to find out more about your local office?Find out about Modis