Jets Mega Mode: Run Rails on AWS Lambda
Update 12/21/2018: An improved way to run Rails on AWS Lambda is now supported that is effortless. This blog post covers it: Jets Afterburner: Serverless Rails on AWS Lambda in 5 Minutes. It is also documented here: Jets Rails Support. It is recommended you use Jets Afterburner for simple cases.
Jets allows you to run Rails applications on AWS Lambda via Mega Mode. The name reminds me of a few things:
- Mega Monolith: Rails applications can sometimes become a Majestic Monolith. Or sometimes it just becomes a Monolith š
- Power Rangers Mega Mode: The Power Rangers can combine their Zords.
- Mega Man: This superhero can change his arm to use different special weapons. One of the things that makes him Mega Man.
Mega Mode combines a Jets app and a Rack app together to allow you to run Rails on AWS Lambda with little effort. The docs for Rails Support are on the Ruby On Jets site. Hereās a Live Mega Mode Demo. Weāll go through an example of Mega Mode and get a Rails application running on AWS Lambda in this post.
Setup: New Jets App
To demonstrate Jets Rails Support, weāll first create a brand new Jets app. Then weāll add an existing Rails application to it. Here are the commands based on the Quick Start to generate a new Jets app.
gem install jets
jets new demo
cd demo
jets generate scaffold Post title:string
vim .env.development # edit with local db settings
jets db:create db:migrate
jets runner '3.times {|i| Post.create(title: "Title #{i+1}") }' # seed some data
jets server # starts server on localhost:8888
You should see a Jets welcome page on the localhost:8888 and simple CRUD actions under localhost:8888/posts. The Jets welcome page looks something like this:
And the CRUD posts index page like this:
Hereās the initial tongueroo/jets-mega-demo repo. Itās been tagged with before-megamode
.
Add Rails App: jets import:rails
Zords Combine! -Power Rangers
Now itās time to add a Demo Rails app to the Jets project. Jets provides a handy jets import:rails command that makes this a simple task.
jets import:rails http://github.com/tongueroo/demo-rails.git
The import command essentially copies the project to a rack folder within the demo Jets app. The import command also adds an example route to config/routes.rb
to enable Mega Mode. It looks something like this:
Jets.application.routes.draw do
# ...
# Enables Mega Mode Rails integration
any "*catchall", to: "jets/rack#process"
end
The newly added catchall route forwards requests to a special jets/rack#process
controller. This controller forwards requests to the Rails application in the rack subfolder.
Hereās the Jets project after the Mega Mode has been set up: tongueroo/jets-mega-demo.
Local Testing
First, it is good to check that the imported Rails app in the rack subfolder works locally.
cd rack # in the imported Rails app
bundle exec rails db:create db:migrate # needed if you havent ran the demo app before
bundle exec rackup # starts Rails app on http://localhost:9292
The demo-rails app has a /info
route that provides some info about the Rails application. You should see something like this:
Once the Rails app looks good, we can shut down the rack server and test the Jets app with Mega Mode. Letās start the jets server:
jets server
Notice that 2 servers start up. The jets server starts up on localhost:8888 and the rack server starts on localhost:9292
Now hit the /info
route again except this time on port 8888. It shows the same info page, but this time itās going through the Jets app via Mega Mode.
Letās also visit localhost:8888 and localhost:8888/posts again. These urls are served by the Jets app as defined by config/routes.rb
. Only when the routes hit the catchall Mega Mode route will it get forwarded to the Rails app. To make it clear we can adjust the app/views/posts/index.html.erb
and replace <h1>Posts</h1>
with <h1>Jets App: Posts</h1>
. Now we can see the difference easily:
http://localhost:8888/posts:
http://localhost:8888/info:
Mega Mode is controlled via routing. You can selectively route requests to the Rails app by adding routes send to jets/rack#process
.
Where to Find Logs
It is useful to know where logs are. The rack server will send Rails logs to whatever has been set up in the Rails app, usually a log file like rack/log/development.log
. Hereās an example:
$ tail -f rack/log/development.log
Started GET "/info" for 127.0.0.1 at 2018-11-03 21:18:34 +0000
Processing by DemoController#index as HTML
(0.2ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
(0.1ms) SELECT version()
Rendering demo/index.html.erb within layouts/application
Rendered demo/index.html.erb within layouts/application (0.5ms)
Completed 200 OK in 13ms (Views: 10.0ms | ActiveRecord: 0.9ms)
The main Jets app sends the logs to stdout as part of the jets server
command. Example:
$ jets s --host 0.0.0.0
* Environment: development
* Listening on tcp://localhost:9292
Use Ctrl-C to stop
I, [2018-11-03T21:28:51.363402 #18894] INFO -- : Processing by PostsController#index
I, [2018-11-03T21:28:51.364730 #18894] INFO -- : Event: {"resource"=>"/posts", "path"=>"/posts", "httpMethod"=>"GET", ...}
I, [2018-11-03T21:28:51.364801 #18894] INFO -- : Parameters: {}
I, [2018-11-03T21:28:51.587102 #18894] INFO -- : Completed Status Code 200 in 0.224176506s
104.220.39.135 - - [03/Nov/2018:21:28:51 +0000] "GET /posts HTTP/1.1" 200 - 0.4635
When the app is deployed to AWS Lambda, both Jets and Rails logs are streamed to CloudWatch Logs instead.
Deploy to AWS Lambda
Weāre almost ready to deploy to AWS Lambda. You will need an RDS database set up for this. You can follow the AWS docs, Create an RDS DB Instance, wizard steps to set up an RDS database. For this tutorial, weāre using MySQL 5.7.23. Also for simplicity, weāll make it publicly accessible and open up security group port 3306. This is a throwaway database and will have been deleted by the time this post has been published. It usually takes less than 5 minutes for the RDS database to be available. Once itās available you can grab the RDS endpoint from the RDS console:
To set up the RDS endpoint with the app, create a .env.development.remote
file at the top level of your project and updated it with the DATABASE_URL
. The format of DATABASE_URL
looks like this:
DATABASE_URL=mysql2://dbuser:dbpass@host/dbname?pool=5
DATABASE_URL=mysql2://dbuser:dbpass@demotest.cbuqdmc3nqvb.us-west-2.rds.amazonaws.com/dbname?pool=5 # another example with an rds host
vim .env.development.remote # edit and add DATABASE_URL
Weāre using an env.development.remote
file because we want this specific value for DATABASE_URL
to be set remotely on AWS Lambda only. More info about Env Files and Remote Only Variables are available on the docs.
One way to test the RDS database locally is by using the JETS_ENV_REMOTE=1
flag. Weāll also use this to create and migrate the database on RDS.
$ JETS_ENV_REMOTE=1 jets db:create db:migrate
$ JETS_ENV_REMOTE=1 jets console
>> ActiveRecord::Base.connection.tables
=> ["ar_internal_metadata", "posts", "schema_migrations"]
>>
Weāre now ready to deploy AWS Lambda! Letās run jets deploy
:
$ jets deploy
...
10:15:26PM UPDATE_COMPLETE_CLEANUP_IN_PROGRESS AWS::CloudFormation::Stack demo-dev
10:15:27PM UPDATE_COMPLETE AWS::CloudFormation::Stack demo-dev
Stack success status: UPDATE_COMPLETE
Time took for stack deployment: 2m 1s.
Prewarming application.
API Gateway Endpoint: https://u3egh0sere.execute-api.us-west-2.amazonaws.com/dev/
$
After the deploy completes, you can check it out in the browser. Using your specific API Gateway endpoint, visit /dev/posts
and /dev/info
. You should see something like this:
https://api-gateway-endpoint/dev/posts:
https://api-gateway-endpoint/dev/info:
Explore the AWS Console
It is useful to check out the Lambda console because you can see the function code and even test it live in the editor. Lambda Functions:
Letās take a look at the demo-dev-posts_controller-index
function and test it with the Lambda Console:
Click on the āTestā button on the upper right-hand corner and configure a test event. For the test event payload, it does not matter what we use since the PostsController#index
doesnāt use the parameters so just use the provided example. After youāve configured an event in hit āTestā again you should see something like this:
For other actions, the test event payload does matter. Controller Lambda functions expect a payload from API Gateway. So you need a similar payload structure to test with the Lambda Console. Adding a puts JSON.dump(event)
in the controller is a great way grab a live API Gateway event payload for testing. Also here are some examples to help you get an idea of the minimal structure:
Action | Payload | Notes |
---|---|---|
posts#index | {"path": "/posts"} |
Path is always required. |
posts#new | {"path": "/posts/new"} |
The new action uses the path to generate the new form. |
posts#show | {"path": "/posts/123"} |
The post id must be provided and exist in the database or youāll get a āCouldnāt find Post without an IDā error. 123 is an example. |
posts#edit | {"path": "/posts/123/edit", "pathParameters": {"id": "123"}} |
You will also need pathParameters because thatās how the controller gets the id parameter. |
jets/rack#process | {"path": "/info"} |
The rack controller requires the path to be forwarded to the Rails app. |
Notice how the jets/rack#process
controller just needs a {"path": "/info"}
payload since thatās all the Rails app /info
url requires.
CloudWatch Logs
As mentioned early in the āWhere to Find Logsā section, when the app is deployed to AWS Lambda, the logs are streamed to Cloudwatch logs instead. Hereās what the CloudWatch Log groups look like:
If you click into the posts_controller-index
log group and hit the /dev/posts
url in the browser you should see something like this:
For Mega Mode requests, go to the jets-rack_controller-process
log group and hit the /dev/info
url, youāll notice both Rails and Jets logs:
Jets prepends āRails: ā to the Rails logging output to distinguish them from the Jets app logging.
Live Demo
Hereās a Live Demo to see and use Mega Mode in action.
Thatās it! You can run a Rails application on AWS Lambda today. Hope you like Mega Mode and give it try. Also if you find Jets interesting, please give it āļø it on GitHub. š
More info
- For an Jets Introduction: Introducing Jets: A Ruby Serverless Framework.
- Also more info at: Jets documentation site.
Jets Links and Tutorial Series
- Introducing Jets: A Ruby Serverless Framework
- Toronto Serverless Presentation: Jets Framework
- Jets Afterburner: Serverless Rails in 5 Minutes
- Mega Mode: Rails on AWS Lambda
- An Introductory CRUD App Part 1
- Deploy to AWS Lambda Part 2
- Debugging Logs Part 3
- Background Jobs Part 4
- IAM Policies Part 5
- Function Properties Part 6
- Extra Environments Part 7
- Different Environments Part 8
- Polymorphic Support Part 9
- Jets Delete Tutorial
- Jets Image Uploads Tutorial with CarrierWave
- Cron Job Tutorial: Backup Route53
- Build an API with the Jets Ruby Serverless Framework
Thanks for reading this far. If you found this article useful, I'd really appreciate it if you share this article so others can find it too! Thanks š Also follow me on Twitter.
Got questions? Check out BoltOps.
You might also like
More tools:
-
Kubes
Kubes: Kubernetes Deployment Tool
Kubes is a Kubernetes Deployment Tool. It builds the docker image, creates the Kubernetes YAML, and runs kubectl apply. It automates the deployment process and saves you precious finger-typing energy.
-
Jets
Jets: The Ruby Serverless Framework
Ruby on Jets allows you to create and deploy serverless services with ease, and to seamlessly glue AWS services together with the most beautiful dynamic language: Ruby. It includes everything you need to build an API and deploy it to AWS Lambda. Jets leverages the power of Ruby to make serverless joyful for everyone.
-
Lono
Lono: The CloudFormation Framework
Building infrastructure-as-code is challenging. Lono makes it much easier and fun. It includes everything you need to manage and deploy infrastructure-as-code.