AWS Elastic Beanstalk is a toolkit to deploy web application server environments to AWS Elastic Compute Cloud. It is designed to get a new environment running as quickly as possible. Everything can be done from the command-line which helps automating the process.

This post describes how to deploy a new Ruby on Rails application with Sidekiq to the Elastic Compute Cloud leveraging its database services (AWS Relational Database Service and AWS ElastiCache).

What is Elastic Beanstalk?

AWS Elastic Beanstalk is an orchestration service for deploying infrastructure to various AWS services like EC2, S3, Simple Notification Service, CloudWatch, autoscaling, and Elastic Load Balancers.

It supports various applications and software stacks:

  • Ruby, PHP, Python applications on Apache HTTP Server
  • .NET Framework applications on IIS 7.5
  • Java applications on Apache Tomcat
  • Node.js applications
  • Docker containers

In “Ruby on Rails 5 in Docker” we explained how to create a deployable docker infrastructure for a simple Rails application. This post introduces you to deployment of this environment to a first AWS infrastructure via Elastic Beanstalk.

Benefits

  • Quickly deploy tested docker environments to Amazon Web Services
  • Configuration can be automated via CLI
  • All settings can be managed inside the AWS Management Console
  • Highly scalable with all AWS functionality

Components

In the “Docker inside Rails” blog post we configured PostgreSQL and Redis as separate services within out docker-compose file.

We could just deploy the services as defined via Docker Compose onto a set of simple EC2 instances, but AWS provides separate services for databases and in-memory data stores.

We suggest leveraging as much AWS managed services as possible instead of managing them completely on our own. This will allow us to use all autoscaling and load-balancing features without much work-around.

AWS Relational Database Service

AWS RDS helps with the configuration, management and scaling of relational databases in the cloud.

It provides six known database engines:

  • PostgreSQL
  • MySQL/MariaDB
  • Oracle
  • Microsoft SQL Server
  • Amazon Aurora

AWS ElastiCache

AWS EC is a managed in-memory data-store in the cloud.

It provides two open-source-in-memory-engines:

  • Redis
  • Memcached

AWS EC2 Instance

For the purpose of demonstrating all steps necessary we restrict ourselves to one EC2 ~~

Step 1 — Create an AWS account

If not already registered with Amazon Webservices. Go to aws.amazon.com and create a new account.

Then go to My Security Credentials and create a new set of credentials. You need these to use the AWS command-line interface.

Step 2 — Install CLI Tools & Initialize Elastic Beanstalk

1
2
3
sudo pip install --upgrade awsebcli awscli
cd ~/projects/my-app
eb init

This lead you through a wizard and create a new application within your AWS cloud.

Step 3 — Configure Container Registry

With Elastic Beanstalk you can use any registry you want, either official Dockerhub, your own private repository or a new private container repository via AWS Elastic Container Registry (ECR):

Using AWS Elastic Container Registry

First we create a new registry repository my-app.

1
aws ecr create-repository --repository-name my-app

Then we allow the aws-elasticbeanstalk-ec2-role to access the container registry.

1
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly --role-name aws-elasticbeanstalk-ec2-role

Now we can build the first container version and push it to the new AWS ECR.

1
2
3
4
5
cd ~/projects/my-app
`aws ecr get-login --no-include-email --region eu-central-1`
docker build -t my-app .
docker tag my-app:latest .dkr.ecr.eu-central-1.amazonaws.com/my-app:latest
docker push .dkr.ecr.eu-central-1.amazonaws.com/my-app:latest

Step 4 — Create Service Configuration

AWS Elastic Beanstalk expects a Dockerrun.aws.json that describes all your services similar to a docker-compose file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "AWSEBDockerrunVersion": 2,
  "volumes": [],
  "containerDefinitions": [
    {
      "name": "app",
      "image": ".dkr.ecr.eu-central-1.amazonaws.com/velaluqa-test:latest",
      "environment": [],
      "essential": true,
      "memory": 128,
      "mountPoints": [],
      "portMappings": [
        {
          "containerPort": 3000,
          "hostPort": 80
        }
      ]
    }
  ]
}

Testing Your Configuration

You can run your services locally to see if everything is set up correctly:

1
eb local run

Step 5 — Create Elastic Beanstalk Environment

Upon creating the environment your app starts on EC2. When everything is working correctly you should see a log for your deployment.

1
2
3
4
5
6
7
eb create \
  --database \
  -db.engine postgres \
  -db.i db.t2.micro \
  -db.size 5 \
  -db.version 9.6.5 \
  --envvars RAILS_ENV=production,SECRET_KEY_BASE=good_secret_key_base,PORT=3000

To open your webapp try executing eb open.

Tipps

Adding Sidekiq Worker

You can easily add a Sidekiq back-end for Rails ActiveJob. As persistent back-end you can use AWS ElastiCache and its Redis engine.

Create a new directory ./.ebextensions and create two files:

In .ebextensions/elasticache.config add:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Resources:
  MyCacheSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: "Lock cache down to webserver access only"
      SecurityGroupIngress :
        - IpProtocol : "tcp"
          FromPort :
            Fn::GetOptionSetting:
              OptionName : "CachePort"
              DefaultValue: "6379"
          ToPort :
            Fn::GetOptionSetting:
              OptionName : "CachePort"
              DefaultValue: "6379"
          SourceSecurityGroupName:
            Ref: "AWSEBSecurityGroup"
  MyElastiCache:
    Type: "AWS::ElastiCache::CacheCluster"
    Properties:
      CacheNodeType:
        Fn::GetOptionSetting:
          OptionName : "CacheNodeType"
          DefaultValue : "cache.t1.micro"
      NumCacheNodes:
        Fn::GetOptionSetting:
          OptionName : "NumCacheNodes"
          DefaultValue : "1"
      Engine:
        Fn::GetOptionSetting:
          OptionName : "Engine"
          DefaultValue : "redis"
      VpcSecurityGroupIds:
        -
          Fn::GetAtt:
            - MyCacheSecurityGroup
            - GroupId

Outputs:
  ElastiCache:
    Description : "ID of ElastiCache Cache Cluster with Redis Engine"
    Value :
      Ref : "MyElastiCache"

And in .ebextensions/options.config add:

1
2
3
4
5
6
option_settings:
  "aws:elasticbeanstalk:customoption":
    CacheNodeType : cache.t1.micro
    NumCacheNodes : 1
    Engine : redis
    CachePort : 6379

Further reading is available in the AWS docs.

To deploy run eb deploy.

Then configure your Rails application to use the environment variables with the ElastiCache host information that you set for your environment.

And add the worker service to your Dockerrun.aws.json:

1
2
3
4
5
6
7
8
9
10
{
  "name": "worker",
  "image": ".dkr.ecr.eu-central-1.amazonaws.com/velaluqa-test:latest",
  "command": "bundle exec sidekiq",
  "environment": [],
  "essential": true,
  "memory": 128,
  "mountPoints": [],
  "portMappings": []
}

Branch-specific Environment

Normally we use a CI service that pulls all changes of a commit for a branch and deploys to the respective environment.

If you want to initiate deploy manually you might want to make sure, that develop only gets deployed to your staging environment, but never to production. For that you can configure branch-defaults in .elasticbeanstalk/config.yml:

1
2
3
4
5
6
7
# ...
branch-defaults:
  develop:
    environment: my-app-dev-staging
  master:
    environment: my-app
# ...

Remote Logs

In the management console you can see a variety of logs. To get these into your console you can use:

1
eb logs

Remote Access

To easily ssh into your machines try eb ssh. After sudo you can work commands like docker ps or hook into your running containers via:

1
docker exec -it <container-name> /bin/bash

Load Balancing and WebSockets

Rails 5 introduced ActionCable which allows for real-time communications over websockets. For WebSockets to work, you have to encure that the Elastic Load Balancer listens on all TCP traffic not just HTTP.

To change the setting via CLI type eb config, which will load your environments config in your $EDITOR. Search for aws.elb:listener and change the InstanceProtocol and ListenerProtocol from HTTP to TCP:

1
2
3
4
5
6
7
8
9
# ...
aws:elb:listener:80:
  InstancePort: '80'
  InstanceProtocol: TCP
  ListenerEnabled: 'true'
  ListenerProtocol: TCP
  PolicyNames: null
  SSLCertificateId: null
# ...

After closing your editor, the configuration will be updated remotely.

Conclusion

Within a few minutes we created a private docker repository, pushed our rails application container image and deployed a fresh environment with all necessary services to AWS Elastic Compute Cloud.

I hope you gained some knowledge about how you could service your application. This is just the beginning. Next up, you may want to increase the number of EC2 instances, adjust deployment schemes, configure autoscaling on when and how to add new instances/tasks or maybe you formalize your infrastructure setup via AWS CloudFormation.

Possibilities are endless, but most importantly you got your product out there and are ready to test!