Ruby on Rails: building a highly available application
In this tutorial, we’re going to dig deeper into the deployment of a highly available and scalable RoR application. But first, let’s make a quick overview about the RESTful architecture with whom we’re gonna implement our app.
RESTful architecture:
RESTful architectures carry some architectural constraints that were inherited from various networked distributed architectural styles. Client and server in RESTful architectures are orthogonal, meaning there is a clear separation of concerns and functions between them. User logic is moved completely to the client side and server components are kept simple to improve scalability. This separation also allows the two components to be developed and evolve independently, so long as they respect the communication protocols and defined interfaces. In a RESTful architecture, each request is treated independently. This means the architecture of the server is stateless and requests cannot take advantage of any stored context and must carry all the necessary information to be understood and processed. Each response also needs to be labeled as cacheable or noncacheable. If a response is cacheable, the client can store and reuse the data for network efficiency (i.e., cache the data to reduce round-trips).
MVC pattern:
Ruby on Rails is a Model-View-Controller (MVC) framework. MVC is a software architectural pattern that divides a given application into three interconnected logic blocks. In the three blocks, the internal representation of some information or object (the model) is separated from the way the user interacts with this information (the controller) and the way this information is presented to the user (the view).
Docker:
Start by setting up the files needed to build the app. The app will run inside a Docker container containing its dependencies. Defining dependencies is done using a file called Dockerfile
. To begin with, the Dockerfile consists of:
That’ll put your application code inside an image that builds a container with Ruby, Bundler and all your dependencies inside it.
Next, open the editor and create a Gemfile and Gemfile.lock. Keep the lock file empty and specify the rails version in the Gemfile. (ex: 5 or 6)
Next, provide an entrypoint script to fix a Rails-specific issue that prevents the server from restarting when a certain server.pid
file pre-exists. This script will be executed every time the container gets started. entrypoint.sh
consists of:
Now you should be able to implement a docker-compose file. This file describes the services that comprise your app (a database and a web app), how to get each one’s Docker image (the database just runs on a pre-made PostgreSQL image, and the web app is built from the current directory), and the configuration needed to link them together and expose the web app’s port.
Next, you’ll need to build the project and generate the rails app skeleton as specified within the Gemfile:
Now that you’ve got a new Gemfile, you need to build the image again. (This, and changes to the Gemfile
or the Dockerfile, should be the only times you’ll need to rebuild using the build command.)
The app is now running but you’ll need to configure the database. By default, Rails expects a database to be running on localhost
- so you need to point it at the db
container instead. You also need to change the database and username to align with the defaults set by the postgres
image.
Finally, create the database:
Now, we’re done with docker and docker compose. The next step will be orchestrating and making our application auto-scalable.
Kubernetes: Orchestration
The portability and reproducibility of a containerized process mean we have an opportunity to move and scale our containerized applications across clouds and datacenters. Containers effectively guarantee that those applications run the same way anywhere, allowing us to quickly and easily take advantage of all these environments.
All containers in Kubernetes are scheduled as pods, which are groups of co-located containers that share some resources. Furthermore, in a realistic application we almost never create individual pods; instead, most of our workloads are scheduled as deployments, which are scalable groups of pods maintained automatically by Kubernetes.
In this Kubernetes YAML file, we have two objects, separated by the ---
:
- A
Deployment
, describing a scalable group of identical pods. In this case, you’ll get just onereplica
, or copy of your pod, and that pod (which is described under thetemplate:
key) has just one container in it, based off of yourapppweb
image from the previous step in this tutorial. - A
NodePort
service, which will route traffic from port 30001 on your host to port 8080 inside the pods it routes to, allowing you to reach your bulletin board from the network.
Also, notice that while Kubernetes YAML can appear long and complicated at first, it almost always follows the same pattern:
- The
apiVersion
, which indicates the Kubernetes API that parses this object - The
kind
indicating what sort of object this is - Some
metadata
applying things like names to your objects - The
spec
specifying all the parameters and configurations of your object.
Now, let’s deploy the application:
Kubernetes: Scaling and Auto-scaling
Now let’s scale the application by replicating the database and the server each three times:
Check the pods, if running:
Check deployments:
To make the application more lucid, we can activate auto-scaling. The parameter that we’ll choose will be the CPU usage.
Congratulation! you’ve created a scalable RoR application that is highly available.
Distributed storage: (amazon S3)
The first thing we need to do is to create a Bucket in S3. Then add user policy to grant access to files. Make sure to give the user the necessary permissions to have read/write access to the S3 Bucket. Next, update config/storage.yml
Finally, add aws-sdk-s3 gem to your Gemfile and install the dependency.
Let’s build our app!
Our app will be, mainly, a blog where the user can authenticate and perform CRUD operation on article model. (for styling I do recommend bulma: it’s a nice css library and opensource)
Step 1: Create a Rails app within the container
Step 2: Generate the post model
Step 3: Generate controllers
Step 4: Configure routes
Step 5: Migrate the data
Step 6: Integrate the devise gem to handle authentication
For more information about the implementation, please consult my GitHub repository. I’ve pushed the whole application.
Thank you for your attention!