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?
There are some partial answers on this issue from various places, here is what I think as a complete answer.
The Cause
Mongo clients use the hostnames listed in the replica set config, not the seed list
Although the connection string is "mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs", mongo client does not connect to the members of the replica set with seed addresses localhost:27017 etc, instead the client connects to the members in the replica config set returned from the seed hosts, i.e., the ones in the rs.initiate call. This is why the error message is Error connecting to mongo1:27017 instead of Error connecting to localhost:27017.
Container hostnames are not addressable outside the container network
A mongo client inside the same container network as the mongo server containers can connect to the server via addresses like mongo1:27017; however, a client on the host, which is outside of the container network, can not resolve mongo1 to an IP. The typical solution for this problem is proxy, see Access docker container from host using containers name for details.
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
I think it is enough to change the name of the replicaSet "rs" to "rs0", this is the name given to your replicaSet in the configuration.
You will have :
$ mongo "mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0"
Related
I am trying to connect to a MongoDB replica set using pymongo, but I keep getting the error: pymongo.errors.ServerSelectionTimeoutError: No replica set members match selector. In the error message it's also specified that my topology type is ReplicaSetNoPrimary, which is odd, as connecting with mongo bash shows a clear primary.
Note that the replica set works fine and is usable via mongo bash on the master node.
Also, I have added firewall rules to allow both inbound and outbound traffic on the specified ports, just to make sure this isn't the issue.
I am using docker-compose for the cluster. The file:
version: "3.9"
services:
mongo-master:
image: mongo:latest
container_name: mongo_master
volumes:
- ./data/master:/data/db
ports:
- 27017:27017
command: mongod --replSet dbrs & mongo --eval rs.initiate(`cat rs_config.json`)
stdin_open: true
tty: true
mongo-slave-1:
image: mongo:latest
container_name: mongo_slave_1
volumes:
- ./data/slave_1:/data/db
ports:
- 27018:27017
command: mongod --replSet dbrs
stdin_open: true
tty: true
mongo-slave-2:
image: mongo:latest
container_name: mongo_slave_2
volumes:
- ./data/slave_2:/data/db
ports:
- 27019:27017
command: mongod --replSet dbrs
stdin_open: true
tty: true
The rs_config.json file used above:
{
"_id" : "dbrs",
"members" : [
{
"_id" : 0,
"host" : "mongo_master:27017",
"priority" : 10
},
{
"_id" : 1,
"host" : "mongo_slave_1:27017"
},
{
"_id" : 2,
"host" : "mongo_slave_2:27017"
}
]
}
The error raises on the last line here:
self.__client = MongoClient(["localhost:27017", "localhost:27018", "localhost:27019"], replicaset="dbrs")
self.__collection = self.__client[self.__db_name][collection.value]
self.__collection.insert_one(dictionary_object)
I ommitted some code for brevity, but you can assume all class attributes and dictionary_object are well defined according to pymongo docs.
Also please note that I have tried many different ways to initialize MongoClient, including a connection string (as in the docs), and the connect=False optional parameter as advised in some blogs. The issue persists...
Edit: I tried adding "mongo_master" to my etc/hosts file pointing at 127.0.0.1 and changing the connection string from localhost to that, and it works with the replica set. This is a bad workaround but maybe can help in figuring out a solution.
Thanks in advance for any help!
To get a connection to a MongoDB replicaset from an external client, you must be able to resolve the hostnames from the local client.
https://docs.mongodb.com/manual/tutorial/deploy-replica-set/#connectivity
Ensure that network traffic can pass securely between all members of the set and all clients in the network.
So, add the following to your /etc/hosts file:
127.0.0.1 mongodb-1
127.0.0.1 mongodb-2
127.0.0.1 mongodb-3
To be able to connect both internally and externally, you will need to run each MongoDB service on different ports.
The following script will initiate a 3-node MongoDB replicaset and run a test client. I recommend using the Bitnami image as it takes care of the replset initiation for you. (Borrowing heavily from this configuration)
#!/bin/bash
PROJECT_NAME=replset_test
MONGODB_VERSION=4.4
PYTHON_VERSION=3.9.6
PYMONGO_VERSION=4.0.1
cd "$(mktemp -d)" || exit
cat << EOF > Dockerfile
FROM python:${PYTHON_VERSION}-slim-buster
COPY requirements.txt /tmp/
RUN pip install -r /tmp/requirements.txt
COPY ${PROJECT_NAME}.py .
CMD [ "python", "./${PROJECT_NAME}.py" ]
EOF
cat << EOF > requirements.txt
pymongo==${PYMONGO_VERSION}
EOF
cat << EOF > ${PROJECT_NAME}.py
from pymongo import MongoClient
connection_string = 'mongodb://root:password123#mongodb-1:27017,mongodb-2:27018,mongodb-3:27019/mydatabase?authSource=admin&replicaSet=replicaset'
client = MongoClient(connection_string)
db = client.db
db['mycollection'].insert_one({'a': 1})
record = db['mycollection'].find_one()
if record is not None:
print(f'{__file__}: MongoDB connection working using connection string "{connection_string}"')
EOF
cp ${PROJECT_NAME}.py ${PROJECT_NAME}_external.py
cat << EOF > docker-compose.yaml
version: '3.9'
services:
mongodb-1:
image: docker.io/bitnami/mongodb:${MONGODB_VERSION}
ports:
- 27017:27017
environment:
- MONGODB_ADVERTISED_HOSTNAME=mongodb-1
- MONGODB_PORT_NUMBER=27017
- MONGODB_REPLICA_SET_MODE=primary
- MONGODB_ROOT_PASSWORD=password123
- MONGODB_REPLICA_SET_KEY=replicasetkey123
volumes:
- 'mongodb_master_data:/bitnami/mongodb'
mongodb-2:
image: docker.io/bitnami/mongodb:${MONGODB_VERSION}
ports:
- 27018:27018
depends_on:
- mongodb-1
environment:
- MONGODB_ADVERTISED_HOSTNAME=mongodb-2
- MONGODB_PORT_NUMBER=27018
- MONGODB_REPLICA_SET_MODE=secondary
- MONGODB_INITIAL_PRIMARY_HOST=mongodb-primary
- MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD=password123
- MONGODB_REPLICA_SET_KEY=replicasetkey123
mongodb-3:
image: docker.io/bitnami/mongodb:${MONGODB_VERSION}
ports:
- 27019:27019
depends_on:
- mongodb-1
environment:
- MONGODB_ADVERTISED_HOSTNAME=mongodb-3
- MONGODB_PORT_NUMBER=27019
- MONGODB_REPLICA_SET_MODE=secondary
- MONGODB_INITIAL_PRIMARY_HOST=mongodb-primary
- MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD=password123
- MONGODB_REPLICA_SET_KEY=replicasetkey123
${PROJECT_NAME}:
container_name: ${PROJECT_NAME}
build: .
depends_on:
- mongodb-1
- mongodb-2
- mongodb-3
volumes:
mongodb_master_data:
driver: local
EOF
docker rm --force $(docker ps -a -q --filter name=mongo) 2>&1 > /dev/null
docker rm --force $(docker ps -a -q --filter name=${PROJECT_NAME}) 2>&1 > /dev/null
docker-compose up --build -d
python ${PROJECT_NAME}.py
docker ps -a -q --filter name=${PROJECT_NAME}
docker logs $(docker ps -a -q --filter name=${PROJECT_NAME})
If all is ok you will get an output confirming both internal and external connectivity:
/tmp/tmp.QM9tQPE8Dj/replset_test.py: MongoDB connection working using connection string "mongodb://root:password123#mongodb-1:27017,mongodb-2:27018,mongodb-3:27019/mydatabase?authSource=admin&replicaSet=replicaset"
d53e8c41ad20
//./replset_test.py: MongoDB connection working using connection string "mongodb://root:password123#mongodb-1:27017,mongodb-2:27018,mongodb-3:27019/mydatabase?authSource=admin&replicaSet=replicaset"
I have been trying all day to setup a replica set for MongoDB transactions automated by Docker Compose, but no success so far. Keep encountering the error where mongo1 (connected from mongo-setup) can't connect to mongo2 and mongo3 containers, saying connection refused.
The error message I got:
{
"ok" : 0,
"errmsg" : "replSetInitiate quorum check failed because not all proposed set members responded affirmatively: mongo2:27018 failed with Error connecting to mongo2:27018 (172.30.0.5:27018) :: caused by :: Connection refused, mongo3:27019 failed with Error connecting to mongo3:27019 (172.30.0.4:27019) :: caused by :: Connection refused",
"code" : 74,
"codeName" : "NodeNotFound"
}
uncaught exception: ReferenceError: EOF is not defined :
#(shell):1:1
uncaught exception: SyntaxError: unexpected token: string literal :
#(shell):1:5
My Docker Compose:
mongo3:
hostname: mongo3
image: mongo:5.0.5-focal
networks:
- shopnetwork
volumes:
- ./data/mongo3:/data/db
ports:
- 27019:27019
restart: always
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "shop-mongo-set" ]
mongo2:
hostname: mongo2
image: mongo:5.0.5-focal
networks:
- shopnetwork
volumes:
- ./data/mongo2:/data/db
ports:
- 27018:27018
restart: always
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "shop-mongo-set" ]
mongo1:
hostname: mongo1
image: mongo:5.0.5-focal
networks:
- shopnetwork
volumes:
- ./data/mongo1:/data/db
ports:
- 27017:27017
depends_on:
- mongo2
- mongo3
restart: always
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "shop-mongo-set" ]
mongo-setup:
image: mongo:5.0.5-focal
networks:
- shopnetwork
volumes:
- ./script:/script
depends_on:
- mongo1
- mongo2
- mongo3
entrypoint: ["/script/setup.sh"]
My setup.sh to initialize replica set:
#!/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
config = {
"_id" : "shop-mongo-set",
"members" : [
{
"_id" : 0,
"host" : "mongo1:27017"
},
{
"_id" : 1,
"host" : "mongo2:27018"
},
{
"_id" : 2,
"host" : "mongo3:27019"
}
]
}
rs.initiate(config)
EOF
echo "Replica set created"
so as tested with the OP there was a mixup with the network configuration in compose and host network.
when using multiple services in docker-compose each service is a different host in the compose network, so for example if we have multiple services using the same port that won't be an issue because each has a different hostname and IP.
when mapping ports from docker network to host network using bridge adapter usually only one service can use each port and better to use ports >1024 for security and compatibility reasons.
when wanting to connect to different services in the same compose file but no external connection from WAN into the compose network is needed the ports section of the compose file or the -p option in the docker run command isn't needed
after fixing said miss-configuration there shouldn't be any problems around that topic
let's say we have the following setup with no authentication...
docker-compose.yaml
services:
db:
image: mongo:4.4.1
volumes:
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js
command: --bind_ip_all --replSet RepSet
ports:
- 27017:27017
mongo-init.js
rs.initiate(
{
_id: "RepSet",
version: 1,
members: [
{ _id: 0, host: "localhost:27017" }
]
})
the above works and the replication is setup properly in single node replicaset mode but...
how does one enable mongo authentication with the above setup?
if i add the following environment variables to the db service, then the rs.initiate() from the mongo-init.js doesn't work. it complains that the server was started without replication.
db:
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password
apparently the mongo docker image entrypoint script strips out the --replSet argument during the initial setup if authentication credentials are used.
does anyone know a 'not so hacky' workaround to get this working?
cheers!
I want to setup MongoDB replica set with docker-compose with only one node. This is my docker-compose.yml file:
version: '3.7'
services:
mongodb1:
image: mongo:latest
command: mongod --replSet rs0
ports:
- 27017:27017
volumes:
- ./mongodb1/data:/data/db
networks:
- mongo-dev-net
setup-rs:
image: mongo:latest
command: mongo mongodb://mongodb1:27017 --eval "rs.initiate();"
depends_on:
- mongodb1
networks:
- mongo-dev-net
networks:
mongo-dev-net:
driver: bridge
It gives me an error while trying to run command in setup-rs service. This is the error:
Error: couldn't connect to server mongodb1:27017, connection attempt failed: SocketException: Error connecting to mongodb1:27017 (MY_IP:27017) :: caused by :: Connection refused :
How should I initiate replica set without using any setup.sh file with only one node? Thanks in advance.
You need to first start your 2 mongodb instances and then call the initiate in a second moment (after both are started)
Something like:
#!/bin/bash
docker-compose up -d
sleep 5
docker exec mongodb1 /scripts/rs-init.sh
And in your mongodb1 you have the rs-init.sh script like:
#!/bin/bash
mongo <<EOF
var config = {
"_id": "dbrs",
"version": 1,
"members": [
{
"_id": 1,
"host": "mongodb1:27017",
"priority": 1
},
]
};
rs.initiate(config, { force: true });
rs.status();
EOF
You can take a look at the detailed step by step here
PS: I didn't test the full solution
I have created a mongodb replica in docker on my local machine. After configuration, replica is working if I connect inside the containers. Then when I connect localhost:30001,30002,30003 from my machine but outside the container, it says connection failed, prompting the following error message.
SERVER [node1:27017] (Type: UNKNOWN)
|_/ Connection error (MongoSocketOpenException): Exception opening socket
|____/ Unknown host: node3
SERVER [node2:27017] (Type: UNKNOWN)
|_/ Connection error (MongoSocketOpenException): Exception opening socket
|____/ Unknown host: node2
SERVER [node1:27017] (Type: UNKNOWN)
|_/ Connection error (MongoSocketOpenException): Exception opening socket
|____/ Unknown host: node1
Here is my docker-comopse file
version: '3'
services:
node1:
image: mongo
container_name: "node1"
networks:
- mongocluster
ports:
- 30001:27017
command: mongod --dbpath /data/db --replSet repset
node2:
image: mongo
container_name: "node2"
networks:
- mongocluster
ports:
- 30002:27017
command: mongod --dbpath /data/db --replSet repset
depends_on:
- node1
node3:
image: mongo
container_name: "node3"
networks:
- mongocluster
ports:
- 30003:27017
command: mongod --dbpath /data/db --replSet repset
depends_on:
- node2
networks:
mongocluster:
driver: bridge
And this is how I configured the cluster in mongodb
config = {
"_id": "repset",
"members": [
{
"_id": 0,
"host": "node1:27017"
},
{
"_id": 1,
"host": "node2:27017"
},
{
"_id": 2,
"host": "node3:27017"
}
]
}
I understand my local machine cannot interpret IP for node1, node2 and node3. I am only using it for dev purpose, so what is the quickest (maybe dirty) way to fix it?
Hope the following links help:
mongodb-replica-set-with-docker-and-connecting-net-core-app
mongo-replica-set-docker-localhost
Your guess is correct. Mongo's Replica set self-reports node1,2,3 as an IP address and your client from outside of the container network tries [unsuccessfully] to reach these.
One easy way to fix it would be to put your client inside the container within the same docker network.
Another [dirty] option - adjust hosts file on your local machine to interpret nodeX as the localhost. You'll need to tweak the port numbers to, so host and container port numbers are the same (start mongo on 30001,2,3 in the container)
And perhaps the easiest one - forget about replica set and just run the standalone mongo container.