The two most common strategies for deploying an application with minimum downtime are Blue/Green(A/B) deployment and Canary deployment. In this article we look at how we can implement these strategies while deploying a lambda function.
Before proceeding to deployment, let’s familiarize ourselves with a few features of Lambda functions.
Publish vs Deploy in the Lambda world
Although we use these terms interchangeably in application deployment, Lambda deals with them separately. To ‘Deploy’ a Lambda function involves the process of zipping the Function code and its dependencies and publishing them using the console or cli or any other IAC tools. This particular deployed function does not have any version associated with it and directly points to the $LATEST version. The ARN of this function also does not specify any version information as shown below in figure 1-1
Figure 1-1: Lambda Function Attributes
To ‘Publish’ a Lambda function involves the process of associating the function with a particular version(we will discuss what versions are in the next section). In the scenarios where we want to keep aside a stable version and keep incrementing the changes to newer versions, this feature comes in handy.
Versions – The function’s instance
The version is an instance of your Lambda function and its dependencies. As per the AWS documentation, each version of the lambda function contains the following components:
- The function code and all associated dependencies.
- The Lambda runtime identifier and runtime version used by the function.
- All the function settings, including the environment variables.
- A unique Amazon Resource Name (ARN) to identify the specific version of the function.
Versions are immutable and if you need to update a version’s code, you need to publish a new version by editing the Lambda function’s unpublished code and deploying the same.
Kindly note that the $LATEST always points to the latest deployment and this is the default behaviour. If a Lambda version is not specified in the function ARN, then it points to the $latest version.
Aliases – The function proxy
Aliases are pointers to the lambda function versions and they are mutable in nature. Like function versions, Aliases also have a unique ARN and a function url can be mapped to it as well. If the alias’ ARN is used for event-source mapping, then even when the alias is updated with a latest version, the mapping would remain the same.
Aliases also allow routing traffic between 2 lambda versions in a weighted manner. We would be utilising this property for our canary and Blue / Green deployments. However as per AWS documentation, “Lambda uses a simple probabilistic model to distribute the traffic between the two function versions. At low traffic levels, you might see a high variance between the configured and actual percentage of traffic on each version.”. Hence we should accommodate this variance while distributing the traffic between two Lambda versions.
Function Urls
A function url is an endpoint for your Lambda function. Prior to function urls, Lambda functions had to be exposed via API Gateway endpoints. You could attach a function url either to the non-versioned($LATEST) version of the Lambda function or to the Alias of the function.
Kindly note: You cannot attach a function url to a Lambda version.
Let’s get our hands dirty
Now , lets see the high level steps involved Canary and Blue / Green deployments and implement the same
High level steps:
- Create the lambda function using Terraform
- Publish a new version of the function
- Create an alias for the function along with routing configuration for Canary deployment
- Deploy a lambda url and test the deployment and the traffic flow
- Once the output is steady, route 100% traffic to the new version as in Blue-Green deployment and test the same
I have uploaded the code for the above steps in my Github repo(repo link: https://github.com/jithinjudepaule/lambda-blue-green/tree/main). Feel free to fork it and move along with me. Lets dive in step by step.
Step1: Create the Lambda function
I will be using Terraform to create the Lambda function. You could use the console or cli or any IAC tool as well for this. For illustrative purposes, I am creating a basic lambda function.
console.log('Loading function');
export const handler = async (event, context) => {
return "Hello from Lambda! This is version 1!";
};
I am setting the property publish=true so it will create a version for my Lambda function as well.
resource "aws_lambda_function" "blue_green_lambda" {
filename = "lambda_function_payload.zip"
function_name = "blue_green_deployment_function"
role = aws_iam_role.iam_for_lambda.arn
handler = "lambda.handler"
publish = true
source_code_hash = data.archive_file.lambda.output_base64sha256
runtime = "nodejs20.x"
}
Once you apply the above code along with the associated IAM role, a new Lambda function by name blue_green_deployment_function gets created(as shown in figure 1-2) which can be invoked using version number arn or the $LATEST arn.
Figure 1-2 Lambda function is created along with a version
Step 2: Publish a new version of the function
In order to publish a new version there has to be an updation in the code which needs to be deployed or else you will get an error while trying to create a new version as shown below in Figure 1-3:
Figure 1-3: Error while trying to create a duplicate version of the function
So lets make some changes in the code and run a terraform apply for the same. I have changed the return message from my lambda function indicating the version number(this will be handy when we test the Canary deployment as well) as below:
return “Hello from Lambda! This is version 2!”;
The below attributes of the lambda function will be altered:
Figure 1-4: Lambda attributes that are altered while publishing a new version
Post applying we see that now there are 2 versions of the same function as shown in Figure 1-5
Figure 1-5: A new version is added for Lambda
On testing version 2 we get the below output:
Figure 1-6: Invocation result for Version 2
Step 3: Create an Alias for the function with traffic splitting
When we create an Alias “prod” for the function we need to define the version it points to as well. As in real production environment, we have 2 versions here. We can create the Alias with 80% traffic pointing to Version 1(the existing version) and 20% to the new version, i.e Version 2.
resource "aws_lambda_alias" "blue_green_lambda_prod_alias" {
name = "prod"
description = "the prod alias"
function_name = aws_lambda_function.blue_green_lambda.arn
function_version = "1"
routing_config {
additional_version_weights = {
"2" = 0.2
}
}
depends_on = [
aws_lambda_function.blue_green_lambda
]
}
Post applying the above configuration we can see in the Lambda function console that we now have the prod Alias with the associated Traffic flow.
Figure 1-7 The prod Alias with traffic splitting
Step 4: Deploy a function url for the alias and test the deployment and the traffic flow
One point to note here is that we can create function urls for both Lambda Function and its Alias as well. But the Lambda function url will always point to the $LATEST version and we cannot achieve Canary deployment using the function’s url. Hence here we will create the function url for the Alias and test the same.
Also in this example I have not used any Authorization and have set the Authorization parameter to none. However in a real production environment you should have an IAM based Authentication setup for the url to make it secure.
resource "aws_lambda_function_url" "blue_green_lambda_url" {
function_name = aws_lambda_function.blue_green_lambda.function_name
qualifier = "prod"
authorization_type = "NONE"
cors {
allow_credentials = true
allow_origins = ["*"]
allow_methods = ["*"]
allow_headers = ["date", "keep-alive"]
expose_headers = ["keep-alive", "date"]
max_age = 86400
}
depends_on = [
aws_lambda_function.blue_green_lambda, aws_lambda_alias.blue_green_lambda_prod_alias
]
}
Once the above configuration is applied a function url will get added to the Alias as shown below:
Lets test this flow now and it should return 80% time version 1 and 20% time version 2.
I hit the url around 20 times and got version 2 around 4 times which means the traffic splitting is happening correctly. The two outputs are shown below
Figure 1-8 Outputs from the Alias function url
Step 5: Route 100% traffic to the Blue environment
Now that both Blue and Green versions of the Lambda function are working as expected, let’s route 100% traffic to version 2.
resource "aws_lambda_alias" "blue_green_lambda_prod_alias" {
name = "prod"
description = "the prod alias"
function_name = aws_lambda_function.blue_green_lambda.arn
function_version = "2"
routing_config {
additional_version_weights = {
"1" = 0.0
}
}
depends_on = [
aws_lambda_function.blue_green_lambda
]
}
We can see that applying the above config changes the traffic weights for the Alias as well as shown below and on testing it we get version 2’s output always:
Figure 1-9: Traffic completely shifted to Blue environment
Conclusion: Thus we saw how we can perform Canary and Blue-Green developments.