Serverless AWS Lambda Costs

AWS Lambda Serverless has the capability to scale almost without limits. Ironically, your main concern is not whether your app can scale up, but rather how to limit its scaling. It shifts your thinking about scalability.

Since the AWS Lambda model charges based on requests and can scale nearly limitlessly, you can end up with a huge bill. Even if you’re careful, you can be unlucky and get hit by a DDOS attack. Thankfully, there are several strategies available to help manage and mitigate these risks.

Reserved Concurrency

A useful Lambda Function setting is Reserved Concurrency.

Summary:

  • Reserved concurrency: Guaranteed and maximum number of function “instances” to run from the AWS Account default concurrency pool, usually 1,000. There is no extra cost for this. See: Reserved Concurrency Concept
  • Provisioned concurrency: Pre-initialized always running function “instances”. There are extra costs to use this.

Interestingly, Reserved concurrency can be used to limit requests and control costs since AWS Lambda bills for both request and duration. AWS Lambda bills for the processed requests regardless of whether or not it returns its TooManyRequestsException, but the duration is shorter. This is similar to traditional EC2 instances. Even if an EC2 instance returns an error, you still get charged for the request. Generally, users still stop requesting after getting error responses.

Hence, a way to mitigate costs and limit AWS Lambda scaling is by configuring Reserved Concurrency.

config/jets/deploy.rb

Jets.deploy.configure do
  config.lambda.controller.reserved_concurrency = 2
end

The Jets default is lambda.controller.reserved_concurrency = 25, which provides a safeguard.

If you encounter a DDOS attack, Lambda will return 429 TooManyRequestsException errors. Similarly, traditional EC2 servers will return 500 errors. In both cases, you will still incur costs for requests and bandwidth.

Short of removing your site from the face of the planet by removing its DNS record, there you still get ding with some costs. However, the costs are mitigated since error responses are light and fast. It costs you a lot less bandwidth and duration to serve the requests. Remember, it costs DDOS attackers money also.

More Docs: Config Concurrency

WAF Protection

Another way mitigate AWS Lambda Costs from DDOS attacks is to use an AWS WAF, a Web Application Firewall.

Here’s an example of how to configure an existing WAF.

config/jets/deploy.rb

Jets.deploy.configure do
  config.lambda.url.cloudfront.enable = true
  config.lambda.url.cloudfront.cert.arn = acm_cert_arn(domain: "example.com", region: "us-east-1")

  config.lambda.url.cloudfront.web_acl_id = web_acl_arn("prod")
end

Note that web_acl_id expects the arn, so the code above is correct. The setting is called web_acl_id because it matches with the CloudFormation property. Here’s a useful cheatsheet command to list your WAFs that CloudFront can use.

aws wafv2 list-web-acls --scope=CLOUDFRONT --region=us-east-1 | jq

Assigning an existing WAF ALC allows you to reuse and associate its Rules with multiple CloudFront Distributions. It can help keep your WAF rules DRY. IE: Change the rules once, and they protect all the CloudFront traffic across all apps.

WAF Web ACL must be a global WAF. Regional WAFs do not work with CloudFront. The web_acl_arn helper can take in an options hash at the end. It defaults {scope: "CLOUDFRONT"}.

Also note that CloudFront provides built-in baseline protection called AWS Sheild Standard. You get it just by using it.

AWS Shield Standard provides protection for all AWS customers against common and most frequently occurring infrastructure (layer 3 and 4) attacks like SYN/UDP floods, reflection attacks, and others to support high availability of your applications on AWS.

A WAF provides additional protection measures.

Standalone Jets WAF

Jets provides a waf command to create a standalone WAF ACL that is decoupled from the Jets app.

jets waf:deploy

You may want to consider creating a “shared” or “infra” project with the standalone WAF config since the WAF can to be shared by multiple projects.

Some advantages for this:

  • It allows you to use the standalone WAF as a shared resource to among all your CloudFront distributions. So if you add a WAF rule, it applies everywhere.
  • Each WAF costs money. The WAF ACL has a $5 baseline cost and $1 per rule per ACL. A CloudFront WAF with 3 rules costs $8. Sharing a WAF can save you money.

That being said, you can configure different WAFs for each Jets app if needed.

For more docs, see: Lambda URL CloudFront WAF