eliasbrange.dev
Deploy FastAPI on AWS Part 2: Fargate & ALB

Deploy FastAPI on AWS Part 2: Fargate & ALB

2022-01-21
| #AWS#Python#Serverless

In the previous part you learned how to deploy FastAPI on Lambda. This time we will use containers, but still in a serverless fashion. Read on to learn how to deploy a FastAPI application on AWS Fargate behind an Application Load Balancer using AWS CDK.

Introduction

FastAPI is a Python framework for creating production-ready APIs, made by Sebastián Ramírez AKA tiangolo. It's fast (both when it comes to performance and developer speed), easy to get started with, and an absolute joy to work with.

In this two-part series you will learn how to create and deploy a minimal FastAPI application on AWS in a serverless fashion.

In part 1, you learned how to deploy FastAPI on Lambda using SAM.

In part 2, you will instead learn how to package FastAPI inside a container and deploy it on AWS Fargate, the serverless container platform. Here we will use an Application Load Balancer to front the application instead of API Gateway, and this time we will define and deploy the application using AWS Cloud Development Kit (CDK).

Are you ready to test out serverless containers? Keep reading!

Requirements

Tutorial

1. Create a directory for your application

To start, create a new directory and cd into it.

$ mkdir fastapi-on-fargate
$ cd fastapi-on-fargate

2. Install AWS CDK for Python

In this example I have used Python, so to follow along the tutorial you must ensure that aws-cdk-lib is installed. This is preferably done in a virtualenv, to avoid polluting the global python installation.

i. Create a virtualenv

$ python3 -m venv .venv

ii. Activate the virtualenv

$ source .venv/bin/activate

iii. Install aws-cdk-lib

$ pip3 install aws-cdk-lib

3. Create a simple FastAPI application

The sample application we will use is similar to the one used in part 1. However, this time we will not use mangum as an adapter.

from fastapi import FastAPI
 
app = FastAPI()
 
 
@app.get("/")
def get_root():
    return {"message": "FastAPI running in a Docker container"}
 

Save the above in a file with the path src/app/__init__.py.

4. Specify runtime dependencies

To run our function we will need to install a couple of third-party dependencies. We define these in a requirements.txt file.

fastapi
uvicorn

Save the above in a file with the path src/requirements.txt.

5. Create a Dockerfile

Now it is time to dockerize our FastAPI application. In the src/ directory, create a Dockerfile with the following contents:

FROM python:3.9-alpine
WORKDIR /src
COPY ./requirements.txt /src/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /src/requirements.txt
COPY ./app /src/app
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]
 

Save the above in a file with the path src/Dockerfile.

6. Test your API locally with Docker Compose

Now that we have an application and a Dockerfile, let's try running it locally using Docker Compose. First, create a Compose file with the following contents:

services:
  app:
    build: src
    ports:
      - '3000:80'

Save the above as docker-compose.yml in the root of the directory you created in step 1.

You should now be able to start your API locally:

$ docker-compose up

The above command should build a Docker image from your Dockerfile, and start the API locally on port 3000. Let's try calling it:

$ curl localhost:3000
 
{"message":"FastAPI running in a Docker container"}
 

7. Define your application using CDK

Now that we have verified that our dockerized API works as expected, it is time to get building with AWS CDK. The current directory structure should look like the following:

fastapi-on-fargate/
  docker-compose.yml
  src/
    requirements.txt
    Dockerfile
    app/
      __init__.py

Next to the src/ directory, create a new directory named cdk/.

In the cdk/ directory, add a file cdk.json with the following contents:

{
  "app": "python3 cdk.py"
}

This tells cdk that our application is defined in cdk.py, so let's create that file:

from aws_cdk import App
from fastapi_stack import FastAPIStack
 
app = App()
FastAPIStack(app, "FastAPIStack")
app.synth()
 

In the above file, we have imported FastAPIStack from fastapi_stack, but that file does not exist (yet). Save the following in fastapi_stack.py (next to cdk.py):

from aws_cdk import Stack
from constructs import Construct
 
import aws_cdk.aws_ec2 as ec2
import aws_cdk.aws_ecs as ecs
import aws_cdk.aws_ecs_patterns as ecs_patterns
 
 
class FastAPIStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
 
        # (i) Create VPC
        self.vpc = ec2.Vpc(self, "MyVPC", max_azs=3)
 
        # (ii) Create Fargate Cluster
        self.ecs_cluster = ecs.Cluster(
            self,
            "MyECSCluster",
            vpc=self.vpc,
        )
 
        # (iii) Define Docker image for the Service
        image = ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
            image=ecs.ContainerImage.from_asset(
                directory="../src",
            )
        )
 
        # (iv) Create Fargate Service and ALB
        self.ecs_service = ecs_patterns.ApplicationLoadBalancedFargateService(
            self,
            "FastAPIService",
            cluster=self.ecs_cluster,
            cpu=256,
            memory_limit_mib=512,
            desired_count=2,
            task_image_options=image,
        )
 

This is where the magic happens. Lets go through the resource one by one.

i. Create VPC

This statement creates a VPC for us to use. Under the hood, CDK will generate a lot of resources (Subnets, Route Tables, Security Groups, etc.)

To see the power of CDK and how much it abstracts away from us, try executing cdk synth and check out the generated CloudFormation template.

ii. Create Fargate Cluster

This statement creates a Fargate Cluster inside the VPC defined above.

iii. Define Docker image for the Service

Here we define which Docker image the Service should use. In a real world scenario, this should preferably reference an image from a registry such as DockerHub. But for the sake of this tutorial, here we build the image locally by specifying the path to the Dockerfile (which is ../src).

iv. Create Fargate Service and ALB

This statement illustrates a high-level construct in CDK. Here we specify which cluster our service should run in, how many instances of the service we want, which image to use, and how much resources to allocate. The construct also sets up an Application Load Balancer in front of the Fargate Service and sets up the connection between them. There are more configuration options available for this particular construct than shown here.

8. Deploy your API to AWS

To deploy the API to AWS you need the following:

  1. An AWS account (duh!).
  2. Credentials configured for said account.
  3. Your account needs to be bootstrapped for CDK.

With the above in place we can now use CDK to deploy our FastAPI application. Issue the following command inside the cdk/ directory:

$ cdk deploy
 
  ...
 
  ✅ FastAPIStack
 
  Outputs:
  FastAPIStack.FastAPIServiceLoadBalancerDNS12341234 = FastA-FastA-XXXXXXXXXXXX-1111111111.eu-west-1.elb.amazonaws.com
  FastAPIStack.FastAPIServiceServiceURL12341234 = http://FastA-FastA-XXXXXXXXXXXX-1111111111.eu-west-1.elb.amazonaws.com
 
  Stack ARN:
  arn:aws:cloudformation:eu-west-1:1234567890:stack/FastAPIStack/11111111-aaaa-2222-bbbb-333333333333
 

If everything works, and hopefully it does, you should see an output similar to the one above. This means that we have successfully deployed the API.

The power of CDK constructs

To see just how much CDK abstracts away, go to the CloudFormation console and check the amount of resources in your newly deployed stack.

With just the few statements in the fastapi_stack.py, over 30+ different AWS resources have been configured and created.

9. Call your newly deployed API

From the cdk deploy output, you should be able to locate the URL for the Application Load Balancer. Let's try it out in the browser, or from the command line:

$ curl http://FastA-FastA-XXXXXXXXXXXX-1111111111.eu-west-1.elb.amazonaws.com
 
{"message":"FastAPI running in a Docker container"}
 

Cleaning up

To clean up, simply issue the following CDK command:

$ cdk destroy

Conclusion

You have now learned how to run a FastAPI application on Fargate, and how to configure and deploy it using AWS CDK. You have also seen how to use high-level constructs in CDK for common use-cases, such as deploying a Fargate Service behind an Application Load Balancer.

This wraps up this short two-part series, I hope you have learned a thing or two.

Now go build something awesome!

Share the knowledge