AWS Cloud Map is a cloud resource discovery service. Cloud Map provides a way to lookup a list of your dynamically changing resources, such as containers.
Amazon API Gateway is a serverless ingress for your web traffic. It has no minimum fee or hourly charge. Instead you pay for the API calls you receive and the amount of data transferred out.
In this pattern you will deploy API Gateway as an ingress in front of an AWS Fargate hosted container image. The API Gateway will discover instances of the running container using AWS Cloud Map.
Architecture
The following diagram shows the architecture that will be deployed:
An API Gateway receives inbound traffic from the public internet.
The API Gateway uses AWS Cloud Map to look up the private IP addresses of tasks that are part of an AWS Fargate deployed service.
API Gateway uses a VPC Link to open connections to the private IP addresses inside of the VPC.
The AWS Fargate hosted tasks receive inbound traffic over a connection opened from the API Gateway VPC Link.
The API Gateway proxies the container’s response back to the client on the public internet.
💡 Tip: API Gateway pricing has no minimum fees or upfront commitments. Instead you pay per API call you receive, and for the amount of outgoing data. This makes API Gateway less expensive than Application Load Balancer for many low traffic applications.
On the other hand if your server side application receives a very large number of small API calls from a large number of connected clients, then you may find the Application Load Balancer pattern for AWS Fargate to be more cost efficient. Application Load Balancer has a constant hourly charge that gives it a higher baseline cost, but the ALB can handle a large number of requests at a lower per request added cost.
Dependencies
This pattern uses AWS SAM CLI for deploying CloudFormation stacks on your AWS account.
You should follow the appropriate steps for installing SAM CLI.
Define the network
This pattern can be deployed on top of either of the following VPC patterns:
Which one you choose depends on your goals for this deployment. You can choose the low cost VPC to start with and upgrade to the large sized VPC later on if you have additional private services, or private database servers you wish to deploy in the VPC.
Download the vpc.yml file from your chosen pattern, but do not deploy it yet. Deployment will be done later in the process.
Define the cluster
The following AWS CloudFormation template creates a simple Amazon ECS cluster for usage with AWS Fargate. It also creates an AWS Cloud Map namespace for keeping track of tasks in the cluster.
AWSTemplateFormatVersion:'2010-09-09'Description:Empty ECS cluster that has no EC2 instances. It is designedto be used with AWS Fargate serverless capacityParameters:VpcId:Type:StringDescription:The VPC that the service is running inside ofResources:# 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/AmazonECSTaskExecutionRolePolicy# This namespace will keep track of the tasks in the clusterServiceDiscoveryNamespace:Type:AWS::ServiceDiscovery::PrivateDnsNamespaceProperties:Name:internalDescription:Internal, private service discovery namespaceVpc:!Ref VpcIdOutputs:ClusterName:Description:The ECS cluster into which to launch resourcesValue:!Ref ECSClusterECSTaskExecutionRole:Description:The role used to start up a taskValue:!Ref ECSTaskExecutionRoleServiceDiscoveryNamespaceId:Description:The shared service discovery namespace for all services in the clusterValue:!Ref ServiceDiscoveryNamespace
Note the AWS::ServiceDiscovery::PrivateDnsNamespace. This is an AWS Cloud Map powered service discovery namespace that will be used to keep track of the tasks running in AWS Fargate.
Define the service
The following AWS CloudFormation template defines a basic NGINX task that runs in AWS Fargate, orchestrated by Amazon ECS. Amazon ECS also registers the running tasks into AWS Cloud Map for service discovery.
AWSTemplateFormatVersion:'2010-09-09'Description:An example service that deploys in AWS VPC networking modeonAWS Fargate. Service runs with networking in publicsubnets and public IP addressesParameters: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 and tasks inClusterName:Type:StringDescription:The name of the ECS cluster into which to launch capacity.ServiceDiscoveryNamespaceId:Type:StringDescription:The ID of a CloudMap namespace into which the service will be registeredECSTaskExecutionRole: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 ContainerPortHealthCheck:Command:- "CMD-SHELL"- "curl -f http://localhost/ || exit 1"Interval:5Retries:2Timeout:3LogConfiguration: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::ServiceProperties:ServiceName:!Ref ServiceNameCluster:!Ref ClusterNameLaunchType:FARGATENetworkConfiguration:AwsvpcConfiguration:AssignPublicIp:ENABLEDSecurityGroups:- !Ref ServiceSecurityGroupSubnets:!Ref PublicSubnetIdsDeploymentConfiguration:MaximumPercent:200MinimumHealthyPercent:75DesiredCount:!Ref DesiredCountTaskDefinition:!Ref TaskDefinitionServiceRegistries:- RegistryArn:!GetAtt ServiceDiscoveryService.ArnPort:!Ref ContainerPort# 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 serviceServiceDiscoveryService:Type:AWS::ServiceDiscovery::ServiceProperties:Name:!Ref ServiceNameDnsConfig:NamespaceId:!Ref ServiceDiscoveryNamespaceIdDnsRecords:- TTL:0Type:SRV# This log group stores the stdout logs from this service's containersLogGroup:Type:AWS::Logs::LogGroupOutputs:ServiceSecurityGroup:Description:The security group of the serviceValue:!Ref ServiceSecurityGroupServiceDiscoveryServiceArn:Description:ARN of the CloudMap serviceValue:!GetAtt ServiceDiscoveryService.Arn
Important things to note:
The AWS::ServiceDiscovery::Service must use a DNS record type of SRV. SRV records keep track of both the IP address as well as the port. This is required for API Gateway to be able to locate the task and send it traffic on the right port.
Container image must have the curl command installed in order to evaluate the Container Health Check commands.
Define the API Gateway
The following AWS CloudFormation template defines an API Gateway that can access a VPC. It uses AWS Cloud Map to locate targets to send traffic to.
AWSTemplateFormatVersion:'2010-09-09'Description:Deploy an API Gateway ingress for an AWS Fargate hosted serviceParameters: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 API gateway link inServiceSecurityGroup:Type:StringDescription:The service's security groupServiceDiscoveryServiceArn:Type:StringDescription:The ARN of the service's Cloud Map serviceResources:# The API Gateway itselfApiGateway:Type:AWS::ApiGatewayV2::ApiProperties:Name:fargate-api-gatewayProtocolType:HTTP# This allows the AWS managed API Gateway to# communicate to resources inside of your own VPCApiGatewayVpcLink:Type:AWS::ApiGatewayV2::VpcLinkProperties:Name:fargate-vpc-linkSecurityGroupIds:- !Ref ApiGatewaySecurityGroupSubnetIds:!Ref PublicSubnetIds# Setup the integration between the API Gateway and the VPC LinkApiGatewayVpcLinkIntegration:Type:AWS::ApiGatewayV2::IntegrationProperties:ApiId:!Ref ApiGatewayConnectionId:!Ref ApiGatewayVpcLinkConnectionType:VPC_LINKIntegrationType:HTTP_PROXYIntegrationUri:!Ref ServiceDiscoveryServiceArnPayloadFormatVersion:1.0IntegrationMethod:ANY# An API gateway stage (production)ApiGatewayStage:Type:AWS::ApiGatewayV2::StageProperties:ApiId:!Ref ApiGatewayStageName:"$default"AutoDeploy:trueDefaultRouteSettings:DetailedMetricsEnabled:trueAccessLogSettings:DestinationArn:!GetAtt LogGroup.ArnFormat:>- {"requestId":"$context.requestId", "ip": "$context.identity.sourceIp",
"caller":"$context.identity.caller",
"user":"$context.identity.user","requestTime":"$context.requestTime",
"routeKey":"$context.routeKey",
"status":"$context.status"}# The route that sends traffic to the integrationApiGatewayRoute:Type:AWS::ApiGatewayV2::RouteProperties:ApiId:!Ref ApiGatewayRouteKey:"$default"Target:!Sub "integrations/${ApiGatewayVpcLinkIntegration}"# This log group stores the access logs from the API GatewayLogGroup:Type:AWS::Logs::LogGroup# This security group is used by the VPC link, so that# you can control which resources in the VPC the VPC link is# allowed to communicate with.ApiGatewaySecurityGroup:Type:AWS::EC2::SecurityGroupProperties:GroupDescription:Security group for API gatewayVpcId:!Ref VpcId# Configure the security group of the service to accept# inbound traffic originating from the security group of# the API gateway's VPC linkServiceIngressFromApiGateway:Type:AWS::EC2::SecurityGroupIngressProperties:Description:Allow API Gateway to communicate to the serviceGroupId:!Ref ServiceSecurityGroupIpProtocol:-1SourceSecurityGroupId:!Ref ApiGatewaySecurityGroupOutputs:ApiGatewayUri:Description:The URI at which you can send traffic to your Fargate serviceValue:!GetAtt ApiGateway.ApiEndpoint
Things to look for in this template:
AWS::ApiGatewayV2::VpcLink and AWS::ApiGatewayV2::Integration - This is how the API Gateway is able to communicate privately to tasks inside of the VPC
ServiceIngressFromApiGateway - The containerized service’s security group must accept inbound traffic from the security group used by the API Gateway’s VPC link.
AWS::ApiGatewayV2::Stage - Note the AccessLogSettings which can be used to store access logs into an AWS CloudWatch log group.
Deploy it all
At this point you should have the following CloudFormation templates:
vpc.yml - Defines the virtual private network that everything will be deployed inside of
cluster.yml - Defines the Amazon ECS cluster and AWS Cloud Map namespace
service.yml - Defines how to deploy the container image using Amazon ECS and AWS Fargate, and register it into AWS Cloud Map.
api-gateway.yml - Defines the API Gateway and how it attaches to the VPC and sends traffic to the container service.
Use the following parent stack to deploy all four templates at once:
AWSTemplateFormatVersion:"2010-09-09"Transform:AWS::Serverless-2016-10-31Description:Parent stack that deploys VPC, Amazon ECS cluster for AWS Fargate,a serverless Amazon ECS service deployment that hoststhe task containers on AWS Fargate, and an API Gateway that forwardstraffic to the deployed containers.Resources:# 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.ymlParameters:VpcId:!GetAtt VpcStack.Outputs.VpcId# This stack contains the container deploymentServiceStack:Type:AWS::Serverless::ApplicationProperties:Location:service.ymlParameters:VpcId:!GetAtt VpcStack.Outputs.VpcIdPublicSubnetIds:!GetAtt VpcStack.Outputs.PublicSubnetIdsClusterName:!GetAtt ClusterStack.Outputs.ClusterNameECSTaskExecutionRole:!GetAtt ClusterStack.Outputs.ECSTaskExecutionRoleServiceDiscoveryNamespaceId:!GetAtt ClusterStack.Outputs.ServiceDiscoveryNamespaceId# API Gateway IngressApiGatewayStack:Type:AWS::Serverless::ApplicationProperties:Location:api-gateway.ymlParameters:VpcId:!GetAtt VpcStack.Outputs.VpcIdPublicSubnetIds:!GetAtt VpcStack.Outputs.PublicSubnetIdsServiceSecurityGroup:!GetAtt ServiceStack.Outputs.ServiceSecurityGroupServiceDiscoveryServiceArn:!GetAtt ServiceStack.Outputs.ServiceDiscoveryServiceArn# The public facing URI of the deployed serviceOutputs:ApiGatewayUri:Description:The URI at which you can send traffic to your Fargate serviceValue:!GetAtt ApiGatewayStack.Outputs.ApiGatewayUri
You can use the following command to deploy this parent stack and it’s child stacks:
After you deploy the templates, you can locate the public facing URI of the API Gateway by visiting the API Gateway console. Or you can get the URI from the outputs of the CloudFormation stack using the following command:
sam list stack-outputs \
--stack-name api-gateway-fargate
Load this URL up in your web browser and verify that you see a “Welcome to nginx!” page. You can now replace the NGINX container image with your own custom container image that listens for traffic on port 80.
Tear it down
You can tear down the entire stack with the following command: