Search code examples
amazon-web-servicesamazon-ecsamazon-route53aws-fargateservice-discovery

Deploy minimal service to ECS Fargate without load balancer accessible via public internet


I have a small application that I want to deploy to AWS with least cost as possible that would still allow the application to be accessible in the public internet and to grow further when required. In addition to that I wanted to streamline my pipeline and automate everything.

That's why I've chosen the following setup:

  • AWS ECS
  • AWS Fargate Spot
  • AWS ServiceDiscovery
  • no load balancer

I wanted to go without load balancer for now because it's simply too costly to start with, especially when I want to scale down the application completely from time to time.

What have so far:

AWSTemplateFormatVersion: "2010-09-09"
Description: ECS deployment

Parameters:
  Image:
    Type: String
  Application:
    Type: String
  Namespace:
    Type: String
  Cluster:
    Type: String
  Cpu:
    Type: String
  Memory:
    Type: String

Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Ref Application
      ExecutionRoleArn: arn:aws:iam::**:role/ecsTaskExecutionRole
      NetworkMode: awsvpc
      Cpu: !Ref Cpu
      Memory: !Ref Memory
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: !Ref Application
          Image: !Ref Image
          Cpu: !Ref Cpu
          Memory: !Ref Memory
          PortMappings:
            - HostPort: 8080
              ContainerPort: 8080
          Essential: true

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: -1

  ServiceDiscoveryService:
    Type: AWS::ServiceDiscovery::Service
    Properties:
      Name: !Ref Application
      DnsConfig:
        DnsRecords:
          - Type: A
            TTL: 300
        NamespaceId: !Ref Namespace
      HealthCheckCustomConfig:
        FailureThreshold: 1

  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref Cluster
      ServiceName: !Ref Application
      TaskDefinition: !Ref TaskDefinition
      DesiredCount: 1
      CapacityProviderStrategy:
        - Base: 0
          CapacityProvider: FARGATE_SPOT
          Weight: 1
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !GetAtt SecurityGroup.GroupId
          Subnets:
            - **
            - **
            - **
      ServiceRegistries:
        - RegistryArn: !GetAtt ServiceDiscoveryService.Arn

I am not sure if I am at all able to run a container on Fargate with a different port than 80 if I am not using a load balancer. Because I read that SRV records can't be interpreted by curl and httpie. And I would definitely want to be able to call my api with that. And as I don't have a load balancer I can't configure a dynamic host mapping because that's not possible with Fargate.

https://docs.aws.amazon.com/de_de/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html

If you are using the Fargate launch type, the awsvpc network mode is required. With the host and awsvpc network modes, exposed container ports are mapped directly to the corresponding host port (for the host network mode) or the attached elastic network interface port (for the awsvpc network mode), so you cannot take advantage of dynamic host port mappings.

With the deployment above I was able to deploy everything to AWS and the task is running successfully. It has a public ip and I am able to query it from my laptop successfully with

http PUBLIC_IP/stocks/MMM

I have now registered a new domain, let's say test-1234.com and registered the name servers of my created hostzones.

Using dig for my-service.test-1234.com results in the private ip of the running task. I would assume that's the expected answer for a successfully registered task but I am not sure.

Querying it with

http my-service.test-1234.com/stocks/MMM

is failing with Failed to establish a new connection

Does anyone know how to make it work?


Solution

  • To summarize the comments:

    It's currently not possible to do that with Fargate. More details here.

    Nevertheless there are two other options that could work:

    1. Deploy the containers on EC2 instead of Fargate, because there you use different Network Modes than only awsvpc and can configure the port mapping.
    2. Deploy container via AWS App Runner where you can as well pause the services when you don't need them so that it's a low budget option as well and you don't need to take care of anything except for creating the App Runner service. The downside is that the creation of the App Runner service is currently only possible via CLI or UI but not yet via CloudFormation.