Serverless Applications with AWS and Travis: Make Deployment Great Again
Pierre Marcenac8 min read
The purpose of this tutorial is to automatically deploy a serverless API with two deployment environments (development and production) from scratch. Using the Amazon Web Services (AWS), this will be a matter of minutes! We will use Node.js and several tools which all come with a freemium model:
- AWS Lambdas are functions in the cloud that can be triggered without bothering with the infrastructure. You upload your code to AWS and it can be run anytime with high availability on any of their servers. Need more resources? No problem, Amazon scales for you. Idle time? Don’t pay for it anymore, you only pay when your API is called.
- AWS API Gateway helps you manage and version APIs and makes it easy to connect incoming requests with Lambda functions.
- Travis CI enables you to automatically test and deploy your code from Github.
You can as well use any other continuous integration (CI) tool, such as CircleCI.
A repo with all needed scripts is available on my Github, but I will walk you through the three main steps to setup your API:
- have a working application online running the code of your choice,
- make continuous deployment happen: let any change of your code on Github automatically deploy to the API,
- deal with several environments: one for production, one for development.
Setup your Lambda and your API
First set up an account on AWS. It needs up to 24 hours to be authorized. You may have to enter your credit card details, but no worries: AWS Lambda’s free tier includes among other things 1 million free requests per month!
Your first Lambda
To begin with, we’re going to set up our very first Lambda function. On the upper panel of the AWS management console, select Lambda > Create a Lambda function > Blank function.
In the true tradition of computer programs, our application will output a fancy ‘Hello world’. When you’re asked to type in the function code, use a hello-world template. It follows the callback logic that Lambda functions use:
exports.handler = (event, context, callback) => {
callback(null, {
Hello: 'World'
});
};
Your first API
For the next operations, you’ll need to go to the Amazon API Gateway and create a Hello World
API from scratch (Create API > New API). Create a /hello-world
resource (Actions > Create resource) and a GET
method linked to our freshly created Lambda function (Actions > Create method > Integration type: Lambda function).
You’ll notice that the Amazon console is pretty intuitive. If you get stuck however, I strongly recommend you this excellent walkthrough on building an API to expose a lambda function. AWS resources are particularly well-made and fully comprehensive, so don’t hesitate to go through the documentation.
See your app in production!
Let’s deploy our work in production! On the API Gateway, go to Actions > Deploy API, create a production
stage and… simply deploy by clicking the button.
On the stage editor screen, Amazon provides you with the invoke URL. Call your newly created API by typing said URL with the route /hello-world
in your browser or use CURL to make a GET request.
Make deployment automatic using Travis CI
Now that the configuration is over, let’s start with the code already! If you browse the project, you’ll notice two main folders:
./lambda
contains theindex.js
with your function code as well as the Node dependencies in thepackage.json
../scripts
contains the deployment script that Travis will use to update your function’s code at each new commit.
Create these two folders : mkdir lambda && mkdir scripts
I recommend you work on your own repository, so you learn to set it up by yourself. Begin by setting up a Git repository (git init
) and a Node project with cd lambda && npm init -y
.
Fill the code you want AWS to execute in ./lambda/index.js
.
Deployment script
In ./scripts/aws-lambda-deploy
, you will write the code for automatically updating a Lambda function.
- It loads all necessary packages including the node package for the AWS SDK and configure your region.
const AWS = require('aws-sdk');
const Promise = require('bluebird');
const lambda = new AWS.Lambda({
region: 'us-west-2'
});
- It zips the folder containing the Lambda function.
const cwd = process.cwd();
const zipLambdaCommand = `
cd ${cwd}/lambda/${lambdaName}/ &&
npm install --production &&
zip -r ${lambdaName}.zip * --quiet`;
- It updates the Lambda function’s code.
const lambdaUpdateFunctionCodeParams = {
FunctionName: `${lambdaName}`,
Publish: true,
ZipFile: read(`${cwd}/lambda/${lambdaName}/${lambdaName}.zip`)
};
lambdaUpdateFunctionCode(lambdaUpdateFunctionCodeParams);
I prefer using promises instead of callbacks, so I promisify them using Bluebird. Then, all what the script does is chain both promises to zip und update the Lambda’s code.
I added a few console.log
and also caught exceptions.
Travis will then execute the script as stated in the travis.yml
:
deploy:
- provider: script
script: node scripts/aws-lambda-deploy.js hello-world production
skip_cleanup: true
on:
branch: master
Setup Travis CI
Once you have all your code on a repository on Github, it’s easy to add it from Travis by going to your Travis profile and flicking the switch on corresponding to your repository. If you’re a complete beginner with Travis, you may want to have a look to an introduction.
Travis needs to be granted access to AWS in order to execute the deployment script. The two credentials that it needs to communicate with AWS (namely AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
) are to be found on the AWS console in My security credentials > Access keys. They are then stored in Travis as environment variables which you can set up and encrypt by clicking on Travis in More options > Settings.
Now, each time you will commit your code, Travis will launch the tests and automate the deployment to AWS. Make a small change in your code, commit it and see by yourself what happens when browsing to the invoke URL of part 1.
Manage deployment environments
Making code deploy to AWS is really cool, but clearly not sufficient if you want to develop and test new ideas for your API. We’d definitely need two environments: one for production and one for development, so your application is always available when you develop new features.
Configure two levels of code on the Lambda
On the Lambda console, at the lambda level, it is possible to configure aliases which will make the Lambda point to either production’s or development’s level of code.
Do this by going to your lambda > Actions > Create alias, and create both production and development aliases, which should both point to a certain version of your code (e.g., version 1
).
Configure two stages on the API
On the API Gateway console, in addition to the production stage, add a new stage to your API Gateway by clicking on your API > Stages > Create > Stage name: development
.
We will use a stage variable to distinguish between production and development, and let the API trigger the Lambda with the corresponding alias. For both stages (production and development), set the NODE_ENV
stage variables by navigating to the stage name > Stage variables > Add stage variable.
Type in NODE_ENV
as name, and set a value of PRODUCTION
for the production stage and DEVELOPMENT
for the development stage.
The GET
method shall now be edited in order to point to the requested stage. On the right panel, go to Resources > GET > Integration request > Lambda function and edit it. But this time, make sure you enter the name of your Lambda function concatenated with the alias: hello-world:${stageVariables.NODE_ENV}
.
Note: At this point, you will have to launch a command in order to extend your function’s permissions.
Amazon will kindly notify you by a popup, saying the API Gateway should be allowed to invoke the Lambda function. Configure your CLI and launch the said command for the functions hello-world:development
and hello-world:production
.
But how will the API Gateway pass this NODE_ENV
variable? For that matter, you have to set the body mapping template by navigating to Add mapping template > application/json and enter the following code:
#set($allParams = $input.params())
{
"stageVariables": {
#foreach($key in $stageVariables.keySet())
"$key" : "$util.escapeJavaScript($stageVariables.get($key))"
#if($foreach.hasNext),#end
#end
}
}
Deploy on each environment
In addition to updating the Lambda’s code, the deployment script shall also update the alias corresponding to the git branch that is being changed.
const lambdaUpdateAliasParams = {
FunctionName: `${lambdaName}`,
Name: lambdaAlias,
FunctionVersion: lambdaVersion
};
lambdaUpdateAlias(lambdaUpdateAliasParams);
On Git, each deployment environment corresponds to a certain branch. The branch master
will deploy to production
, and the branch develop
to development
. This appears in the travis.yml
where we now automate the deployment for both environments:
- provider: script
script: node scripts/aws-lambda-deploy.js hello-world development
skip_cleanup: true
on:
branch: develop
Create the new branch for the development environment (git co -b develop
), make a small change in the function’s code, and push it to Github.
Wait for Travis to do its magic. You can check the deployment did not fail on Travis console. Once the build passed, if you go to the invoke URLs of each stage, you’ll see the changes corresponding to each environment!
Conclusion
Amazon provides you with a panel of services that integrate well with Lambda functions and the API Gateway. It goes from machine learning to logging tools or queuing services, and will help you in designing the best APIs. Possible future features for the API we just conceived are:
- have different environment variables for each stage,
- encrypt the environment variables on AWS,
- schedule or define triggers to run your API at your convenience,
- manage and organize logs.
In addition to that, many frameworks can now help you manage and deploy your Lambda web services, such as Serverless, Apex, or Zappa if you prefer using Python.
In serverless computing, you concentrate on coding simple functions instead of handling complex HTTP requests or focusing on architecture’s issues. Automatic deployment, as we saw it, makes your application available for production and development in a few clicks. Making your own backend API and interacting with other APIs is a matter of minutes!