Deploying a Dockerised Web App Using AWS Elastic Container Service (ECS)

Abdul Rahman
The Startup
9 min readSep 15, 2020

--

In an earlier tutorial, we discussed how to deploy a web app on to an EC2 instance. However, it is not optimal to just have one copy of your app running on a single server, especially as your user base, and hence the traffic into your website, grows. What if that single server breaks down? What if the server overloads with traffic? You don’t want your entire website to go down in such instances (no pun intended). This is why we need multiple EC2 instances running copies of our app. We will also then need a load balancer to — you guessed it right — balance the traffic load by distributing network traffic among our instances.

You may be asking now, “Alright, so now I need to manage multiple instances? What if all of them break down at once?”. Well, this is why you should consider using AWS’s Elastic Container Service (ECS). ECS can automatically start up EC2 instances and host a copy of your web app on each of them. ECS also monitors EC2 instances and spin up new ones if any of them go down. On top of all that, ECS can scale the number of copies of your web app up and down as per the amount of traffic load and CPU and memory needs. It saves you a considerable amount of time, money and energy that you can re-invest in developing your app further. I hope this short intro convinced you to learn more about ECS, so let’s get cracking :)

Prerequisites for this tutorial:

Besides a Free-tier AWS account, you will need AWS CLI and docker installed on your machine. You can install docker from here and learn how to download and set up AWS CLI here. It is assumed that you already have a Dockerised web app in hand. If not, you can learn how to create one from my earlier tutorial or download a sample one from my git repository here.

WHAT DOES THE SETUP INCLUDE?

An ECS setup typically includes the following:

  • An auto-scaling group of EC2 instances
  • An ECS cluster
  • An ECS task definition and tasks
  • An ECR repository containing a docker image
  • An ECS Service

An ECS cluster manages an auto scaling group of EC2 instances. An ECR repository will contain docker images of your web app. An ECS service places tasks inside instances in the auto-scaling group. A task is, simply put, a copy of your web app. A task can spin up one or more docker containers depending on how your app is set up. Task definitions are blueprints for tasks that specify things like which docker image(s) to use, how much memory and CPU should be allocated to the task and the networking among different containers run by the task.

CREATING A SIMPLE ECS SETUP

1-Create an empty cluster

Let’s start by creating an empty ECS cluster. From the AWS console, click on the “Services” tab and click on “Elastic Container Service”. From the ECS page click on “Clusters” in the left-hand pane. Now, click on “Create cluster”. Select the “EC2 Linux + Networking” option and on the next page give your cluster a name. Tick the “Create an empty cluster” tick-box and click on “Create”.

2-Create an auto scaling group

Now, we need an auto-scaling group. But, before we do that we need to create the launch template based on which the instances in the auto-scaling group will spin up. For this, go to the EC2 console and select “Launch Templates” under “Instances” from the left-hand pane. Click on “Create launch template”. Give the launch template a name and a description. Under AMI select “amzn2-ami-ecs-hvm-2.0.20200902-x86_64-ebs” or any other ECS-optimised Linux-based AMIs. Now, go down and expand the “Advance details” section. Under “User data” copy and paste the following. Replace “<your-cluster-name>” with the name of the empty cluster you just created.

#!/bin/bashecho ECS_CLUSTER=<your-cluster-name> >> /etc/ecs/ecs.config

Under “IAM profile” click on “Create new IAM profile”. This will take you to the IAM console. From there, click on the “Create role” button. For the “AWS Trusted Entity” keep the default “AWS Service” option and under “Choose a Use Case” select EC2. Click on “Next: Permissions”. Select “EC2FullAccess”, “AmazonEC2ContainerRegistryFullAccess”, “AmazonECS_FullAccess” and “ssm-kms-policy” before clicking on “Attach policies”. The “ssm-kms-policy” is what allows you to log into the instance. If you don’t see the “ssm-kms-policy” policy, just create a new policy with the same name and using following JSON:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:GetEncryptionConfiguration",
"s3:GetBucketAcl",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::dcp-685169213993-eu-west-2-logs"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"cloudwatch:PutMetricData",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"kms:*",
"ssm:*",
"ec2messages:*",
"ssmmessages:*",
"logs:CreateLogGroup",
"logs:PutLogEvents",
"kms:ReEncrypt*",
"ec2:DescribeInstanceStatus"
],
"Resource": "*"
},
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:AbortMultipartUpload"
],
"Resource": "arn:aws:s3:::dcp-685169213993-eu-west-2-logs/session-manager/*"
}
]
}

Now, let’s go back to the launch template creation. Click on “Create launch template”. Then go back to the EC2 console and select “Auto Scaling Groups” from the left-hand pane. Click on “Create an auto scaling group”. Give it a suitable name, select the launch template you just created and click “Next”. For “On-demand instances” put down “2” and for instance type, remove any default selections and select “t2.micro”. No additional instances needed. Under network choose all the subnets available and click “Next”. Set “Desired”, “Minimum” and “Maximum capacity” of instances to “2”. Click “Skip to review”. Click on “Create Auto Scaling group”. Now if you go to “Instances” page from the EC2 console, you’ll be able to see the 2 instances which are part of the auto scaling group you just created. You may have to wait for a few minutes for the newly spun up instances to show up.

2-Create an ECR repository and upload your docker image

To create an ECR repo go to the ECS console and select “Repositories” from the left-hand pane. Click on “Create repository”. Give the repository a name. Enable the “Scan on push” option and click on the “Create repository” button.

Now click on the “View push commands” button and a popup will give you instructions on how to push your docker image into the repository.

Once you have finished pushing your image and can see it listed inside your ECR repository, copy the image URI. We’ll need this in a later step.

3-Create an application load balancer

From the EC2 console, click on “Load Balancers” from the left-hand pane. Then click on “Create load balancer” and select “Application Load Balancer”. Give the load balancer a suitable name, select all the availability zones for your VPC and click “Next”. Skip to the “Configure security groups” stage and opt to create a new security group. Under “Type” select “HTTP” and “Anywhere” as the “Source”. Click “Next” and opt to “Create a new target group”. Give the target group a name and make sure the protocol and port are set to “HTTP” and “80” respectively. Change the target type to “IP”. Under “Health checks” select the “HTTP” protocol again and for “path” select the path of your web app where you want your health checks to be done. I am giving the root path “/” of my flask app. Click on “Next” and “Next” again. Review your selections and click on “Create”

If your health check status code is set to “200” which is the default, you will want to make sure that the path you give is working in your web app and doesn’t do a forward or redirect to another path. Or else, you could change the success code under “Advanced health check settings”

4-Create a task definition

Go to the ECS console and click on “Task Definitions”. Select “Create task definition” and select “EC2” compatibility option. Give your task definition a name. Select “none” for the IAM role and “awsvpc” for the network mode. Under task size set “Task memory” as “256” and “CPU” as “512”. Scroll down and click on “Add container”.

Give the container a name and paste the image URI you copied earlier. For “Port mappings” add the container ports you want to use. The only one I’m using is port 80 of my container. Click on “Add” and you will be taken back to the task creation page. You should now see your container entry listed as part of the task definition. Scroll down and click on “Create”.

5-Create an ECS service

The last part of the process is creating an ECS service. Go to the empty cluster you created earlier and under “Services” click on “Create”. Select the “EC2” launch type, give your service a name and put in “2” for the number of tasks. Click on “Next”. Select your cluster VPC and all the subnets available. Under “Load balancing” select “Application Load Balancer”, then select the load balancer you just created. Now, select your container port and click on “Add to load balancer”. Select the listener port we created with the load balancer i.e “80:HTTP” and change the path pattern to the default path for your web app. In my case it is “/”. You will automatically be given a target group name. If not, type in one. Add the health check path as we did when creating the load balancer. Skip to the review stage and click on “Create service”.

The number of tasks in a service should not be bigger than the number of available instances in the ECS cluster.

You’ll now see the ECS service placing tasks in the instances in the auto scaling group. You can see this under “Events” on the ECS service page. You’ll also see targets being registered inside the target group you just created on the EC2 console. You can monitor the health checks under the “Targets” section on your target group page.

If you want to see what’s happening inside your instances, you can connect to your instances using a session manager from the EC2 console and see the docker logs. Inside the instances you can view the ECS logs by typing in the following:

cat /var/log/ecs/ecs-agent.log

TESTING IF IT WORKS

To see if your setup works go to your application load balancer and copy it’s DNS name. Paste it into a new browser tab and you should be able to access your web app!

If you are seeing a 502 error, it might be worth checking the security groups for all the instances in the auto scaling group. Make sure they allow inbound HTTP traffic through port 80 from anywhere.

GOING FURTHER FROM HERE

We have been looking at how to install a single web app using a single application load balancer. Application Load Balancers have a feature called “Path-based routing” that will allow you to host multiple web apps using a single load balancer. You will be able to use the DNS name of your application load balancer and add separate paths for each of your web apps. For instance, “aws.my-load-balancer.com/app1” and “aws.my-load-balancer.com/app2”. Load balancers are quite expensive. So, having separate ones for each of your web apps may not be a good idea. But, when you do this you may end up with the problem of not being able to rewrite your URLs. That is, you may want to remove the “/app1” and “/app2” bits from your URLs as those web apps may not accept those paths.

Unfortunately, AWS doesn’t support URL rewrites as of today. So, one good option would be using an Nginx container as a sidecar within each of your instances. They will essentially act as reverse proxies for your app containers rewriting the URLs as the traffic passes through them. Nginx does rewrite rules quite well. Using an Nginx sidecar container will also improve the security of your setup. But that is a fairly big setup. So, it would be best to keep the topic for a separate tutorial :)

--

--

Abdul Rahman
The Startup

Budding software engineer with experience in full-stack development and DevOps engineering. 📚 Love to code. ⌨️ Love to write.✒️