AWS Fargate is a serverless compute engine that allows you to run containers without managing servers. With Fargate, you no longer have to provision clusters of virtual machines to run ECS containers: this is all done for you.
Fargate has an Amazon ECS construct that can host an API. In this take, we will build a Fargate service using the AWS CDK, put the API in a docker image, and then host it inside Amazon ECS.
The API will be a pizza API and we'll store the data in a DynamoDB table.
Let’s get started!
Why Use Fargate with ECS?
Fargate has some benefits over lambda functions because of provisioned servers. When you have a high volume of traffic, you can save on costs by running containers. These can scale out to as many servers as needed to meet the current demand and avoid paying per request. Once you have millions of requests per day or even per hour, paying per actual load starts to become more cost-effective.
The one gotcha here is that scaling out can become a bit of a hassle because, by the time you spin up a new container, it is already too late to handle the current load. A good technique is to come up with scaling strategies ahead of time, which means you definitely must have a predictable load.
Requirements
Feel free to clone the sample code for this project from GitHub.
Be sure to have the latest version of Node and the AWS CDK Toolkit installed.
Then, simply spin up a new TypeScript project using the toolkit.
Build the ECR Image
First, install Fastify and DynamoDB in the root package.json
file. Then, create an app
folder to contain the application.
Create the app/api.ts
file and add this code snippet:
This is a pizza API with GET/PUT endpoints. We store the data in a DynamoDB table.
Note: The host
and port
come from an environment variable when this runs in Fargate. This guarantees the app runs on 0.0.0.0
and not the loopback IP address. The port number must also be 80
for HTTP traffic.
To upload the app to an ECR image, create a repository in the AWS console.
- Log in to AWS and click 'Elastic Container Registry'
- Click 'Create repository'
- Leave it as Private
- Set a name, for example,
pizza-fargate-api
, and make note of this
Next, create the Dockerfile in the root folder.
Note: For this docker build to work, be sure to move typescript
and @types/node
in the package.json
file from dev to just plain dependencies.
For the load balancer to get access to the container, be sure to specify the PORT
and ADDRESS
. As mentioned, this cannot be the loopback 127.0.0.0
because it does not allow incoming connections from outside the container. In Fastify, this is the default behavior, so we must manually set this IP address.
In ECR, click on the newly created repository, then 'View push commands'. This lists instructions for uploading the docker image to the repository (they are self-explanatory, so we will not repeat them here).
Once the commands complete successfully, the image should appear in the Amazon Elastic Container Registry.
The AWS CDK
Open the node-fargate-api-stack.ts
file and drop in this entire code snippet.
The ApplicationLoadBalancedFargateService
construct needs a VPC (which should already exist in your AWS account). The load balancer directs HTTP traffic from the internet to the cluster. This is the reason why it is publicly available.
The DynamoDB table simply declares a main partition key: the number id and table name. The task definition is then granted full access to the DynamoDB table.
The cpu
, desiredCount
, and memoryLimitMiB
set scalability configuration for the service when it first starts. The service uses task definitions to spin up new containers that can handle the load. You can manually scale Fargate by updating the desired task count in your service definition. This CDK also creates auto-scaling policies based on metrics like CPU utilization. Fargate uses CloudWatch alarms to trigger scaling actions based on metrics or events.
For this CDK to work, be sure to update the main entry point in bin/node-fargate-api.ts
. Set the account number and region.
Unit Test the CDK
Type cdk synth
in a terminal, then inspect the CloudFormation YAML template that will get uploaded to AWS. This has enough information to write unit tests.
Open node-fargate-api.test.ts
in the test
folder and enter this code snippet.
This time, it is important to specify the env
for each test because the VPC lookup in the stack requires the environment. Luckily, you can mock the account id and region because it is just a unit test. What is important is that the unit test verifies the YAML template since we are creating these specific resources via the CDK.
The Load Balancer
Before we can check that the load balancer works, simply deploy the CDK.
Note: If the deploy takes a very long time, it is likely that the load balancer cannot clear the health endpoint check. Simply cancel the deploy, then go back and double-check your CDK.
The CDK should have an output with a publicly accessible URL from the load balancer. You can now hit the two endpoints with CURL.
To smack this API with as many requests as possible, write this K6 script and save it as a JavaScript file.
Then, run a load test using K6:
This test simulates 50 users hitting the API at the same time for 20 minutes. The reason you want to go for 20 minutes is because Fargate takes a few minutes to spin up new tasks to handle the current load. This is the reason why Fargate does better with predictable load. By the time the new task spins up, it might already be too late. (In contrast, a lambda function cold starts in mere seconds on the slower end and is therefore more suitable for spiky unpredictable traffic).
The load test might also show some failing requests. This happens because tasks are sometimes too slow to spin up and respond to current incoming traffic.
By the end of the load test, you should see the maximum amount of configured tasks running in your service.
Before we end, be sure to fire a cdk destroy
so you don't accrue further EC2 charges. The one downside to Fargate is that you continue to pay for the running service even when there is no traffic.
Wrapping Up
In this post, we built and hosted a Fargate service using AWS CDK and Amazon ECS.
We've seen that Fargate running on ECS can be a great alternative to lambda functions. Given predictable traffic and heavy load, it is possible to save on costs because you are not paying per request.
Happy coding!
P.S. If you liked this post, subscribe to our JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.
P.P.S. If you need an APM for your Node.js app, go and check out the AppSignal APM for Node.js.