Software container is a lightweight executable that runs consistently on any infrastructure. It is an instance of software bundle called an image. The image is simply a bundle of operating system (OS) libraries and dependencies to run application code. Using OS virtualization, a platform provides the ability to instantiate container from the image. Docker is the most widely used platform to build images as well as run containers. In continuation to my previous posts on GraphQL, this post describes an approach to implement GraphQL API using serverless container architecture on AWS cloud. I recommend you to go through serverless GraphQL API post before reading further.
Prerequisites
- Understanding of web APIs and its role in a web architecture
- Understanding of GraphQL fundamentals
- Experience with Python programming language (Python3)
- Understanding of containerized application architecture
- Experience with AWS and AWS container services such as ECR, ECS and Fargate
- Understanding of infrastructure as code (IaC) and AWS CDK to achieve that on AWS cloud
Why build the API as a container?
As I mentioned in my previous post, AWS provides AppSync as a fully managed GraphQL API. Then the question is, why build it as a container? AppSync is well integrated and has many advantages but it lacks few basic features making it very difficult to implement and support.
1. Lack of multi-language support
AppSync only supports Apache Velocity Template Language (VTL) to write resolvers so it results into steep learning curve for a new team member. AWS has introduced few features to overcome this, such as direct lambda resolvers. However, lambda incurs additional cost and introduces one more integration layer.
2. Lack of Test-driven Development (TDD) support
Test-driven development is the most widely used approach these days but currently it is not possible to utilize TDD approach for AppSync implementation
3. Lack of debugging tools
Currently, it is not possible to debug AppSync resolvers line by line. It is possible to write log messages or investigate AWS logs but it takes longer to debug any logic implemented in AppSync using VTL
We can overcome these limitations and also leverage scalable architecture by implementing GraphQL API using serverless container architecture. This post provides the details to build the API in Python using AWS ECS and Fargate
The API implementation
Create the project directory and go to that directory
mkdir GraphQLServer-Container-AWS
cd GraphQLServer-Container-AWS
Initialize AWS infrastructure code using AWS CDK
cdk init app --language python
Activate Python virtual environment so you can install required Python packages
source .venv/bin/activate
Install Python packages required for CDK
pip install -r requirements.txt
Code language: CSS (css)
Following files and folder will be created for the infrastructure
Now, create GraphQL API project folder inside GraphQLServer-Container-AWS
mkdir GraphQLAPI
cd GraphQLAPI
Follow the steps from my previous post to create schema and resolvers. You can select schema-first or code-first approach. Rename __init__.py to main.py and replace existing code with the following
from ariadne import make_executable_schema, load_schema_from_path
from ariadne.asgi import GraphQL
from resolvers import resolvers
schema = make_executable_schema(
load_schema_from_path('./Schemas/'),
resolvers)
app = GraphQL(schema, debug=True)
Code language: JavaScript (javascript)
Install Uvicorn server
pip install uvicorn
Launch GraphQL API
uvicorn main:app
Code language: CSS (css)
Now that the API is working locally, create the Dockerfile
FROM python:3.8-alpine
# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE=1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED=1
# Install pip requirements
COPY requirements.txt .
RUN python -m pip install -r requirements.txt
WORKDIR /app
COPY . /app
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
Code language: PHP (php)
Ensure that GraphQLAPI folder has its own requirements.txt
ariadne==0.14.1
uvicorn==0.17.5
Build the docker image locally, instantiate the container locally and validate
docker build --tag graphqlsrv .
docker run --publish 80:8000 graphqlsrv
Code language: CSS (css)
After testing the image locally, go to CDK stack and write the code to create AWS resources
from aws_cdk import (
Stack,
aws_autoscaling as autoscaling,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecspatterns,
CfnOutput
)
from constructs import Construct
class GraphQlServerContainerAwsStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
#prefix for name of all resources
nameprefix = "GraphQLContainer"
#create VPC
vpc = ec2.Vpc(
self,
nameprefix + "VPC",
max_azs=2)
#create cluster
cluster = ecs.Cluster(
self,
nameprefix + "Cluster",
vpc=vpc)
#create autoscaling group
asg = autoscaling.AutoScalingGroup(
self,
nameprefix + "ASG",
instance_type=ec2.InstanceType("t2.micro"),
machine_image=ecs.EcsOptimizedImage.amazon_linux2(),
vpc=vpc)
capacityprovider = ecs.AsgCapacityProvider(
self,
nameprefix + "ASGCP",
auto_scaling_group=asg)
cluster.add_asg_capacity_provider(capacityprovider)
#create ecs fargate from local image
ecsfargateservice = ecspatterns.NetworkLoadBalancedFargateService(
self,
nameprefix + "FargateService",
cluster=cluster,
task_image_options={
'image':ecs.ContainerImage.from_asset('./GraphQLAPI')
})
ecsfargateservice.service.connections.security_groups[0].add_ingress_rule(
peer=ec2.Peer.ipv4(vpc.vpc_cidr_block),
connection=ec2.Port.tcp(80),
description="Allow HTTP inbound from VPC"
)
#output load balancer DNS
CfnOutput(self, nameprefix + "LoadBalancerDNS", value=ecsfargateservice.load_balancer.load_balancer_dns_name)
The infrastructure code here creates load balanced ECS cluster with Fargate. This helps to achieve fully managed serverless architecture for the container application. You can change the capacity and other properties to match your workload
Synthesize the infrastructure code
cdk synth
If you have not deployed with AWS CDK in your AWS account before, you will need to bootstrap first before the deployment
cdk bootstrap
cdk deploy
After successful deployment, copy the LoadBalancerDNS and launch it in a browser to validate API in the GraphQL playground
Complete source code with unit test cases for both API and infrastructure is located at my Github repo
https://github.com/vizeit/GraphQLServer-Container-AWS.git
Code language: JavaScript (javascript)