Serverless public facing API hosted on AWS Fargate
A serverless, containerized public facing API in a private network, managed by ECS, hosted on AWS Fargate
Nathan Peck
Senior Developer Advocate at AWS
About
Sometimes you want to create a public facing service, but you want stricter control over the networking of the service. This pattern is especially useful for the following usecases:
An API which is public facing but needs an extra layer of security hardening by not even having a public IP address that an attacker could send a request directly to.
An API service which needs to be massively horizontally scalable while not being constrained by number of IP addresses available.
A web or API service which initiates outbound connections but to the public you want those connections to originate from a specific and limited set of IP addresses that can be whitelisted.
Everything is deployed in an Amazon Virtual Private Cloud (VPC) which has two subnets:
Public subnet: Has an attached internet gateway to allow resources launched in that subnet to accept connections from the internet, and initiate connections to the internet. Resources in this subnet have public IP addresses.
Private subnet: For internal resources. AWS Fargate tasks in this subnet have no direct internet access, and only have private IP addresses that are internal to the VPC, not directly accessible by the public.
The public facing subnet hosts a couple resources:
Public facing load balancer: Accepts inbound connections on specific ports, and forwards acceptable traffic to resources inside the private subnet.
NAT gateway: A networking bridge to allow resources inside the private subnet to initiate outbound communications to the internet, while not allowing inbound connections.
💡 Tip: Application Load Balancer has a constant hourly charge, which gives it a baseline cost, even if your application receives no traffic.
If you expect your API to receive very low traffic, or intermittent traffic, then you may prefer to use the API Gateway pattern for AWS Fargate. Amazon API Gateway charges per request, with no minimum fee. However, this can add up to a higher per request cost if you do receive large amounts of traffic.
Dependencies
This pattern requires that you have an AWS account, and that you use AWS Serverless Application Model (SAM) CLI. If not already installed then please install SAM CLI for your system.
Define the VPC
To deploy this pattern you will use the base pattern that defines a large VPC for an Amazon ECS cluster. This will deploy the public and private subnets as well as the NAT gateway that provides internet access to the private subnets. Download the vpc.yml file from that pattern, but don’t deploy it yet. You will deploy the VPC later as part of this pattern.
Define the cluster
The following AWS CloudFormation template creates a simple Amazon ECS cluster that is setup for serverless usage with AWS Fargate.
AWSTemplateFormatVersion:'2010-09-09'Description:Empty ECS cluster that has no EC2 instances. It is designedto be used with AWS Fargate serverless capacityResources:# Cluster that keeps track of container deploymentsECSCluster:Type:AWS::ECS::ClusterProperties:ClusterSettings:- Name:containerInsightsValue:enabled# This is a role which is used within Fargate to allow the Fargate agent# to download images, and upload logs.ECSTaskExecutionRole:Type:AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Statement:- Effect:AllowPrincipal:Service:[ecs-tasks.amazonaws.com]Action:['sts:AssumeRole']Condition:ArnLike:aws:SourceArn:!Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:*StringEquals:aws:SourceAccount:!Ref AWS::AccountIdPath:/# This role enables basic features of ECS. See reference:# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECSTaskExecutionRolePolicyManagedPolicyArns:- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicyOutputs:ClusterName:Description:The ECS cluster into which to launch resourcesValue:!Ref ECSClusterECSTaskExecutionRole:Description:The role used to start up a taskValue:!Ref ECSTaskExecutionRole
Define the service
Next we need to define an Amazon ECS service that deploys a container using AWS Fargate. The following template deploys an Application Load Balancer into the VPC public subnet, while deploying the containers themselves into AWS Fargate in the private subnet:
AWSTemplateFormatVersion:'2010-09-09'Description:An example service that deploys in AWS VPC networking modeonAWS Fargate. Service runs with networking in private subnetsand with private IP addresses only.Parameters:VpcId:Type:StringDescription:The VPC that the service is running inside ofPublicSubnetIds:Type:List<AWS::EC2::Subnet::Id>Description:List of public subnet ID's to put the load balancer inPrivateSubnetIds:Type:List<AWS::EC2::Subnet::Id>Description:List of private subnet ID's to put the tasks inClusterName:Type:StringDescription:The name of the ECS cluster into which to launch capacity.ECSTaskExecutionRole:Type:StringDescription:The role used to start up an ECS taskServiceName:Type:StringDefault:webDescription:A name for the serviceImageUrl:Type:StringDefault:public.ecr.aws/docker/library/nginx:latestDescription:The url of a docker image that contains the application process thatwill handle the traffic for this serviceContainerCpu:Type:NumberDefault:256Description:How much CPU to give the container. 1024 is 1 CPUContainerMemory:Type:NumberDefault:512Description:How much memory in megabytes to give the containerContainerPort:Type:NumberDefault:80Description:What port that the application expects traffic onDesiredCount:Type:NumberDefault:2Description:How many copies of the service task to runResources:# The task definition. This is a simple metadata description of what# container to run, and what resource requirements it has.TaskDefinition:Type:AWS::ECS::TaskDefinitionProperties:Family:!Ref ServiceNameCpu:!Ref ContainerCpuMemory:!Ref ContainerMemoryNetworkMode:awsvpcRequiresCompatibilities:- FARGATEExecutionRoleArn:!Ref ECSTaskExecutionRoleContainerDefinitions:- Name:!Ref ServiceNameCpu:!Ref ContainerCpuMemory:!Ref ContainerMemoryImage:!Ref ImageUrlPortMappings:- ContainerPort:!Ref ContainerPortHostPort:!Ref ContainerPortLogConfiguration:LogDriver:'awslogs'Options:mode:non-blockingmax-buffer-size:25mawslogs-group:!Ref LogGroupawslogs-region:!Ref AWS::Regionawslogs-stream-prefix:!Ref ServiceName# The service. The service is a resource which allows you to run multiple# copies of a type of task, and gather up their logs and metrics, as well# as monitor the number of running tasks and replace any that have crashedService:Type:AWS::ECS::Service# Avoid race condition between ECS service creation and associating# the target group with the LBDependsOn:PublicLoadBalancerListenerProperties:ServiceName:!Ref ServiceNameCluster:!Ref ClusterNameLaunchType:FARGATENetworkConfiguration:AwsvpcConfiguration:AssignPublicIp:DISABLEDSecurityGroups:- !Ref ServiceSecurityGroupSubnets:!Ref PrivateSubnetIdsDeploymentConfiguration:MaximumPercent:200MinimumHealthyPercent:75DesiredCount:!Ref DesiredCountTaskDefinition:!Ref TaskDefinitionLoadBalancers:- ContainerName:!Ref ServiceNameContainerPort:!Ref ContainerPortTargetGroupArn:!Ref ServiceTargetGroup# Security group that limits network access# to the taskServiceSecurityGroup:Type:AWS::EC2::SecurityGroupProperties:GroupDescription:Security group for serviceVpcId:!Ref VpcId# Keeps track of the list of tasks for the serviceServiceTargetGroup:Type:AWS::ElasticLoadBalancingV2::TargetGroupProperties:HealthCheckIntervalSeconds:6HealthCheckPath:/HealthCheckProtocol:HTTPHealthCheckTimeoutSeconds:5HealthyThresholdCount:2TargetType:ipPort:!Ref ContainerPortProtocol:HTTPUnhealthyThresholdCount:10VpcId:!Ref VpcIdTargetGroupAttributes:- Key:deregistration_delay.timeout_secondsValue:0# A public facing load balancer, this is used as ingress for# public facing internet traffic.PublicLoadBalancerSG:Type:AWS::EC2::SecurityGroupProperties:GroupDescription:Access to the public facing load balancerVpcId:!Ref VpcIdSecurityGroupIngress:# Allow access to public facing ALB from any IP address- CidrIp:0.0.0.0/0IpProtocol:-1PublicLoadBalancer:Type:AWS::ElasticLoadBalancingV2::LoadBalancerProperties:Scheme:internet-facingLoadBalancerAttributes:- Key:idle_timeout.timeout_secondsValue:'30'Subnets:!Ref PublicSubnetIdsSecurityGroups:- !Ref PublicLoadBalancerSGPublicLoadBalancerListener:Type:AWS::ElasticLoadBalancingV2::ListenerProperties:DefaultActions:- Type:'forward'ForwardConfig:TargetGroups:- TargetGroupArn:!Ref ServiceTargetGroupWeight:100LoadBalancerArn:!Ref 'PublicLoadBalancer'Port:80Protocol:HTTP# Open up the service's security group to traffic originating# from the security group of the load balancer.ServiceIngressfromLoadBalancer:Type:AWS::EC2::SecurityGroupIngressProperties:Description:Ingress from the public ALBGroupId:!Ref ServiceSecurityGroupIpProtocol:-1SourceSecurityGroupId:!Ref 'PublicLoadBalancerSG'# This log group stores the stdout logs from this service's containersLogGroup:Type:AWS::Logs::LogGroup
Deploy it all
You should now have the following three files:
vpc.yml - Template for the base VPC that you wish to host resources in
cluster.yml - Template for the ECS cluster and its capacity provider
service.yml - Template for the web service that will be deployed on the cluster
Use the following parent stack to deploy all three stacks:
AWSTemplateFormatVersion:"2010-09-09"Transform:AWS::Serverless-2016-10-31Description:Parent stack that deploys VPC, Amazon ECS cluster for AWS Fargate,and a serverless Amazon ECS service deployment that hoststhe task containers on AWS FargateResources:# The networking configuration. This creates an isolated# network specific to this particular environmentVpcStack:Type:AWS::Serverless::ApplicationProperties:Location:vpc.yml# This stack contains the Amazon ECS cluster itselfClusterStack:Type:AWS::Serverless::ApplicationProperties:Location:cluster.yml# This stack contains the container deploymentServiceStack:Type:AWS::Serverless::ApplicationProperties:Location:service.ymlParameters:VpcId:!GetAtt VpcStack.Outputs.VpcIdPublicSubnetIds:!GetAtt VpcStack.Outputs.PublicSubnetIdsPrivateSubnetIds:!GetAtt VpcStack.Outputs.PrivateSubnetIdsClusterName:!GetAtt ClusterStack.Outputs.ClusterNameECSTaskExecutionRole:!GetAtt ClusterStack.Outputs.ECSTaskExecutionRole
Use the following command to deploy all three stacks:
Open the Amazon ECS cluster in the web console and verify that the service has been created with a desired count of two. You will observe the service create tasks that launch into AWS Fargate. Last but not least ECS will register the task’s IP addresses with the Application Load Balancer so that it can send them traffic.
On the “Health & Metrics” tab of the service details page you can click on the load balancer name to navigate to the load balancer in the EC2 console. This will give you the load balancer’s public facing CNAME that you can copy and paste into your browser to verify that the sample NGINX webserver is up and running.
Tear it down
You can tear down the entire stack with the following command:
sam delete --stack-name serverless-api-environment
See Also
If you plan to run an extremely large workload that requires many vCPU’s worth of compute resources, or you wish to have more control over the precise CPU generation used to power your application, then consider using Amazon ECS to host your containerized API service on EC2.