mongodbdockerdocker-composereplicaset

MongoDB docker replica set connection error "Host not found"


I have a local MongoDB replica set created following this SO answer.

The docker-compose file:

services:
  mongo1:
    container_name: mongo1
    image: mongo:4.2
    ports:
      - 27017:27017
    restart: always
    command: ["--bind_ip_all", "--replSet", "rs" ]
  mongo2:
    container_name: mongo2
    image: mongo:4.2
    ports:
      - 27018:27017
    restart: always
    command: ["--bind_ip_all", "--replSet", "rs" ]
  mongo3:
    container_name: mongo3
    image: mongo:4.2
    ports:
      - 27019:27017
    restart: always
    command: ["--bind_ip_all", "--replSet", "rs" ]
  replica_set:
    image: mongo:4.2
    container_name: replica_set
    depends_on:
      - mongo1
      - mongo2
      - mongo3
    volume:
      - ./initiate_replica_set.sh:/initiate_replica_set.sh
    entrypoint: 
      - /initiate_replica_set.sh

The initiate_replica_set.sh file:

#!/bin/bash

echo "Starting replica set initialize"
until mongo --host mongo1 --eval "print(\"waited for connection\")"
do
    sleep 2
done
echo "Connection finished"
echo "Creating replica set"
mongo --host mongo1 <<EOF
rs.initiate(
  {
    _id : 'rs0',
    members: [
      { _id : 0, host : "mongo1:27017" },
      { _id : 1, host : "mongo2:27017" },
      { _id : 2, host : "mongo3:27017" }
    ]
  }
)
EOF
echo "replica set created"

The replica set is brought up successfully and runs fine, but it errors when I try to connect to the replica set:

$ mongo "mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs"
MongoDB shell version v5.0.2
connecting to: mongodb://localhost:27017,localhost:27018,localhost:27019/?compressors=disabled&gssapiServiceName=mongodb&replicaSet=rs
{"t":{"$date":"2021-08-05T21:35:40.667Z"},"s":"I",  "c":"NETWORK",  "id":4333208, "ctx":"ReplicaSetMonitor-TaskExecutor","msg":"RSM host selection timeout","attr":{"replicaSet":"rs","error":"FailedToSatisfyReadPreference: Could not find host matching read preference { mode: \"nearest\" } for set rs"}}
Error: Could not find host matching read preference { mode: "nearest" } for set rs, rs/localhost:27017,localhost:27018,localhost:27019 :
connect@src/mongo/shell/mongo.js:372:17
@(connect):2:6
exception: connect failed
exiting with code 1

More verbose log:

{
  "t": {
    "$date": "2021-08-05T21:35:54.531Z"
  },
  "s": "I",
  "c": "-",
  "id": 4333222,
  "ctx": "ReplicaSetMonitor-TaskExecutor",
  "msg": "RSM received error response",
  "attr": {
    "host": "mongo1:27017",
    "error": "HostUnreachable: Error connecting to mongo1:27017 :: caused by :: Could not find address for mongo1:27017: SocketException: Host not found (authoritative)",
    "replicaSet": "rs",
    "response": "{}"
  }
}

What is the cause of the problem and how do I fix it?


Solution

  • There are some partial answers on this issue from various places, here is what I think as a complete answer.

    The Cause

    The Fix

    Because the problem involves docker networking and docker networking varies between Linux and Mac. The fixes are different on the two platforms.

    Linux

    The proxy fix (via 3rd party software or modifying /etc/hosts file) works fine but sometimes is not viable, e.g., running on remote CI hosts. A simple self-contained portable solution is to update the intiate_replia_set.sh script to initiate the replica set with member IPs instead of hostnames.

    intiate_replia_set.sh

    echo "Starting replica set initialization"
    until mongo --host mongo1 --eval "print(\"waited for connection\")"
    do
       sleep 2
    done
    echo "Connection finished"
    echo "Creating replica set"
    
    MONGO1IP=$(getent hosts mongo1 | awk '{ print $1 }')
    MONGO2IP=$(getent hosts mongo2 | awk '{ print $1 }')
    MONGO3IP=$(getent hosts mongo3 | awk '{ print $1 }')
    
    read -r -d '' CMD <<EOF
    rs.initiate(
      {
        _id : 'rs',
        members: [
          { _id : 0, host : '${MONGO1IP}:27017' },
          { _id : 1, host : '${MONGO2IP}:27017' },
          { _id : 2, host : '${MONGO3IP}:27017' }
        ]
      }
    )
    EOF
    
    echo $CMD | mongo --host mongo1
    echo "replica set created"
    

    This way the mongo replica set members have container IP instead of hostname in their addresses. And the container IP is reachable from the host.

    Alternatively, we can assign static IP to each container explicitly in the docker-compose file, and use static IPs when initiating the replica set. It is a similar fix but with more work.

    Mac

    The above solution unfortunately does not work for Mac, because docker container IP on Mac is not exposed on the host network interface. https://docs.docker.com/docker-for-mac/networking/#per-container-ip-addressing-is-not-possible

    The easiest way to make it work is to add following mapping in /etc/hosts file:

    127.0.0.1   mongo1 mongo2 mongo3