Migrating from Lambda@Edge to CloudFront Functions
I have been using Lambda@Edge for several years now, first time I used it was back in 2018, wrote this post back then. I was always under the impression that the Lambda function was running in the edge cache, hence the name. But I was apparently wrong, it runs in the region cache and not in the edge cache. That became clear when AWS, back in May 2021, release the new and shiny CloudFront Functions that was going to run in true edge cache.
So what are the differences? How do we go about to migrate from Lambda@Edge to CloudFront Functions. In this post I will try to explain the differences and talk about my experience migrating my blog infrastructure to CloudFront functions.
Lambda@Edge vs CloudFront Functions
So what are the difference between the two then? First of all Lambda@Edge run at the 13 (at the time of writing this post) Regional Caches. CloudFront Functions run at the 200+ Edge Caches. That mean that the your code will run closer to your users.
The other major differences are around runtime support, memory usages, execution time, and more. Here is a table showing the differences.
Feature | CloudFront Functions | Lambda@Edge |
---|---|---|
Runtime | JavaScript (ECMAScript 5.1 compliant) | Node.js, Python |
CloudFront triggers | Viewer request Viewer response | Viewer request Viewer response Origin request Origin response |
Execution time | 1 millisecond | 5 seconds (viewer triggers) 30 seconds (origin triggers) |
Memory | 2MB | 128MB (viewer triggers) 10GB (origin triggers) |
Package size | 10 KB | 1 MB (viewer triggers) 50 MB (origin triggers) |
Network access | No | Yes |
File system access | No | Yes |
Access to the request body | No | Yes |
That was a lot of differences, but what does it mean? How do I know which to choose?
Pricing
So what about pricing? Is CloudFront Functions or Lambda@Edge more expensive? With Lambda@Edge you pay $0.60 per 1 million requests plus the execution time ($0.00000625125 FOR EVERY 128MB-SECOND). With CloudFront Functions you pay $0.10 per 1 million Invocations and nothing for execution time. That mean that CloudFront Functions is the cheapest option of the two.
Choosing Lambda@Edge or CloudFront Functions
As we just saw there are several differences between Lambda@Edge and CloudFront functions. Which should I use? I would say that if you don't need to react on origin triggers, have access to the request body, need file or network access, can finish execution within 1 ms and have really small deployment packages, then CloudFront Functions is probably a great match for you. Just remember that no network access mean no access to any other AWS service.
Migrating
CloudFront Functions are deployed with the CloudFront distribution in the region of your choice. This truly made deployment much easier, Lambda@Edge always had to be deployed in us-east-1 region, with CloudFront Functions there was no need to target different regions during deployment.
I have been using Lambda@Edge to make CloudFront act more like a webserver, so if a link was pointing to a folder I use Lambda@edge to append index.html to the path, so the correct file is requested from S3. Since I didn't use any network access, my Lambda@edge function was rather fast, and written in pure Javascript the migration process was rather easy.
Create the Function
First of all I created a AWS::CloudFront::Function and deployed that.
CloudFrontFunction:
Type: AWS::CloudFront::Function
Properties:
Name: UpdatePath
AutoPublish: true
FunctionCode: !Sub |
function handler(event) {
var request = event.request;
var uri = request.uri;
uri = uri.replace(/\/$/, '\/index.html');
request.uri = uri;
return request;
}
FunctionConfig:
Comment: !Sub Append index.html to folder paths
Runtime: cloudfront-js-1.0
The Runtime of the function must always be cloudfront-js-1.0. I could not find a good way to keep the code in an external source file and not inline it in the CloudFormation template. This is a rather clunky way to add the code for the function. I would love to see a deployment similar to how Lambda is deployed. Since my code was rather short it is manageable to have it inlined.
Testing and debugging
When the function was deployed I could turn to the console to test it out, which was a rather nice experience. You can select the trigger and what stage, Development or Live, you like to use for the test.
The result is presented in a really nice way.
It is also possible to perform tests using the CLI in an automatic way. This is done using the test-function command.
aws cloudfront test-function \
--name ExampleFunction \
--if-match ETVABCEXAMPLE \
--event-object fileb://event-object.json
--stage DEVELOPMENT
The event-object is Json with the information about stage etc.
{
"version": "1.0",
"context": {
"eventType": "viewer-request"
},
"viewer": {
"ip": "1.2.3.4"
},
"request": {
"method": "GET",
"uri": "/mytest/",
"headers": {
"host": {"value": "example.org"}
}
}
}
The deployment and testing is really nice, you can easily deploy a new development version and test it out before going live. Logs, printed using console.log() is always sent to CloudWatch logs in us-east-1, doesn't matter what edge location the Function runs in, logs are in us-east-1
So after I had tested the Function it was time to update the CloudFormation template.
Doing the actual migration
The last step is to do the actual update of CloudFormation and do the migration. I commented out the LambdaFunctionAssociations section and added the FunctionAssociations instead.
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
....
FunctionAssociations:
- EventType: viewer-request
FunctionARN: !GetAtt CloudFrontFunction.FunctionMetadata.FunctionARN
#LambdaFunctionAssociations:
# - EventType: viewer-request
# LambdaFunctionARN: !Ref LambdaARN
The deployment took a couple of minutes but was not that bad. After the deployment was done the page was working perfect and I could delete the Lambda@Edge function.
Conclusion
Migrating to CloudFront Functions from Lambda@Edge was really easy, and was accomplished with just a few steps. So what did the migration bring? I feel that it was easier to debug and test CloudFront Functions. Testing was nice and straight forward. That logs are always in us-east-1 and not as with Lambda@Edge where they end up in the region the Lambda@Edge function runs in, is a killer feature. My cost was down slightly, but not much, I'm not using that much. I can see cost to be a driver for migration for heavy usage. For some reason the page felt more responsive, even though it's the same code that is running. Can be due to CloudFront Functions running closer to the client, can be my imagination, I have not done any proper measurements yet.
In short, I would say that if you can you should migrate.