This article is part 3 of a 4 part guide to running Docker containers on AWS ECS. ECS stands for Elastic Container Service. It is a managed container service that can run docker containers. Although AWS also offers container management with Kubernetes, (EKS) it also has its proprietary solution (ECS).

The guide will cover:

  • Creating the ECS Cluster.
  • Provision an Image Registry (ECR) and push docker images to the registry.
  • Deploying Containers to the cluster using Task and Service Definitions.
  • Creating a Pipeline to Update the services running on the ECS Cluster.

Here are the part 1 and 2:

Running Docker Containers on AWS With ECS – Part 1

Running Docker Containers on AWS ECS – Upload Docker Images to ECR – part 2

Part 3 of this guide will cover, “Deploying Containers to the cluster using Task and Service Definitions,”.

For this demonstration, we will use the simple hello-world image we had pushed to our ECR registry from Docker Hub. We will create a task and service definition and deploy this to the ECS cluster.

Requirements/Prerequisites

  • An AWS Account.
  • Created a User on the account with Permissions to provision resources on the account.
  • Created a Route 53 Hosted Zone with your custom domain (Can be public or Private zone depending on the user requirements).
  • Imported or generated your site certificate to ACM (Amazon Certificate Manager).
  • Provisioned an AWS ECS Cluster.
  • Uploaded your Docker Image to the ECR Registry.

Create the AWS Application Load Balancer and Target Group

The ALB (Application Load Balancer) is an AWS managed load balancer that routes traffic based on OSI layer 7 protocols. We will use the Load Balancer to expose our hello-world service endpoint. We already have an AWS guide on creating an Application load balancer on the link below:

Hence, I will not go into too much detail about the ALB. We will use the CloudFormation template below to create and configure our ECS ALB. The template will provision;

  • An ALB (Application Load balancer).
  • The ALB Security Group.
  • Target Group.
  • ALB Listeners and Listener Rule.

N/B: Should the reader/user want to expose their services internally, they should create the load balancer on the private subnets. Otherwise, the load balancer should be internet-facing and created on the public subnets. For a highly available load balancer, the user should provision it on different subnets in different availability zones.

AWSTemplateFormatVersion: "2010-09-09"
Description: "Create ALB, Target Groups and ALB security group"
Parameters:
    VPC:
        Type: String
        Description: The vpc to launch the service
        Default: vpc-ID

    PublicSubnet1:
        Type: String
        Description: The subnet where to launch the service
        Default: subnet-ID

    PublicSubnet2:
        Type: String
        Description: the subnet where to Launch the service
        Default: subnet-ID

Resources:            
    ALBSecurityGroup:
        Type: "AWS::EC2::SecurityGroup"
        Properties:
            GroupDescription: "security group for ALB"
            GroupName: "test-prod-ALB-SG"
            Tags: 
              - 
                Key: "Project"
                Value: "test-blog"
              - 
                Key: "createdBy"
                Value: "Maureen Barasa"
              - 
                Key: "Environment"
                Value: "test"
              - 
                Key: "Name"
                Value: "test-ALB-SG"
            VpcId: !Ref VPC
            SecurityGroupIngress: 
              - 
                CidrIp: "0.0.0.0/0"
                FromPort: 80
                IpProtocol: "tcp"
                ToPort: 80
              - 
                CidrIp: "0.0.0.0/0"
                FromPort: 443
                IpProtocol: "tcp"
                ToPort: 443
    
    ApplicationLoadBalancer:
        Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
        Properties:
            Name: "test-Application-Load-Balancer"
            Scheme: "internet-facing"
            Type: "application"
            Subnets: 
              - !Ref PublicSubnet1
              - !Ref PublicSubnet2
            SecurityGroups: 
              - !Ref ALBSecurityGroup
            IpAddressType: "ipv4"
            LoadBalancerAttributes: 
              - 
                Key: "access_logs.s3.enabled"
                Value: "true"
              - 
                Key: "idle_timeout.timeout_seconds"
                Value: "60"
              - 
                Key: "deletion_protection.enabled"
                Value: "false"
              - 
                Key: "routing.http2.enabled"
                Value: "true"
              - 
                Key: "routing.http.drop_invalid_header_fields.enabled"
                Value: "false"
            Tags: 
              - 
                Key: "Project"
                Value: "test-blog"
              - 
                Key: "createdBy"
                Value: "Maureen Barasa"
              - 
                Key: "Environment"
                Value: "test"
              - 
                Key: "Name"
                Value: "test-Application-Load-Balancer"

    HTTPSListener:
        Type: "AWS::ElasticLoadBalancingV2::Listener"
        Properties:
            LoadBalancerArn: !Ref ApplicationLoadBalancer
            Port: 443
            Protocol: "HTTPS"
            SslPolicy: "ELBSecurityPolicy-2016-08"
            Certificates: 
              - 
                CertificateArn: arn:aws:acm:eu-central-1:*************:certificate/************
                
            DefaultActions: 
              - 
                Order: 1
                TargetGroupArn: !Ref TestTargetGroup
                Type: "forward"

    HTTPListener:
        Type: "AWS::ElasticLoadBalancingV2::Listener"
        Properties:
            LoadBalancerArn: !Ref ApplicationLoadBalancer
            Port: 80
            Protocol: "HTTP"
            DefaultActions: 
              - 
                Order: 1
                RedirectConfig: 
                    Protocol: "HTTPS"
                    Port: "443"
                    Host: "#{host}"
                    Path: "/#{path}"
                    Query: "#{query}"
                    StatusCode: "HTTP_301"
                Type: "redirect"
                
    TestTargetGroup:
        Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
        Properties:
            HealthCheckIntervalSeconds: 30
            HealthCheckPath: "/"
            Port: 80
            Protocol: "HTTP"
            HealthCheckPort: "traffic-port"
            HealthCheckProtocol: "HTTP"
            HealthCheckTimeoutSeconds: 5
            UnhealthyThresholdCount: 2
            TargetType: "ip"
            Matcher: 
                HttpCode: "200"
            HealthyThresholdCount: 5
            VpcId: !Ref VPC
            Name: "target-group-1"
            HealthCheckEnabled: true
            TargetGroupAttributes: 
              - 
                Key: "stickiness.enabled"
                Value: "false"
              - 
                Key: "deregistration_delay.timeout_seconds"
                Value: "300"
              - 
                Key: "stickiness.type"
                Value: "lb_cookie"
              - 
                Key: "stickiness.lb_cookie.duration_seconds"
                Value: "86400"
              - 
                Key: "slow_start.duration_seconds"
                Value: "0"
              - 
                Key: "load_balancing.algorithm.type"
                Value: "round_robin"

               
    TestListenerRule1:
        Type: "AWS::ElasticLoadBalancingV2::ListenerRule"
        Properties:
            Priority: "1"
            ListenerArn: !Ref HTTPSListener
            Conditions: 
              - 
                Field: "host-header"
                Values: 
                  - "test1.helloworld.com"
            Actions: 
              - 
                Type: "forward"
                TargetGroupArn: !Ref TestTargetGroup
                Order: 1
                ForwardConfig: 
                    TargetGroups: 
                      - 
                        TargetGroupArn: !Ref TestTargetGroup
                        Weight: 1
                    TargetGroupStickinessConfig: 
                        Enabled: false

Outputs:        
    ALB:
        Description: The created loadbalancer
        Value: !Ref ApplicationLoadBalancer

    TargetGroup:
        Description: The created TargetGroup 
        Value: !Ref TestTargetGroup

    LoadBalancerSecurityGroup:
        Description: the securty group for the ALB
        Value: !Ref ALBSecurityGroup

Ensure that under HTTPS Listener, replace the certificate with your generated certificate ARN. Also, under the listener rule, we should replace the host header with a record set created by the user on the route 53 hosted zone.

The Tags and Names of the resources should also be customized to the user’s requirements.

ECS Task and Service Definition

An ECS Task Definition defines the requirements for your Docker container. It defines the image to be used, CPU, and memory requirements e.t.c.

An ECS service definition defines how the application/service will be run. It defines the launch type, the cluster where the service will be run, the target group to use for the ALB, the task definition to use e.t.c.

Create the ECS Task Execution Role

N/B: The task execution role is usually already created on AWS accounts. One can search for it as ecsTaskExecutionRole. In case one has not been created on your account, use the below CloudFormation template to create one.

AWSTemplateFormatVersion: "2010-09-09"
Description: "Template to create ECS Task Execution Role"

Resources:
  ECSTaskExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      Description: The ECS task execution Role
      RoleName: AWSECSTaskExecutionRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - ecs-tasks.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
      Tags: 
        - 
          Key: "Project"
          Value: "test-blog"
        - 
          Key: "Environment"
          Value: "test"
        - 
          Key: "createdBy"
          Value: "Maureen Barasa"
        - 
          Key: "Name"
          Value: "AWSECSTaskExecutionRole"
          
Outputs:
  IAMRole:
    Description: the role created
    Value: !Ref ECSTaskExecutionRole
    Export: 
      Name: !Sub "${AWS::StackName}-rolename"

Create the ECS Task and Service Definition

Use the below templates to create the task and service definition.

N/B: The template creates task and service definition for a Fargate cluster. Also, for the task role and task execution role arn, use the arn for the role created above, or if existing, use the arn for the ecsTaskExecutionRole.

To create task and service definitions for EC2 cluster, replace LaunchType on Service Definition with EC2. On the Task Definition, EC2 can work with any Network Mode; awsvpc, bridge or host. Fargate only works with awsvpc mode.

AWSTemplateFormatVersion: "2010-09-09"
Description: "hello-world task and service definition"

Parameters:
    VPC:
        Type: String
        Description: The vpc to launch the service
        Default: vpc-ID

    Subnet1:
        Type: String
        Description: The subnet where to launch the service
        Default: subnet-ID

    Subnet2:
        Type: String
        Description: The subnet where to launch the service
        Default: subnet-ID

Resources:
    CWLoggroup:
        Type: AWS::Logs::LogGroup
        Properties:
            LogGroupName: ecs-hello-world-Loggroup

    TaskDefinition:
        Type: "AWS::ECS::TaskDefinition"
        Properties:
            ContainerDefinitions: 
              - 
                Essential: true
                Image: 429758582529.dkr.ecr.eu-central-1.amazonaws.com/hello-world:latest
                LogConfiguration: 
                    LogDriver: "awslogs"
                    Options: 
                        awslogs-group: !Ref CWLoggroup
                        awslogs-region: !Ref AWS::Region
                        awslogs-stream-prefix: "ecs"
                Name: "Hello_World"
                PortMappings: 
                  - 
                    ContainerPort: 80
                    HostPort: 80
                    Protocol: "tcp"
            Family: "Hello_World"
            TaskRoleArn: arn:aws:iam::429758582529:role/AWSECSTaskExecutionRole
            ExecutionRoleArn: arn:aws:iam::429758582529:role/AWSECSTaskExecutionRole
            NetworkMode: "awsvpc"
            RequiresCompatibilities: 
              - "FARGATE"
            Cpu: "256"
            Memory: "512"

    ServiceDefinition:
        Type: "AWS::ECS::Service"
        Properties:
            ServiceName: "hello-world"
            Cluster: "arn:aws:ecs:eu-central-1:429758582529:cluster/eu-central-1-test-ECS-Fargate-Cluster"
            LoadBalancers: 
              - 
                TargetGroupArn: "arn:aws:elasticloadbalancing:eu-central-1:************:targetgroup/test-fargate-hello/*********"
                ContainerName: "Hello_World"
                ContainerPort: 80
            DesiredCount: 1
            LaunchType: "FARGATE"
            PlatformVersion: "1.4.0"
            TaskDefinition: !Ref TaskDefinition
            DeploymentConfiguration: 
                MaximumPercent: 200
                MinimumHealthyPercent: 100
            NetworkConfiguration: 
                AwsvpcConfiguration: 
                    AssignPublicIp: "ENABLED"
                    SecurityGroups: 
                      - "sg-ID"
                    Subnets: 
                      - !Ref Subnet1
                      - !Ref Subnet2
            HealthCheckGracePeriodSeconds: 300
            SchedulingStrategy: "REPLICA"

Outputs:
  HelloTaskDefinition:
    Description: The created name of the ECS TaskDefinition 
    Value: !Ref TaskDefinition

  HelloService:
    Description: The ECS service
    Value: !Ref Service

N/B: The CloudFormation Template should be customized to the user’s requirements. We can customize the:

  • Names of the resources to be provisioned.
  • For Container Definitions, we can change the image name, port mappings, etc.
  • The user/reader should also replace the cluster and target group arn to reflect his/her own values.

When done you should have the service running on your ECS Cluster as below.

ECS Cluster with Service Running
ECS Cluster with Running Task and Service

You can now access your service via the domain name you created.

Other AWS Guides:

Happy Building!!!

Your support is our everlasting motivation,
that cup of coffee is what keeps us going!


As we continue to grow, we would wish to reach and impact more people who visit and take advantage of the guides we have on our blog. This is a big task for us and we are so far extremely grateful for the kind people who have shown amazing support for our work over the time we have been online.

Thank You for your support as we work to give you the best of guides and articles. Click below to buy us a coffee.

LEAVE A REPLY

Please enter your comment!
Please enter your name here