In today’s rapidly evolving technology landscape, the ability to rapidly and reliably deploy applications is a competitive advantage for any organization. AWS Lambda, a serverless computing service from Amazon Web Services (AWS), enables developers to run code without provisioning or managing servers. However, managing AWS resources and deploying applications can become complex as projects grow. This is where AWS CloudFormation and Git-based CI/CD pipelines come into play, automating and simplifying the deployment process to ensure efficiency, consistency and reliability.
Understanding AWS Lambda
AWS Lambda is a high-demand service offering from AWS that enables code to run in response to triggers such as changes in data, changes in system state, or user actions. Lambda functions can perform a variety of tasks, from updating databases to processing data streams in real time. The beauty of AWS Lambda lies in its serverless nature, which abstracts away core infrastructure management tasks, allowing developers to focus solely on writing code.
Example: A simple AWS Lambda function in Python
import json
def lambda_handler(event, context):
print("Hello, World!")
return
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
Introduction to AWS CloudFormation
AWS CloudFormation is a service that helps you model and deploy Amazon Web Services resources so that you can spend less time managing those resources and more time focusing on your applications running on AWS. You create a template that describes all the AWS resources you want (such as Amazon EC2 instances or Amazon RDS DB instances), and AWS CloudFormation takes care of provisioning and configuring those resources for you.
Project setup with AWS Lambda and CloudFormation
To get started, you’ll need an AWS account and the AWS CLI installed and configured. The first step is to create a Lambda function, which can be done directly in the AWS Management Console or through the AWS CLI.
Step by step guide
- Create a Lambda function: Start by creating a simple Lambda function, as shown in the Python example above.
- Define Lambda with CloudFormation: Use the example CloudFormation template to define your Lambda function ua
`.yaml`
file. This file lists the configuration of your Lambda function, including its triggers, roles, and runtime.
For our example, we implement a Node.js Lambda function that simply returns “Hello world!“. We’ll also include an API Gateway in our CloudFormation template to run a Lambda function over HTTP.
CloudFormation template (lambda-api.yaml
)
AWSTemplateFormatVersion: '2010-09-09'
Resources:
HelloWorldFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
exports.handler = async (event) =>
return
statusCode: 200,
body: JSON.stringify('Hello, World!'),
;
;
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Runtime: nodejs14.x
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
- PolicyName: LambdaExecutionPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: 'logs:*'
Resource: 'arn:aws:logs:*:*:*'
HelloWorldApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: HelloWorldApi
Description: API Gateway for HelloWorld Lambda Function
FailOnWarnings: 'true'
ApiRootMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
ResourceId: !GetAtt HelloWorldApi.RootResourceId
RestApiId: !Ref HelloWorldApi
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub arn:aws:apigateway:$AWS::Region:lambda:path/2015-03-31/functions/$HelloWorldFunction.Arn/invocations
This template creates a Lambda function, an IAM role with the necessary permissions, and an API gateway setting to run the function via a GET request.
Setting up a Git repository
- Create a new repository: Use GitHub, Bitbucket, or any Git service of your choice. Initialize a new repository for your AWS Lambda project.
- Commit your project: Add your Lambda function code and CloudFormation template to the repository. Enter a code with a meaningful message.
- Branching strategy: Adopt a branching strategy that fits your team’s workflow. Common strategies include feature branching, Git Flow, or trunk-based development.
Building a CI/CD pipeline with AWS services and Git
With your AWS Lambda function and CloudFormation template under version control, the next step is to automate the deployment process using a CI/CD pipeline. AWS offers services such as AWS CodeBuild and AWS CodePipeline and integrations with third-party tools to facilitate this.
Continuous integration with AWS CodeBuild and GitHub actions
AWS CodeBuild: Automates the process of building (compiling) your code and running unit tests. It can be run on every class to your repository.
- Create a build project in AWS CodeBuild: Please provide your source repository and build specifications. Use
`buildspec.yml`
file to define build commands and settings. - Integration with GitHub: Connect your GitHub repository to run an AWS CodeBuild project on each commit or pull request.
GitHub actions: Offers CI/CD capabilities directly from your GitHub repository.
1. Set up a GitHub action workflow: To create `.github/workflows/main.yml`
file in your repository.
2. Define the workflow steps: Include steps to install dependencies, run tests, and build your code. Use AWS actions to deploy your application using CloudFormation.
Continuous Delivery with AWS CodePipeline
AWS CodePipeline automates stages of your release process for fast and reliable updates.
- Create a pipeline: Use AWS CodePipeline to orchestrate the steps from code change to deployment.
- Original phase: Connect to your Git repository as the source stage. AWS CodePipeline can be integrated with GitHub, AWS CodeCommit, or Bitbucket.
- Construction phase: Use AWS CodeBuild to compile code and run tests.
- Implementation phase: Define the deployment stage in your pipeline that uses AWS CloudFormation to deploy your Lambda function.
The role of CI/CD in modern development
Continuous integration (CI) and continuous deployment (CD) form the backbone of modern software development practices. CI/CD pipelines automate steps in the software delivery process, such as running automatic upgrades, running tests, and deploying to production environments. This automation ensures that code changes are more reliable and software can be released at a faster pace.
CI/CD pipeline components
- Continuous integration: Developers merge their changes back into the master branch as often as possible. Automated builds and tests are run for these changes to ensure they integrate well.
- Continuous delivery: Automatically deploy any code changes to a test or rendering environment after the build phase.
- Continuous implementation: Extend continuous delivery to automatically deploy changes to production without manual intervention.
Integrating AWS Lambda with CloudFormation into CI/CD pipelines brings the benefits of serverless computing to the realm of automated software deployment, improving the agility and efficiency of cloud-based projects.
Now, let’s automate the deployment of our CloudFormation pool using GitHub Actions. We’ll create a CI/CD workflow that deploys the stack whenever changes are pushed to the master branch.
GitHub Actions Workflow (deploy.yml
)
name: Deploy AWS Lambda and API Gateway via CloudFormation
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup AWS CLI
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: $ secrets.AWS_ACCESS_KEY_ID
aws-secret-access-key: $ secrets.AWS_SECRET_ACCESS_KEY
aws-region: us-east-1
- name: Deploy CloudFormation Stack
run: |
aws cloudformation deploy \
--template-file lambda-api.yaml \
--stack-name HelloWorldStack \
--capabilities CAPABILITY_IAM
Implementation with CloudFormation
aws cloudformation create-stack
--stack-name MyLambdaStack
--template-body file://aws cloudformation create-stack
--stack-name MyLambdaStack --template-body
file://lambda-api.yaml
This command creates a CloudFormation stack that includes your Lambda function, automatically handling provisioning and deployment.
Picking up where we left off, the next sections will cover Git integration for version control and building a CI/CD pipeline for AWS Lambda functions using AWS CloudFormation. While the initial part of our article laid the groundwork by introducing AWS Lambda, CloudFormation and the basics of CI/CD, here we will focus on the practical implementation of these concepts.
Detailed example: Automating Lambda deployments
Let’s consider a scenario where you have a Lambda function to process uploaded images. A function defined in a CloudFormation template requires automated deployment via a Git CI/CD pipeline.
- Lambda function and CloudFormation: The Lambda function is written in Python, and its infrastructure is defined in the CloudFormation template,
`template.yaml`
. - Setting up a GitHub repository: The code and CloudFormation template are stored in a GitHub repository. The repository includes a
`.github/workflows/main.yml`
file for GitHub actions ia`buildspec.yml`
for AWS CodeBuild. - CI/CD pipeline configuration: GitHub Actions is configured to run AWS CodeBuild each time the
`main`
branch, running tests and building code. AWS CodePipeline is set up to deploy a Lambda function using a CloudFormation template after a successful build.
By following these steps and using the configurations and examples provided, you can automate the deployment of AWS Lambda functions with CloudFormation, using Git for version control and AWS services for CI/CD. This approach increases efficiency, reduces deployment errors, and accelerates the delivery of serverless applications.
Extending Unit Testing to Node.js with AWS Lambda
Unit testing
Unit tests are the first line of defense in ensuring the integrity of your code. They involve testing individual units or components of the software to confirm that each part is working as expected.
Example
For a Node.js AWS Lambda function, you can use a testing framework such as Jest to write unit tests. These tests should cover all handler functions, ensuring they perform correctly with different inputs.
// Example unit test for a Node.js Lambda function using Jest
const handler = require('./index');
test('returns Hello, World! response', async () =>
const event = ; // Mock event object
const context = ; // Mock context object
const response = await handler(event, context);
expect(response.statusCode).toBe(200);
expect(JSON.parse(response.body)).toBe('Hello, World!');
);
Include this in your CI/CD pipeline by adding a test step to your GitHub Actions workflow before deploying:
- name: Run Unit Tests
run: npm test
Integration testing
Integration tests verify that the various modules or services used by your application work well together. For Lambda functions, this could mean testing integration with other AWS services such as S3, DynamoDB, or API Gateway.
Example
Use the AWS SDK to simulate the integration between your Lambda function and other AWS services. You can use AWS’s own aws-sdk-mock library to mock AWS SDK calls.
// Example integration test using aws-sdk-mock
const AWSMock = require('aws-sdk-mock');
const handler = require('./index');
AWSMock.mock('DynamoDB', 'putItem', (params, callback) =>
callback(null, "successfully put item in DynamoDB");
);
test('lambda function integrates successfully with DynamoDB', async () =>
const event = /* event data */ ;
const context = ;
const response = await handler(event, context);
// Assertions to validate integration behavior
);
End-to-end (E2E) testing
E2E testing involves testing the application workflow from start to finish. It aims to replicate real user scenarios to ensure that the system works as intended.
Example
After setting up your Lambda function with an API Gateway endpoint, use a tool like Postman or a simple curl command to simulate an actual API request:
curl -X GET https://your_api_gateway_endpoint_url -d '"key":"value"'
Automate those tests with tools like Cypress or Selenium and integrate them into your CI/CD pipeline for post-deployment execution. For GitHub Actions, this could be a separate job that runs only after the deployment job has completed successfully.
Load testing
Before deploying your Lambda function to production, it is critical to perform load testing to ensure that it can handle the expected traffic volume. Tools like Artillery or JMeter can simulate heavy traffic to an API Gateway endpoint.
Example
Configure Artillery to hit your API Gateway endpoint with a predefined number of requests per second and watch your Lambda function perform under stress.
config:
target: 'https://your_api_gateway_endpoint_url'
phases:
- duration: 60
arrivalRate: 10
scenarios:
- flow:
- get:
url: "https://dzone.com/"
Security testing
Security testing is essential to identify vulnerabilities within your application. Tools like OWASP ZAP or Snyk can be integrated into your CI/CD pipeline to automatically scan for security issues.
Example
Integrate Snyk into your GitHub Actions workflow to scan your Lambda function dependencies for vulnerabilities as part of your CI process.
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: $ secrets.SNYK_TOKEN
Monitoring and feedback
Post-implementation monitoring tools and feedback mechanisms should be in place to monitor application performance and detect problems early. AWS CloudWatch, Sentry, and Datadog are great tools for monitoring AWS Lambda functions.
Integrating end-to-end testing into your CI/CD pipeline ensures that your AWS Lambda functions are deployed with confidence. By covering different types of testing, from unit to E2E and load testing, you can catch and mitigate potential problems early in the development cycle, maintain high code quality, and deliver a reliable, efficient serverless application.
Conclusion
Automating AWS Lambda deployments using CloudFormation and Git-based CI/CD pipelines is a robust methodology for managing serverless applications. By integrating version control with automated build and deployment processes, teams can achieve greater efficiency, better code quality, and more reliable releases. As serverless architectures continue to evolve, mastering these practices will be critical for developers and organizations looking to leverage the full power of AWS Lambda and the broader AWS ecosystem.
Happy coding and cheers!!