Reputation: 53
I'm trying to set up a single node MongoDB replicaset on Docker and connect to it within my node app, but the connection is refused. The connection works fine if I use mongo as a standalone instance and no replicaset.
This is how I connect to mongo on my node app :
mongoose.connect("mongodb://admin:secretpass@app_mongodb:27017/dbname?authSource=admin&replicaSet=rs0")
And this is the error I receive :
/var/www/worker/node_modules/mongoose/lib/connection.js:824
const serverSelectionError = new ServerSelectionError();
^
MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
Mongo logs show that the app tries to connect :
{"t":{"$date":"2023-01-27T10:22:46.410+00:00"},"s":"I", "c":"NETWORK", "id":22943, "ctx":"listener","msg":"Connection accepted","attr":{"remote":"172.18.0.10:41318","uuid":"2e1ccbc5-3162-4f64-80f3-8be580079ef6","connectionId":68,"connectionCount":11}}
{"t":{"$date":"2023-01-27T10:22:46.417+00:00"},"s":"I", "c":"NETWORK", "id":51800, "ctx":"conn68","msg":"client metadata","attr":{"remote":"172.18.0.10:41318","client":"conn68","doc":{"driver":{"name":"nodejs|Mongoose","version":"4.11.0"},"os":{"type":"Linux","name":"linux","architecture":"x64","version":"5.10.0-12-amd64"},"platform":"Node.js v19.5.0, LE (unified)","version":"4.11.0|6.7.5"}}}
{"t":{"$date":"2023-01-27T10:22:46.425+00:00"},"s":"I", "c":"NETWORK", "id":22944, "ctx":"conn68","msg":"Connection ended","attr":{"remote":"172.18.0.10:41318","uuid":"2e1ccbc5-3162-4f64-80f3-8be580079ef6","connectionId":68,"connectionCount":10}}
docker-compose.yml :
version: '3.8'
services:
mongodb:
container_name: app_mongodb
build:
dockerfile: ./Dockerfile
x-bake:
no-cache: true
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=secretpass
volumes:
- ./db:/data/db
networks:
- proxy
restart: unless-stopped
worker:
container_name: app_worker
image: "node:latest"
command: "npm run dev"
user: "node"
working_dir: /var/www/worker
environment:
WAIT_HOSTS: app_mongodb:27017
volumes:
- ./worker:/var/www/worker
networks:
- proxy
links:
- mongodb
depends_on:
- mongodb
restart: unless-stopped
networks:
proxy:
external: true
Dockerfile :
FROM mongo
# Initiate replica set
RUN echo "secretpasswordkey" > "/tmp/replica.key"
RUN chmod 600 /tmp/replica.key
RUN chown 999:999 /tmp/replica.key
CMD ["mongod", "--replSet", "rs0", "--bind_ip_all", "--keyFile", "/tmp/replica.key"]
And I also run this command with mongosh after starting up the container (is there a way to add that to the Dockerfile instead by the way?) :
rs.initiate({_id: 'rs0', members: [{_id:1, 'host':'127.0.0.1:27017'}]})
A quick check shows the replicaset is indeed initialized :
rs0 [direct: primary] admin> rs.status()
{
set: 'rs0',
date: ISODate("2023-01-27T10:35:44.062Z"),
myState: 1,
term: Long("1"),
syncSourceHost: '',
syncSourceId: -1,
heartbeatIntervalMillis: Long("2000"),
majorityVoteCount: 1,
writeMajorityCount: 1,
votingMembersCount: 1,
writableVotingMembersCount: 1,
optimes: {
lastCommittedOpTime: { ts: Timestamp({ t: 1674815739, i: 1 }), t: Long("1") },
lastCommittedWallTime: ISODate("2023-01-27T10:35:39.161Z"),
readConcernMajorityOpTime: { ts: Timestamp({ t: 1674815739, i: 1 }), t: Long("1") },
appliedOpTime: { ts: Timestamp({ t: 1674815739, i: 1 }), t: Long("1") },
durableOpTime: { ts: Timestamp({ t: 1674815739, i: 1 }), t: Long("1") },
lastAppliedWallTime: ISODate("2023-01-27T10:35:39.161Z"),
lastDurableWallTime: ISODate("2023-01-27T10:35:39.161Z")
},
lastStableRecoveryTimestamp: Timestamp({ t: 1674815679, i: 1 }),
electionCandidateMetrics: {
lastElectionReason: 'electionTimeout',
lastElectionDate: ISODate("2023-01-27T10:25:49.015Z"),
electionTerm: Long("1"),
lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1674815148, i: 1 }), t: Long("-1") },
lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1674815148, i: 1 }), t: Long("-1") },
numVotesNeeded: 1,
priorityAtElection: 1,
electionTimeoutMillis: Long("10000"),
newTermStartDate: ISODate("2023-01-27T10:25:49.082Z"),
wMajorityWriteAvailabilityDate: ISODate("2023-01-27T10:25:49.111Z")
},
members: [
{
_id: 1,
name: '127.0.0.1:27017',
health: 1,
state: 1,
stateStr: 'PRIMARY',
uptime: 643,
optime: { ts: Timestamp({ t: 1674815739, i: 1 }), t: Long("1") },
optimeDate: ISODate("2023-01-27T10:35:39.000Z"),
lastAppliedWallTime: ISODate("2023-01-27T10:35:39.161Z"),
lastDurableWallTime: ISODate("2023-01-27T10:35:39.161Z"),
syncSourceHost: '',
syncSourceId: -1,
infoMessage: '',
electionTime: Timestamp({ t: 1674815149, i: 1 }),
electionDate: ISODate("2023-01-27T10:25:49.000Z"),
configVersion: 1,
configTerm: 1,
self: true,
lastHeartbeatMessage: ''
}
],
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1674815739, i: 1 }),
signature: {
hash: Binary(Buffer.from("a211cfe3faa237bf8a30ccbc8ca929eea704f467", "hex"), 0),
keyId: Long("7193276291800367109")
}
},
operationTime: Timestamp({ t: 1674815739, i: 1 })
}
Ideas on what I'm doing wrong ? Thanks !
Upvotes: 0
Views: 1550
Reputation: 391
In my case that it was hosted at a VPS, I had to change "mongodb" to the Remote IP address of the VPS:
rs.initiate({_id: 'rs0', members: [{_id: 1, 'host': 'MY_VPS_IP:27017'}]})
Upvotes: 0
Reputation: 53
Thanks to @WernfriedDomscheit's comment pointing me to the right direction, I realized I have to give the mongodb container hostname instead of 127.0.0.1 when initiating the replicaset :
rs.initiate({_id: 'rs0', members: [{_id: 1, 'host': 'mongodb:27017'}]})
I was able to automate initialization by adding a healthcheck routine to my docker-compose.yml file. The Dockerfile is not necessary anymore.
The whole mongodb container description is now this :
mongodb:
container_name: app_mongodb
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGODB_USER}
- MONGO_INITDB_ROOT_PASSWORD=${MONGODB_PASS}
volumes:
- ./db:/data/db
networks:
- proxy
restart: unless-stopped
healthcheck:
test: test $$(echo 'rs.initiate({_id':' "rs0", members':' [{_id':' 1, "host"':' "mongodb':'27017"}]}) || rs.status().ok' | mongosh -u $${MONGO_INITDB_ROOT_USERNAME} -p $${MONGO_INITDB_ROOT_PASSWORD} --quiet) -eq 1
interval: 10s
start_period: 30s
It's important to set the host in the initiate()
and not leave the arguments empty because otherwise by default it'll setup the replicaset using the container ID as host, and if the container is removed and restarted the replicaset won't work anymore as the new container ID will be different.
Upvotes: 1