Clustering a Node.js application with Mongo, Docker and Rancher

[Sletschatince the availability of Rancher’s Beta release a few weeks ago, I’ve been pretty excited about the new scheduling and service discovery capabilities in the platform. To help people understand the impact of these capabilities, today I’m going to show how to use these features to deploy a fully clustered and HA implementation of a Node.js application. I’m going to use ][Let’s Chat as our example application, It is an excellent ][open-source, Slack-like team chat application. Rancher Chief Architect Darren Shepherd demonstrated Let’s Chat a bit in the last Rancher Meetup, which you can watch a recordingof if you like, but today I’m going to walk through in detail how to deploy it at scale.] [Let’s Chat is a self-hosted chat application that can be used with small teams, it support multiple features including discussion rooms, notifications and alarms, XMPP chat, and multiple authentication methods. You can get loads more more information about Let’s Chat on their ][repository on Github][.]

Application Components

[Let’s Chat requires the following components to run smoothly:]

  • Node.js (0.10+)[ to run the application code.]
  • MongoDB (2.6+) [as the main data store for the application.]
  • Python (2.7.x)[ which is required by some tools used in the application.]

[Luckily, Let’s Chat has its own ready to use Docker image (][sdelements/lets-chat][), which installs all the dependencies needed to run the application and defines the environment variable options for the application, such as : ]LCB_DATABASE_URI[ which defines the address for a single or multiple MongoDB servers.] [Deploying a single Let’s Chat app with a Mongo backend is incredibly easy, and there is an excellent Docker compose file already available to do this. However, my goal was to build a clustered and highly available setup, that could handle host failure, so I will run several MongoDB containers and set up a replica set to replicate the data between those containers, the replica set has the ability to elect a new primary server when the master goes down.] [Let’s Chat uses the ][mongoose][ driver to connect to the MongoDB servers, which can be configured to attach to a replica set and send its data to the new primary MongoDB server.]

Rancher Environment

[If you don’t already have a Rancher environment, it is easy to set one up. In total, I’m going to use 4 machines for this deployment: one to to run my Rancher management container, and the other three to host the application containers.] [Rancher’s Management server can be installed on any Linux machine that has Docker 1.6 or later running on it. Simply run the following command:]

# docker run -d --restart=always -p 8080:8080 rancher/server

[The Rancher server will take a few minutes to start and will be available on port 8080 of the host you’re using to deploy it. Once Rancher is up and running, add three new hosts to your environment by clicking the”Add Host” button, and using either the Docker Machine drivers to deploy hosts on one of the supported clouds, or click on \“custom\” to add any host to your environment.] letschat2 [To do this, copy and paste the command on your two machines that will be registered with Rancher. As you can see, this command contains the Rancher server’s information and the registration key, and will automatically register the host with your Rancher environment.] [Alternatively, you can create your Rancher environment with any automation platform. I’ve created simple Ansible roles which I will use to install Docker on all machines and run rancher management server as well as register the other hosts with Rancher platform, the roles are:]

  • [Docker Role:][ which will install and configure Docker on Ubuntu 14.04 machines.]
  • [Rancher Role:][ which will install and start Rancher’s management server.]
  • [Rancher Agent:][ which will run the Rancher agent container on hosts and register them with Rancher.]

[You can then use these roles in a playbook to install and register machines with Rancher’s platform, regardless, at the end you should have an environment running with three hosts.]

Building the Application Services

[Once we have our resources available, it is time to start deploying our app. At the top of the Rancher UI, click on applications, and select the icon to “add stack” A Stack is a collection of related services. A “service” is a deployment of a Docker container image that can be scheduled to run on one or several hosts.] [Each stack defines the scope of service discovery used by the application when linking different services, for example later we will are going to link the Let’s Chat application service with the Mongo database service, within our new stack.] [You can read more about services and its option from ][Rancher’s official documentation][. ]The setup will consists of two main services:

  • [MongoDB service.]
  • [Application service.]

MongoDB service

[I’ve modified the standard Let’s Chat MongoDB image to make any new container able to connect to the replica set automatically, to achieve that I added a small python script to the script which is responsible to bring up the MongoDB server in the original image, you can find the new image on Github.] [The script will first detect the IP of the running MongoDB container and then tries to connect to the primary MongoDB server and add itself to the existing replica set, if it finds that less than three MongoDB servers exist it will not initiate a connection because it means that no replication has been started.] [The Image expects one environment variable to be set as ]MONGO_SERVICE_NAME [which is set to “mongo” by default and its purpose is to use this name to lookup the IPs of the started MongoDB containers, this is provided by the DNS service in Rancher’s platform where all the container’s IPs will be added to the DNS service under the same service name.] [First we will start with the MongoDB service, click on “Add New Service” to be redirected to the configuration page:] letschat3 [Note that the ]MongoDB[ service started with --replSet command to instruct the server that he is a part of a replica set called “letschat”, you can also specify a volume to ensure that the data is safe on the host:] letschat4 [We want to run one instance of our Mongo container on each host, so under scale, select “]Always run one instance of this container on every host” [which allows to run the service’s containers on each machine to ensure scalability.] Now start the service and you will see that it started a container on each registered host: letschat5

Application service

[Now let’s create an application service that will run the Let’s Chat container on each host. I was hoping to use the standard Docker image and connect it to the distributed Mongo backend, but this was the problem I needed to address to make the system scale. ] Problem [The first problem is that when the application service linked with the mongo service with link name “mongo” for example it will make the let’s chat application connect to only one MongoDB server which may not be the primary MongoDB server, so in some cases it will not be able to write data on a secondary MongoDB server.] Solution [To solve this problem I tweaked the let’s chat Docker container to run a ]python[ script that will communicate with ]Rancher’s DNS[ service] [and find all MongoDB containers, then modify the ]LCB_DATABASE_URI [environment variable which is one of the options of Let’s Chat to add seed list of the ips of the MongoDB container, so now the mongoose driver will know that it will reroute the request to the primary server, for example the LCB_DATABASE_URI will be:]


[Instead of:]


You have to provide 2 environment variables to the script: MONGO_SERVICE_NAME to search for the mongo service name and fetch all relevant container’s ips and RANCHER_CLUSTER which can be not used if you don’t want this modification on the original Docker image. So to start the application service, it will be configured like the following: letschat6 [Note that I added mongo as a service links because once a service is linked to another service, a DNS record mapped to each container instance is automatically created and discoverable by containers from the other service.] [And the Environment variables will something like this:] letschat7 [To get the full view on how the services created, you can see the automatically generated docker-compose.yml and rancher-compose.yml files:] docker-compose.yml: letschat8 rancher_compose.yml: letschat9


Starting The Application

[The first service that we will start is the MongoDB service, after its starting we need to configure the initial setup for the replication between MongoDB servers:] letschat10 [Attach to one of the mongo servers, and run the following command to initialize the application:]

> config = {
    "_id" : "letschat",
    "members" : [
      {"_id" : 0, "host" : "<private-ip-of-the-1st-container>:27017"},
      {"_id" : 1, "host" : "<private-ip-of-the-2nd-container>:27017"},
      {"_id" : 2, "host" : "<private-ip-of-the-3rd-container>:27017"}
   ] }
> rs.initiate(config)

[This step will not be need every time we create or remove a host to scale the application up and down, instead the modification to the MongoDB image will take care of this. ][After starting the replica set, we can start the rest of the services, you should see something like this:] letschat11 [Now if you take a look on the log files for the “]letschat[” container on all servers you will find that in each container:]

lets-chat@0.4.2 prestart /usr/src/app
> lets-chat@0.4.2 start /usr/src/app
> node app.js
██╗     ███████╗████████╗███████╗     ██████╗██╗  ██╗ █████╗ ████████╗
██║     ██╔════╝╚══██╔══╝██╔════╝    ██╔════╝██║  ██║██╔══██╗╚══██╔══╝
██║     █████╗     ██║   ███████╗    ██║     ███████║███████║   ██║
██║     ██╔══╝     ██║   ╚════██║    ██║     ██╔══██║██╔══██║   ██║
███████╗███████╗   ██║   ███████║    ╚██████╗██║  ██║██║  ██║   ██║
══════╝╚══════╝   ╚═╝   ╚══════╝     ╚═════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝
Release [33m0.4.2[39m

[Which indicates that the modification to the letschat Docker image has been able to detect all the MongoDB containers that has been started under the service name “]mongo[“.][ ]

Adding Load Balancer

[Another great feature of Rancher is the ability to add load balancer for different services on your stack, in our case I will add a load balancer to bounce requests between the application containers, for more information about Load Balancers in Rancher’s platform, please refer to the ][documentation][.] [First from your stack click on “Add Load Balancer”, the following options will make the load balancer to distribute requests in round robin fashion but with a session stickiness:] letschat12 [As you can see the load balancer linked to the letschat service we created earlier, and will distribute request in round robin fashion on all the containers under this service, the following is the stickiness options that should be added to ensure that the request will be routed to the same server for the same session, the application uses a cookie with name ]connect.sid: letschat13 [After creating the load balancer you can start using the application, by accessing the address of the load balancer:] letschat14

Test the scalability

[We need to make sure that the application is scalable so, after creating a new machine, register it with Rancher platform to check the configured containers will start automatically on the server and will be add to the cluster:] letschat15letschat16 [This means that a new host was added to the pool and the containers automatically started on this new host, also if you check the logs for new mongodb container, it will show that it added the container to the replica set:]

2015-07-27T02:09:12.139+0000 I REPL     [WriteReplSetConfig] Starting replication applier threads
2015-07-27T02:09:12.141+0000 I REPL     [ReplicationExecutor] New replica set config in use: { _id: "letschat", version: 6, members: [ { _id: 0, host: "", arbiterOnly: false, ……...

I hope this helps you understand how to use Rancher scheduling with mongo replicasets. Letschat is a great application for learning how to deploy a Dockerized application with fault tolerance. If you’re interested in getting your hands dirty with Rancher, please download the software from github, join the Betato get additional support, visit our forumswith any questions, or register for our next monthly online meetup to ask questions and meet our developers. Hussein Galal is a Linux System Administrator, with experience in Linux, Unix, Networking, and open source technologies like Nginx, Apache, PHP-FPM, Passenger, MySQL, LXC, and Docker. You can follow Hussein on Twitter @galal_hussein.