This is a guest post by Juan Pablo Amaya, a product developer at CodeScrum. Codescrum is a software development company led by Jairo Diaz and focuses on creating user centric digital services and products for clients. This post originally appeared on the CodeScrum blog.
In a past post we shared a Rails starter template (rails-template) with a minimal base for building common rails applications. Now we want to show how to run the rails-template inside a Docker local machine and be able to do simple scale and load balancing.
In a past post we shared a Rails starter template (rails-template) with a minimal base for building common rails applications. Now we want to show how to run the rails-template inside a Docker local machine and be able to do simple scale and load balancing.
The diagram below shows the architecture of the example. Basically we run one container for the Mongo database, one for the redis store and multiple containers for the Rails application and the Workers (Sidekiq). On top of that is the Nginx load balancer container that acts as a reverse proxy for the rails containers.
Docker and Docker Compose
For our experiments with docker we are using docker-machine, that let us to create a Docker host on our machine through a Virtualbox VM. We are not going to talk about how to configure Docker on a local machine, but is fairly explained in the Get started with Docker Machine and a local VM guide from the Docker page.
Docker Compose is a tool that let us to define and run with just one file a set of docker containers. For our rails-template we are going to define three classes of containers (webapp, webserver, database) and linking together. Docker compose also allows us to do simple scaling up or down of any container that we have defined.
Docker images
For each of our container definitions we need some docker images:
- MongoDB database: we are going to use the official mongo 3.0 image from docker-hub.
- Redis store: we use the official redis image from docker-hub.
- Nginx proxy: we are going to use the Automated Nginx reverse proxy for docker containers from jwilder. This allows us to update and reload the nginx proxy to add/remove servers to be load balanced when new containers are started/stopped.
- Rails app: For this we create our own image from the official ruby 2.2.2 image using this Dockerfile:
# Dockerfile FROM ruby:2.2.2 # Install dependencies. RUN apt-get update -qq && apt-get install -y build-essential libpq-dev # Setup app directory. RUN mkdir /myapp WORKDIR /myapp # Copy the Gemfile and Gemfile.lock into the image and install gems before the project is copied to avoid do bundle install every time some project file change. COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install --without development test doc --jobs=4 # Everything up to here was cached. This includes the bundle install, unless the Gemfiles changed. Now copy the app into the image. ADD . /myapp # Expose unicorn port. EXPOSE 8080
- Worker app: For this we are going to use the same image builded from the Rails app.
Docker compose definition
The docker compose file that define the containers is:
# docker-compose.yml webapp: build: . command: bundle exec unicorn -E production -c config/unicorn.rb volumes: - .:/myapp links: - db env_file: .env environment: RACK_ENV: production RAILS_ENV: production VIRTUAL_HOST: rails-template.docker db: image: mongo:3.0 command: mongod --smallfiles --quiet proxy: image: jwilder/nginx-proxy:latest ports: - "80:80" volumes: - "/var/run/docker.sock:/tmp/docker.sock"redis: image: redis worker: build: . command: bundle exec sidekiq -e production -c 5 env_file: .env environment: RAILS_ENV: production links: - db - redis
The docker-compose file defines a webapp service that use the image from theDockerfile of the project. This runs the application through unicorn, and links to the db service. This also sets some environment variables needed by the app, between them one important is the VIRTUAL_HOST environment variable, this is the trick that tells to the proxy service that this container wants to be proxyfied. We use the rails-template.docker as the virtual server name, so we need to add it to the hosts file:
# /etc/hosts . . . {docker-machine ip} rails-template.docker
The {docker-machine ip} value is taken from the command:
docker-machine ip boot2docker
The db and redis services are self described.
In the proxy service the only thing to emphasize is that the 80 port is exposed to the host machine to allows to check the application in our local machine
The worker service links to the db and redis services.
Running the containers
To be able to run the rails-template in Docker we need to first build or pull the images, we do this with:
docker-compose build
It is going to take some time while it download the images from docker-hub and build our rail image.
Now we can run the containers with the command:
docker-compose up -d
The -d option is to run all containers in the background.
We can check the app in the browser at http://rails-template.docker
The Rails application also is configured to run a sidekiq async process that prints the current time every time a request to the app is made, we can see that working with:
docker-compose logs worker
Scaling the application
Now that we have the application running we can scale the webapp service to 3 replicas and the worker up 2 replicas. We do this with the command:
docker-compose scale webapp=3 docker-compose scale worker=2
Now there are two more webapp containers running in the docker machine and be automatically load balanced by the proxy container. We can check this making many requests and by opening the webapp logs:
while true; do curl http://rails-template.docker; echo -----; sleep 1; done;
In other console
docker-compose logs webapp docker-compose logs worker
We would be able to see how the requests are taken by each of the webapp containers and fire a worker background process.
The example code could be found in this repository:http://github.com/codescrum/rails-template-docker-compose-example-1