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.
You can now access your service via the domain name you created.