Creating a load balancer
Load balancers are like the waiters & waitresses of the web server world. Whenever a user wants to look at a web page, some software must respond to that request. If we only have one container instance running, then that instance will be the only one available to take orders and process them.
Having only one container running your application is kind of like going to a food truck with only one person making the food. Many businesses start with such a simple model, but need to expand when they get more customers. In those cases, we cannot the same person taking orders as the one making them. Similarly the server that takes the requests for web pages should not be the same as the one making them.
This is where load balancers come in. Load balancers act like waitress. A waitress who does not know about making the food, just about describing what the customer wants, forwarding that request to a cook, and then getting the food back to the customer's table. Similarly, load balancers do not know about HTML, CSS, or JavaScript. They know HTTP, which is the language of web requests. It can forward requests for web pages to different containers. If one container is too busy to respond to a request, it can send the request to one that is not busy. Hence they balance the workload amongst the containers.
For this part of the tutorial, we're going to use AWS EC2. Search for "ec2" in the service search menu:
The EC2 console is sophisticated and has many options, and is under constant improvement.
The load balancing options are in the sidebar, if you scroll down enough.
Click on "Load Balancers" and then click the blue button "Create Load Balancer".
You'll want to choose on the "Application Load Balancer" option, so click the "Create" button there.
Here we are brought to the load balancer configuration. We'll give it a name using our 'the-greatest' naming convention: "the-greatest-rails-lb-ever"
For the Availability Zones, you'll want to enable both subnets.
The next panel is to "Configure Security Settings", but we can skip that for now and move on to "Configure Security Groups".
For this step, we DO NOT want to select an existing security group. When switching to "Create a new security group", we're asked the name and description of the new group. We're sticking to the same naming style, and going with "the-greatest-rails-security-group-ever".
In step 4, we configure the load balancer around what group it will delegate work to. I've gone with the name "the-greatest-target-group-ever".
From here you can skip to step 6, review.
Click the blue button "Create" and you should see, after a brief loading period, a success screen:
Creating the ECS cluster
We have containers but we don't have any machine instances to run them on. Towards this end, we need to create an ECS cluster. The next section will do that for us. Towards this end, we'll first need a key pair from EC2 to run ECS.
Creating an EC2 key pair
Search for "ec2" in the AWS console.
Scroll down the side menu until you find "Key Pairs".
Next we'll create a new key pair by clicking the orange button "Create key pair".
We're going to create "the-greatest-rails-key-pair-ever"
Click on "Create key pair" and you'll get a success screen. It should also download a .pem
file that you will need soon.
Creating the ECS cluster
Search for "ecs" in the AWS console and let's create a cluster.
Once there, click the "Clusters" option from the side menu.
Click "Create Cluster". We'll be using the "EC2 Linux + Networking" option.
Click the "Next Step" button at the bottom and now we can pick a cluster name. I went with "the-greatest-rails-cluster-ever".
Scroll down and you should see Instance configuration. I changed the EC2 Instance type to "t2.micro" because it is a part of the free tier. Large applications will likely want "m3.medium" or larger.
We also specify the Key pair ("the-greatest-rails-key-pair-ever"), which we created earlier.
For the networking portion of this configuration, you'll be using the same VPC and subnets we configured earlier, when we configured the Availability Zones for the load balancer.
One last configuration is allowing CloudWatch to provide Container Insights.
When you're done, hit "Create". It will kick off the cluster. Give it a minute and it should be ready when all three steps are green.
When it's ready, click the "View Cluster" button, where we will create the ECS service.
Creating an ECS service
To create an ECS service on our new cluster, click on the "Create" button in the cluster detail view.
Much of the initial service configuration is filled in for us. When I did this, I needed to specify the launch type as "EC2", and the service name as "the-greatest-rails-service-ever". The number of tasks can be set to 1.
Go to the "Next Step" to configure the network.
In this part, we need to choose "Application Load Balancer", which is what we created earlier. The IAM role can be changed to "AWSServiceRoleForEC2". The load balancer name should be picked for you, since it's the only one available. When you're done here, you can click "Add to load balancer".
After you've clicked that, it should pop up with configurations on "Container to load balance". At this point, I picked "the-greatest-target-group-ever" as the target group name.
Make sure that the "Enable service discovery integration" is unchecked. Then click "Next Step".
Leave this configuration as is.
This is what my review page looked like.
When ready, hit "Create Service" and you'll see a success screen.
Creating AWS Security Groups
AWS Security Groups are like virtual firewalls, and are absolutely necessary to create a secure cluster. We're going to create a security group around our EC2 instances and our RDS database. This is to ensure that the traffic between the two instances is protected from the outside world.
Adding the cluster to the security group
Search for "ecs" in the AWS console.
We're going to go back to the cluster we just created.
Click on the name ("the-greatest-rails-cluster-ever") and it should bring you to the detail view.
Click on the "EC2 Instances" tab below and you should see at least one instance available.
Click on the "EC2 Instance" link, which in my case, starts with "i-089132...". This should bring you the detail view for the EC2 instance.
If you scroll down a bit in the lower panel, you should find a section called "Security groups" under the "Description" tag. In my case, the link starts with "EC2ContainerService-the-greatest-...".
Clicking on that link should bring you a list with only one security group.
Click on the "Inbound" tab in the bottom panel.
In that tab, click "Edit". We are editing the inbound rules for this security group. We already have one for HTTP. We also want to allow SSH-ing into this group, so we add another row for SSH, and set the source to "Anywhere". We add one more rule for All TCP. Set the source to the security group we created earlier. In my case, I started typing "the-great" and I added the one that begins with "sg-02f94...".
Hit "Save" and and the new rules will show up in the bottom panel.
We've added the security group to the ECS instance. Now we need to add it to our RDS database.
Adding the database to the security group
Search for "rds" in the AWS console.
Find your database in the list of database, then click it's name. In my example, it is "the-greatest-rails-app-ever-db-1".
We're going to edit the security group for the database as well. In the detail view of database, we can edit it's security group. Click on it's name, which in my case it is "default (sg-7d493718)".
This view should look familiar, since we were just editing the group for the container instances. Look for the "Inbound" tab at the bottom.
Clicking on that tab should give you an edit button. From there we can add a new rule for MySQL/Aurora, belonging to the same security group we used before.
Test the SSH connection
Go back into your EC2 panel and click on "Instances"" in the side menu.
Clicking "Connect" will bring up instructions on how to connect to this instance. Yours will be different from mine. Run the instructions to be sure you can connect to your instance, with one exception: Instead of root
, you should use ec2-user
.
Here's what I got when running it:
$ ssh -i "the-greatest-rails-key-pair-ever.pem" ec2-user@ec2-54-85-141-101.compute-1.amazonaws.com
Last login: Fri Feb 28 21:24:36 2020 from 104.218.140.180
__| __| __|
_| ( \__ \ Amazon Linux 2 (ECS Optimized)
____|\___|____/
For documentation, visit http://aws.amazon.com/documentation/ecs
No packages needed for security; 1 packages available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-172-16-0-12 ~]$
Check the running Docker containers:
[ec2-user@ip-172-16-0-12 ~]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e3fbc2eda44f 035513743183.dkr.ecr.us-east-1.amazonaws.com/the-greatest-rails-app-ever "sh entrypoint.sh" 50 seconds ago Up 48 seconds 0.0.0.0:32769->8080/tcp ecs-the-greatest-rails-docker-app-ever-30-web-a2e082efd5f689d71500
9be25ec937fa amazon/amazon-ecs-agent:latest
Final Testing of Our Rails application
Since our container is running, we should be able to access our Rails application. The EC2 instance should have a publicly accessible domain name.
We're going to plug in that URL and add /blog_posts
at the end, since that endpoint will allow us to test the database as well.
Clicking on the 'New Blog Post' link should bring up a form to create a new blog post.
Once we've created the blog post, we will be redirected to this screen:
A little anti-climatic, but we have shown that our application does work in AWS!
Wrapping up
What have we learned so far?
- How to dockerize our Rails application.
- How to create an IAM role and ECR repository for a Docker image.
- How to create an ECS cluster and Task Definition based on that image.
- How to test the EC2 instance that is running the container.
With AWS, there is plenty to learn and explore. I will continue to post about new topics as I learn them! If you have any questions, suggestions or other comments, please feel to them down below.
Thanks everyone! :)
Top comments (10)
Hi @farleyknight, thx for the guide! I followed your steps but my instance is not starting. When I check the log I see pretty strange error that keeps showing up again and again:
Do you have any ideas why this can happen? If yes, can you please share how did you solve it? If not, maybe you can give me a hint how I can troubleshoot this issue?
Thx a lot for this guide and your help!
@farleyknight , the mysql connected and created/migrated db, however the web service is not starting, docker log from aws:
It seems like aws container for unknown reason is stuck executing this command:
Do you have any idea why this can happen?
P.S. I added echo before
bundle exec
to make sure that container finished running previous lines:To anybody who is having the same problem as I had, it turns out that ruby on rails on docker container application in my case was running on random port (e.g. 32768, check screens below). This happened because in container settings the port was set to be 0, that means just to use random Host Port. Because of this, simple ping to 80 or 8080 port was not passing thru instance running container.
I figured that out by ssh-ing to instance that runs container (mentioned in this article above under Test the SSH connection section) and running
docker ps
command (see the screen below)Simple check I used to see if the request is forwarded to container or not is to execute
curl localhost:32768
from instance running container (see the screen below)So to sum up, the app was working, but I was checking a wrong port (80 or 8080). What I should have done is just to open instance url:Host Port, where Host Port was randomly selected and it was not selected to be 80 or 8080.
To fix this issue you should modify container routing to explicitly set Host Port to be equal to 80 or 8080 (or any other port you choose to run web application)
How did you solved database connection issue?
@farleyknight I am also facing the similiar problem. Can you please help me with this?
Hi @farleyknight I ran my cluster and my instance is working fine, the only issue that I have is that I can't connect to the DB on RDS, I tried with PgAdmin and I got a timeout error, maybe is something related to the security group?
@lucasocon , did you manage to start application on cluster? e.g. can you open blog site and create a separate post with web UI?
I managed to connect to DB with DataGrip only when I choose
Standard create
andPublic Access: Yes
when creating db. Still I am not able to launch application due to I guess MySQL connection issue, see my post above.Any ideas how this can be resolved?
This hypothesis was wrong, I added console log statement before and after mysql app initialization and saw that the app was not frozen and passed mysql initialization phase.
The real problem for the issue I described here was
Host Port = 0
which meant that container is going to use random port as a Host Port in traffic routing. Check my replies above if you want to know more details why this happened and how to fix it.I followed tthis but somehow my task is getting automatically shutdown and restarted again and again, cloudfront logs are:
2020-07-13T01:54:35.600+05:30
yarn install v1.22.4
2020-07-13T01:54:35.917+05:30
[1/4] Resolving packages...
2020-07-13T01:54:37.333+05:30
success Already up-to-date.
2020-07-13T01:54:37.337+05:30
Done in 1.75s.
2020-07-13T01:54:38.325+05:30
yarn install v1.22.4
2020-07-13T01:54:38.628+05:30
[1/4] Resolving packages...
2020-07-13T01:54:40.119+05:30
[2/4] Fetching packages...
2020-07-13T01:54:42.023+05:30
info fsevents@2.1.3: The platform "linux" is incompatible with this module.
2020-07-13T01:54:42.023+05:30
info "fsevents@2.1.3" is an optional dependency and failed compatibility check. Excluding it from installation.
2020-07-13T01:54:42.025+05:30
info fsevents@1.2.13: The platform "linux" is incompatible with this module.
2020-07-13T01:54:42.025+05:30
info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation.
2020-07-13T01:54:42.085+05:30
[3/4] Linking dependencies...
2020-07-13T01:54:42.093+05:30
warning " > webpack-dev-server@3.11.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
2020-07-13T01:54:42.093+05:30
warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0".
2020-07-13T01:54:47.701+05:30
[4/4] Building fresh packages...
2020-07-13T01:54:47.730+05:30
Done in 9.41s.
2020-07-13T01:55:02.519+05:30
Everything's up-to-date. Nothing to do
2020-07-13T01:55:06.280+05:30
D, [2020-07-12T20:25:06.280705 #220] DEBUG -- : [1m[35m (1.0ms)[0m [1m[34mSELECT pg_try_advisory_lock(2903025111434932890)[0m
2020-07-13T01:55:06.300+05:30
D, [2020-07-12T20:25:06.300771 #220] DEBUG -- : [1m[35m (1.2ms)[0m [1m[34mSELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC[0m
2020-07-13T01:55:06.311+05:30
D, [2020-07-12T20:25:06.311509 #220] DEBUG -- : [1m[36mActiveRecord::InternalMetadata Load (1.1ms)[0m [1m[34mSELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2[0m [["key", "environment"], ["LIMIT", 1]]
2020-07-13T01:55:06.318+05:30
D, [2020-07-12T20:25:06.318450 #220] DEBUG -- : [1m[35m (1.1ms)[0m [1m[34mSELECT pg_advisory_unlock(2903025111434932890)[0m
2020-07-13T01:55:06.336+05:30
database has been migrated!
2020-07-13T01:55:09.226+05:30
=> Booting Puma
2020-07-13T01:55:09.226+05:30
=> Rails 6.0.3.2 application starting in staging
2020-07-13T01:55:09.226+05:30
=> Run
rails server --help
for more startup options2020-07-13T01:55:10.939+05:30
Puma starting in single mode...
2020-07-13T01:55:10.939+05:30
2020-07-13T01:55:10.939+05:30
2020-07-13T01:55:10.939+05:30
2020-07-13T01:55:10.939+05:30
2020-07-13T01:55:10.939+05:30
Use Ctrl-C to stop
2020-07-13T01:55:17.332+05:30
I, [2020-07-12T20:25:17.329063 #224] INFO -- : [f033624b-8770-4112-8b05-bb7f3566e0b8] Started GET "/" for 172.31.12.192 at 2020-07-12 20:25:17 +0000
2020-07-13T01:55:17.332+05:30
I, [2020-07-12T20:25:17.330961 #224] INFO -- : [f033624b-8770-4112-8b05-bb7f3566e0b8] Processing by StaticsController#home as HTML
2020-07-13T01:55:17.337+05:30
I, [2020-07-12T20:25:17.337764 #224] INFO -- : [f033624b-8770-4112-8b05-bb7f3566e0b8] Redirected to 172.31.35.107:32811/users/sign_in
2020-07-13T01:55:17.338+05:30
I, [2020-07-12T20:25:17.338120 #224] INFO -- : [f033624b-8770-4112-8b05-bb7f3566e0b8] Completed 302 Found in 7ms (Allocations: 536)
2020-07-13T01:55:17.356+05:30
I, [2020-07-12T20:25:17.356581 #224] INFO -- : [4bce0e2c-2eb9-4067-82ec-c8d46d2f6acb] Started GET "/" for 172.31.37.118 at 2020-07-12 20:25:17 +0000
2020-07-13T01:55:17.357+05:30
I, [2020-07-12T20:25:17.357318 #224] INFO -- : [4bce0e2c-2eb9-4067-82ec-c8d46d2f6acb] Processing by StaticsController#home as HTML
2020-07-13T01:55:17.358+05:30
I, [2020-07-12T20:25:17.358182 #224] INFO -- : [4bce0e2c-2eb9-4067-82ec-c8d46d2f6acb] Redirected to 172.31.35.107:32811/users/sign_in
2020-07-13T01:55:17.358+05:30
I, [2020-07-12T20:25:17.358390 #224] INFO -- : [4bce0e2c-2eb9-4067-82ec-c8d46d2f6acb] Completed 302 Found in 1ms (Allocations: 371)
2020-07-13T01:55:47.358+05:30
I, [2020-07-12T20:25:47.358200 #224] INFO -- : [7d482232-6a1d-4d7f-b279-4a508d6c8798] Started GET "/" for 172.31.12.192 at 2020-07-12 20:25:47 +0000
2020-07-13T01:55:47.359+05:30
I, [2020-07-12T20:25:47.359128 #224] INFO -- : [7d482232-6a1d-4d7f-b279-4a508d6c8798] Processing by StaticsController#home as HTML
2020-07-13T01:55:47.360+05:30
I, [2020-07-12T20:25:47.360016 #224] INFO -- : [7d482232-6a1d-4d7f-b279-4a508d6c8798] Redirected to 172.31.35.107:32811/users/sign_in
2020-07-13T01:55:47.360+05:30
I, [2020-07-12T20:25:47.360237 #224] INFO -- : [7d482232-6a1d-4d7f-b279-4a508d6c8798] Completed 302 Found in 1ms (Allocations: 371)
2020-07-13T01:55:47.386+05:30
I, [2020-07-12T20:25:47.386789 #224] INFO -- : [ad51aec8-ca24-4df3-9c4a-fe0b270f4a9c] Started GET "/" for 172.31.37.118 at 2020-07-12 20:25:47 +0000
2020-07-13T01:55:47.387+05:30
I, [2020-07-12T20:25:47.387553 #224] INFO -- : [ad51aec8-ca24-4df3-9c4a-fe0b270f4a9c] Processing by StaticsController#home as HTML
2020-07-13T01:55:47.388+05:30
I, [2020-07-12T20:25:47.388546 #224] INFO -- : [ad51aec8-ca24-4df3-9c4a-fe0b270f4a9c] Redirected to 172.31.35.107:32811/users/sign_in
2020-07-13T01:55:47.388+05:30
I, [2020-07-12T20:25:47.388751 #224] INFO -- : [ad51aec8-ca24-4df3-9c4a-fe0b270f4a9c] Completed 302 Found in 1ms (Allocations: 371)
2020-07-13T01:55:47.557+05:30
D, [2020-07-12T20:25:47.557215 #224] DEBUG -- [Bugsnag]: Request to sessions.bugsnag.com completed, status: 202
2020-07-13T01:56:17.391+05:30
I, [2020-07-12T20:26:17.389284 #224] INFO -- : [fa10091b-ba10-40a7-bca8-c354367d6224] Started GET "/" for 172.31.12.192 at 2020-07-12 20:26:17 +0000
2020-07-13T01:56:17.391+05:30
I, [2020-07-12T20:26:17.389995 #224] INFO -- : [fa10091b-ba10-40a7-bca8-c354367d6224] Processing by StaticsController#home as HTML
2020-07-13T01:56:17.391+05:30
I, [2020-07-12T20:26:17.390748 #224] INFO -- : [fa10091b-ba10-40a7-bca8-c354367d6224] Redirected to 172.31.35.107:32811/users/sign_in
2020-07-13T01:56:17.391+05:30
I, [2020-07-12T20:26:17.390890 #224] INFO -- : [fa10091b-ba10-40a7-bca8-c354367d6224] Completed 302 Found in 1ms (Allocations: 371)
2020-07-13T01:56:17.417+05:30
I, [2020-07-12T20:26:17.417779 #224] INFO -- : [77bd4b5c-21ad-4896-b1ff-3a50c4f1351d] Started GET "/" for 172.31.37.118 at 2020-07-12 20:26:17 +0000
2020-07-13T01:56:17.418+05:30
I, [2020-07-12T20:26:17.418630 #224] INFO -- : [77bd4b5c-21ad-4896-b1ff-3a50c4f1351d] Processing by StaticsController#home as HTML
2020-07-13T01:56:17.419+05:30
I, [2020-07-12T20:26:17.419514 #224] INFO -- : [77bd4b5c-21ad-4896-b1ff-3a50c4f1351d] Redirected to 172.31.35.107:32811/users/sign_in
2020-07-13T01:56:17.419+05:30
I, [2020-07-12T20:26:17.419734 #224] INFO -- : [77bd4b5c-21ad-4896-b1ff-3a50c4f1351d] Completed 302 Found in 1ms (Allocations: 370)
2020-07-13T01:56:17.551+05:30
D, [2020-07-12T20:26:17.550908 #224] DEBUG -- [Bugsnag]: Request to sessions.bugsnag.com completed, status: 202
2020-07-13T01:56:47.396+05:30
I, [2020-07-12T20:26:47.396093 #224] INFO -- : [9668a89d-5422-4a72-a15a-9c4ef62f9b21] Started GET "/" for 172.31.12.192 at 2020-07-12 20:26:47 +0000
2020-07-13T01:56:47.397+05:30
I, [2020-07-12T20:26:47.397029 #224] INFO -- : [9668a89d-5422-4a72-a15a-9c4ef62f9b21] Processing by StaticsController#home as HTML
2020-07-13T01:56:47.397+05:30
I, [2020-07-12T20:26:47.397894 #224] INFO -- : [9668a89d-5422-4a72-a15a-9c4ef62f9b21] Redirected to 172.31.35.107:32811/users/sign_in
2020-07-13T01:56:47.398+05:30
I, [2020-07-12T20:26:47.398179 #224] INFO -- : [9668a89d-5422-4a72-a15a-9c4ef62f9b21] Completed 302 Found in 1ms (Allocations: 370)
2020-07-13T01:56:47.445+05:30
I, [2020-07-12T20:26:47.445049 #224] INFO -- : [66f07b83-37e8-4d51-b7b7-bd5bc0ffb581] Started GET "/" for 172.31.37.118 at 2020-07-12 20:26:47 +0000
2020-07-13T01:56:47.446+05:30
I, [2020-07-12T20:26:47.445939 #224] INFO -- : [66f07b83-37e8-4d51-b7b7-bd5bc0ffb581] Processing by StaticsController#home as HTML
2020-07-13T01:56:47.446+05:30
I, [2020-07-12T20:26:47.446826 #224] INFO -- : [66f07b83-37e8-4d51-b7b7-bd5bc0ffb581] Redirected to 172.31.35.107:32811/users/sign_in
2020-07-13T01:56:47.447+05:30
I, [2020-07-12T20:26:47.447048 #224] INFO -- : [66f07b83-37e8-4d51-b7b7-bd5bc0ffb581] Completed 302 Found in 1ms (Allocations: 370)
2020-07-13T01:56:47.570+05:30
D, [2020-07-12T20:26:47.570039 #224] DEBUG -- [Bugsnag]: Request to sessions.bugsnag.com completed, status: 202
Apologies Siddhant for the late reply. I can't quite tell what the problem is from these logs. Do you want to DM me on dev.to and maybe I can help you one-on-one? Let me know. Thanks!