Lambda + Terraform + Github Actions
- Create a deployment pipeline for a Lambda function with Terraform
- Use Lambda versions and aliases to define two environments for development and production
- Use the API Gateway stageVariables to target one of these 2 environments
- Use Gihub Actions to automatically update the Lambda function
- A git push on the develop branch will update the dev version of the Lambda function
- A git push on the master branch will update the prod version of the Lambda function
Install and setup the project
Get the code from this github repository :
# download the code
$ git clone \
--depth 1 \
https://github.com/jeromedecoster/lambda-terraform-github-actions.git \
/tmp/aws
# cd
$ cd /tmp/aws
To setup the project, run the following command :
# create env + terraform init
$ make setup
This command will :
- Create an AWS user and put the access keys in the .env file
- Initialize Terraform from the information defined in the infra directory
Creating the architecture
To create the architecture, run the following command :
# terraform plan + apply
$ make tf-apply
This command will :
- Create a Lambda function that runs a very simple code
- Create an API Gateway that will allow us to access the Lambda function
The API Gateway is created :
The /hello
resource has a GET
method that targets our Lambda :
We can see in the Integration Request box that, by targeting the Lambda function, we associate a specific version defined by stageVariable :
Two stages have also been published : dev
and prod
.
Here is the source code to deploy the stage dev
:
resource "aws_api_gateway_deployment" "deployment_dev" {
depends_on = [
aws_api_gateway_integration.lambda
]
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
}
resource "aws_api_gateway_stage" "dev" {
deployment_id = aws_api_gateway_deployment.deployment_dev.id
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
stage_name = "dev"
variables = {
"stage" = "dev"
}
}
The stage dev
has a stageVariable called stage
whose value is dev
:
The stage prod
has a stageVariable called stage
whose value is prod
:
Lambda function is created :
The code is simple :
Here is the source code to create the 2 aliases :
resource "aws_lambda_alias" "alias_dev" {
name = "dev"
description = "dev"
function_name = aws_lambda_function.hello.arn
function_version = "$LATEST"
}
resource "aws_lambda_alias" "alias_prod" {
name = "prod"
description = "prod"
function_name = aws_lambda_function.hello.arn
function_version = "$LATEST"
}
We find these aliases in the web interface :
We can test access to the Lambda function by a curl call :
$ make hello-dev
"Hello from Lambda"
Setup up the Github project
The project uses Github Actions to automatically deploy updates
Lambda function automatic deployment requires AWS credentials
Here is the source code using the accesses :
- name: Configure AWS credentials
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 }}
These accesses were created previously
They are located in the .env
file which is based on the .env.tmpl
template
We add these secrets in our project :
The secrets have been added :
Creating the develop branch
As a reminder, the deployment of our project is done according to this principle :
-
A git push on the
develop
branch will update thedev
version of the Lambda function -
A git push on the
master
branch will update theprod
version of the Lambda function
At the moment we only have one master
branch. So we create a develop
branch from it :
We edit the code of the Lambda function of the develop branch :
We modify the message for Hello from develop :
We commit directly to the develop
branch :
The cd.yml workflow starts :
It ends quickly :
To test our deployment we run the following command :
$ make hello-dev
This command executes a simple curl call :
$ curl $(terraform output -raw hello_dev)
It get the URL from a Terraform output :
output "hello_dev" {
value = "${aws_api_gateway_stage.dev.invoke_url}/hello"
}
Our code modification, on the develop
branch, returns the correct message :
# successul return !
$ make hello-dev
"Hello from develop"
Updating the master branch
We quickly edit the master branch to test the proper functioning of our continuous deployment :
The message is now Hello from master :
We commit in the master branch :
The cd.yml workflow starts :
It ends quickly. To test our deployment we run the following command :
$ make hello-prod
"Hello from master"
In the Lambda web interface, we can see that aliases now point to the published versions of a Lambda function and no longer to the $LATEST
version :
The prod
and dev
versions have an alias
label :
This publication and alias association is performed by this part of the publish.sh script :
VERSION=$(aws lambda publish-version \
--function-name $PROJECT_NAME \
--description $1 \
--region $AWS_REGION \
--query Version \
--output text)
aws lambda create-alias \
--function-name $PROJECT_NAME \
--name $1 \
--function-version $VERSION \
--region $AWS_REGION
Updating with the Github flow
Our project works :
-
Updating the code on the
master
branch deploys a Lambda accessible via the URL/prod/hello
-
Updating the code on the
develop
branch deploys a Lambda accessible via the URL/dev/hello
We now want to simulate a real update of our project using the Github flow.
We will create a Pull Request to add a new feature to our function.
We will name this feature feature-1
.
We edit our hello.js
file from our develop branch :
To simulate the first version of our feature-1
, we modify our message to Hello with feature-1-v1 :
By committing our code, we choose to create a new branch and name it feature-1
:
The github web interface offers us to create a Pull Request.
We name it feature-1
and we indicate that this branch should be merged into the develop
branch :
The pull request has been created but we do not merge it right away :
We will first update our Lambda function :
It is important to note that the previous commit on the new feature-1
branch did not trigger our continuous deployment workflow.
This is because the workflow is configured to be triggered only after a push action on the master
or the develop
branches :
name: cd
on:
push:
branches:
- master
- develop
We are changing the message to Hello with feature-1-v2 :
We commit this update in the feature-1
branch :
We are now satisfied with all these modifications, we will be able to merge the pull request :
The workflow is started :
He finishes quickly :
Our new version is quickly accessible from the stage dev
:
$ make hello-dev
"Hello with feature-1-v2"
If we are satisfied with this version, we can deploy it in production.
We just need to create a new Pull Request to merge the develop branch to the master branch.
The deployment will be automatic.