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!