Today, what I want to cover is setting up a MongoDB replica set on Docker. What will make this post a little bit different than most of the documentation for setting up MongoDB on Docker is that I will try to emulate a setup that is closer to what you will find in a production environment. Once we're complete with setting this up you will have a MongoDB Replica Set that has authentication enabled and uses a key as well. In my production environments, I would use certificates, however for development purposes, the key file will certainly suffice.

At the end of this post, if you happen to be following along, you should have a similar setup as per the below diagram.

+-Docker-Host---------------------------------+
|                                             |
|  +----+--+Docker-MongoDB-Network+---+----+  |
|       |             |               |       |
|  +----v------+ +----v------+ +------v----+  |
|  |  MongoDB  | |  MongoDB  | |  MongoDB  |  |
|  +-----^-----+ +-----^-----+ +-----^-----+  |
|        |             |             |        |
+------37017---------37018---------37019------+
      PRIMARY      SECONDARY     SECONDARY

Pre-requisites

  • Docker version 1.12.1 >
  • Docker Mongo Image
Assumptions

While this is more around MongoDB and Docker, I will try to not make any assumptions and start from the top. You should, however, have Docker already installed, but if you don't, here is the one-liner from their site.

curl -sSL https://get.docker.com/ | sh  

Setup

Now that you have Docker installed, let's start by pulling down the latest MongoDB Docker Image by simply running the following command.

docker pull mongo  

Next, let's verify we have the image pulled down and available to us for use.

docker images | grep mongo  

You should see the following output, however, the time CREATED time will be different depending on when you execute the command to view the image.

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE  
mongo               latest              83b0cb5c5a67        1 minute ago        326.7 MB  

Creating our Network

Creating a network for our containers to live on is more like what you may find in a production Mongo deployment. Typically, you will have a database network that is segregated from front-end traffic. This can be done a variety of ways but in this instance, we will go with a simple bridge network. In future posts, I will show how to utilize other network types.

To create our new network for our containers to live on, we will issue the following command:

docker network create mongodb-rs-local  

Next, we will check that our network was created.

docker network ls -fname=mongodb-rs-local  
# Output
NETWORK ID          NAME                DRIVER              SCOPE  
f91c765e4686        mongodb-rs-local    bridge              local  

We can also check with brctl. You can see under the interfaces header that we have no containers bound to this network. I use brctl here as I find the output very clean for a quick at-a-glance look. However, Docker also provides us a method of viewing this information in much more detail, so I will show both methods below.

brctl show  
# Output
bridge name    bridge id       STP enabled interfaces  
br-f91c765e4686    8000.02424e7f33d7   no  
docker network inspect mongodb-rs-local  
# Output
[
    {
        "Name": "mongodb-rs-local",
        "Id": "f91c765e46860b3e5bfe078419f829aea29062773e5bd7c1325dd818813236a8",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {
        },
        "Options": {},
        "Labels": {}
    }
]

Creating our Keyfile

Now that we have our network created and ready for containers to be connected to, we will create the key file which our MongoDB replica set will use for internal authentication. To set this up is a trivial process but the permissions are really what is important here.

We need to know what the UID/GID are of the MongoDB user running inside the container. So we will run a quick command against a throwaway container to get the UID/GID of the MongoDB user.

docker run -t mongo grep mongodb /etc/passwd  
# Output
mongodb:x:999:999::/home/mongodb:/bin/sh  

Take note of the UID/GID which is 999, we will need to give the key this ownership so that MongoDB can access the key for internal authentication. We will also need to set the key to RO(ReadOnly)400 for the owner. The path to the key is important as we need to mount the key. Alternatively, you can mount the entire directory. In our example, we will be mounting the key file only.

Create the Key

openssl rand -base64 755 > <path-to-keyfile>  
chmod 400 <path-to-keyfile>  
chown 999:999 <path-to-keyfile>  

Start our Containers

Our key file is created and ready, our network is created and ready. Let's start up the containers. First, let's examine the command we will use to start each container so you have a better idea

The below command we will use to start our containers. Keep in mind that the command is the same for all three containers except the port and the name, which we will increment accordingly.

docker run  
-p 37017:27017 
-v <full-path-to-keyfile-src-on-host-on-host>:<full-path-to-keyfile-dst-on-container> 
--name mongo-local-001 
--net mongodb-rs-local 
-d 
mongo --auth --replSet rslocal01 --keyFile <full-path-to-keyfile-dst-on-container>  
  • docker run - Runs a command in a new container
  • -p 37017:27017 - Publish a container's port(s) to the host. In this case host:37017 -> container:27017
  • -v full-path-to-keyfile-src-on-host:full-path-to-keyfile-dst-on-container - Bind mount a volume. In this case we're binding the keyfile.
  • --name mongo-local-001 - Assign a name to the container
  • --net mongodb-rs-local - Connect a container to a network. In this case the network we previously created.
  • -d - Run container in background and print container ID
  • mongo --auth --replSet rslocal01 --keyFile full-path-to-keyfile-dst-on-container - The starting command: mongo with auth, a replica set and keyfile

Starting the Containers

Notice the port and name has been incremented as stated.

docker run -p 37017:27017 -v <full-path-to-keyfile-src-on-host>:<full-path-to-keyfile-dst-on-container> --name mongo-local-001 --net mongodb-rs-local -d mongo --auth --replSet rslocal01 --keyFile <full-path-to-keyfile-dst>

docker run -p 37018:27017 -v <full-path-to-keyfile-src-on-host>:<full-path-to-keyfile-dst-on-container> --name mongo-local-002 --net mongodb-rs-local -d mongo --auth --replSet rslocal01 --keyFile <full-path-to-keyfile-dst>

docker run -p 37019:27017 -v <full-path-to-keyfile-src-on-host>:<full-path-to-keyfile-dst-on-container> --name mongo-local-003 --net mongodb-rs-local -d mongo --auth --replSet rslocal01 --keyFile <full-path-to-keyfile-dst>  

Verify the Containers

This step is not needed, however I like to perform this step as a checks and balances measure to make sure everything is as it should be. So first, let's check to see if the containers are started.

docker ps  
# Output
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                      NAMES  
1b3f8ecc1fd3        mongo               "/entrypoint.sh --aut"   2 minutes ago       Up 2 minutes       0.0.0.0:37019->27017/tcp   mongo-local-003  
506d20720dda        mongo               "/entrypoint.sh --aut"   2 minutes ago       Up 2 minutes       0.0.0.0:37018->27017/tcp   mongo-local-002  
57409f00d332        mongo               "/entrypoint.sh --aut"   2 minutes ago       Up 2 minutes       0.0.0.0:37017->27017/tcp   mongo-local-001  

We can also verify they are bound to our network

brctl show br-f91c765e4686  
bridge name    bridge id       STP enabled interfaces  
br-f91c765e4686    8000.02424e7f33d7   no      veth2130dea  
                            veth4ee24f4
                            vethf0a3285

Alternatively, you can use the docker network inspect

docker network inspect mongo-rs-local  
[
    {
        "Name": "mongo-rs-local",
        "Id": "f91c765e46860b3e5bfe078419f829aea29062773e5bd7c1325dd818813236a8",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "1b3f8ecc1fd3dc278d2c6d042ab2b31026f248ab77fc754007ef805def0e9a72": {
                "Name": "mongo-local-003",
                "EndpointID": "1722b055adb4d87b08f5e0673a96f3e8e970aaf728e0d8905515d9a164717042",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": ""
            },
            "506d20720dda532fe0336028842fa96a5441c1380b55b817ce16078d494dce05": {
                "Name": "mongo-local-002",
                "EndpointID": "e91baad1d629b9533c0e76fefaa9ff91b97e29a11c10cb1720f95ae075412494",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "57409f00d332840f9158ce69fea1577ee4f5ddcdcb6a074539f28c1b4642fa65": {
                "Name": "mongo-local-001",
                "EndpointID": "a8ccdda4d2a5ed252a2cfab201c49a6bc7f861130db6d724123c73a0e6c5f276",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

Excellent, the containers are started and are connected to the network we created. They are now ready for configuration.

Configuring our Containers

We have auth enabled, but before we create a user, we must first configure our replica set.

Connect to Mongo

You can connect to any of the containers to perform the following, however, I am choosing to connect to the first instance. Here, I am using docker exec, however, you could also use mongo shell directly if you have that installed as we have ports exposed.

docker exec -it mongo-local-001 mongo  
MongoDB shell version: 3.2.8  
connecting to: test  
Welcome to the MongoDB shell.  
For interactive help, type "help".  
For more comprehensive documentation, see  
    http://docs.mongodb.org/
Questions? Try the support group  
    http://groups.google.com/group/mongodb-user
>
Initiate the Replica Set

Now, we need to initiate the replica set. Doing so is quite simple for our standard replica set. There are more options and I will cover some in other posts, but for now the following configuration can be passed to rs.initiate();

> rs.initiate({
      "_id" : "rslocal01",
      "members" : [
          {
              "_id" : 0,
              "host" : "mongo-local-001:27017"
          },
          {
              "_id" : 1,
              "host" : "mongo-local-002:27017"
          },
          {
              "_id" : 2,
              "host" : "mongo-local-003:27017"
          }
      ]
  });
Add an Admin User

Finally, we can add a user to authenticate with. Here I will simply create a root user, however, you can create any user you need to at this point.

> use admin;
> db.createUser({ user: "root", pwd: "password", roles: ["root"] });
> db.auth("root", "password");

I hope this proves to be useful to some of you out there.

Until next time. Keep it simple. - ShellFu