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:
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.
- 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.