MongoDB: не удаётся подключиться к replica set снаружи контейнера

У меня проблема: не получается подключиться из вне докер контейнера с MongoDb в режиме реплики. А внутри получается. Прикладываю пример моих конфигов:

docker-compose.mongo.replica-set.yml

services:
  mongo:
    image: mongo:latest
    container_name: mongo_container
    ports:
      - "${MONGODB_PORT}:27017"
    volumes:
      - mongo-data:/data/db
      - ./backend/docker-entrypoint.sh:/docker-entrypoint.sh:ro
    entrypoint: ["/docker-entrypoint.sh"]
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGODB_USER_NAME}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_USER_PASSWORD}
      MONGODB_HOST: ${MONGODB_HOST}
      MONGODB_PORT: ${MONGODB_PORT}
      MONGODB_REPLICA_SET: ${MONGODB_REPLICA_SET}
      MONGODB_DATABASE_AUTH_NAME: ${MONGODB_DATABASE_AUTH_NAME}
      MONGODB_REPLICA_SET_HOST: ${MONGODB_REPLICA_SET_HOST}

volumes:
  mongo-data:

.env

MONGODB_USER_NAME=admin
MONGODB_USER_PASSWORD=password
MONGODB_HOST=127.0.0.1
MONGODB_PORT=29017
MONGODB_DATABASE_NAME=intellectika-5
MONGODB_DATABASE_AUTH_NAME=admin
MONGODB_REPLICA_SET=rs0
MONGODB_REPLICA_SET_HOST=127.0.0.1

docker-entrypoint.sh

#!/bin/sh
set -e

: "${MONGO_INITDB_ROOT_USERNAME:?MONGO_INITDB_ROOT_USERNAME not set}"
: "${MONGO_INITDB_ROOT_PASSWORD:?MONGO_INITDB_ROOT_PASSWORD not set}"
: "${MONGODB_REPLICA_SET:?MONGODB_REPLICA_SET not set}"
: "${MONGODB_REPLICA_SET_HOST:?MONGODB_REPLICA_SET_HOST not set}"
: "${MONGODB_PORT:?MONGODB_PORT not set}"
: "${MONGODB_DATABASE_AUTH_NAME:?MONGODB_DATABASE_AUTH_NAME not set}"

KEYFILE=/etc/mongo.key

echo "MONGO_INITDB_ROOT_USERNAME: $MONGO_INITDB_ROOT_USERNAME"
echo "MONGO_INITDB_ROOT_PASSWORD: $MONGO_INITDB_ROOT_PASSWORD"
echo "MONGODB_REPLICA_SET: $MONGODB_REPLICA_SET"
echo "MONGODB_REPLICA_SET_HOST: $MONGODB_REPLICA_SET_HOST"
echo "MONGODB_PORT: $MONGODB_PORT"
echo "MONGODB_DATABASE_AUTH_NAME: $MONGODB_DATABASE_AUTH_NAME"

if [ ! -f "$KEYFILE" ]; then
  echo "? Генерируем keyfile внутри контейнера..."
  head -c 756 /dev/urandom | base64 > "$KEYFILE"
  chmod 600 "$KEYFILE"
  echo "✅ Keyfile создан: $KEYFILE"
fi

echo "? Запуск mongod init с --replSet, без auth..."
mongod --replSet "${MONGODB_REPLICA_SET}" --bind_ip_all --port "${MONGODB_PORT}" --dbpath /data/db --fork --logpath /tmp/mongod-init.log

echo "⏳ Ждем, пока mongod init будет доступен..."
timeout=15
while ! mongosh --host "127.0.0.1" --port "${MONGODB_PORT}" --eval "db.adminCommand('ping')" >/dev/null 2>&1; do
  sleep 1
  timeout=$((timeout - 1))
  if [ "$timeout" -le 0 ]; then
    echo "❌ mongod init не отвечает"
    pkill mongod
    exit 1
  fi
done
echo "✅ mongod init поднялся"

echo "? Проверяем состояние replica set и инициализируем с правильным host..."
RS_INIT_CHECK=$(mongosh --host "127.0.0.1" --port "${MONGODB_PORT}" --quiet --eval "try { rs.status().ok } catch(e) { 0 }")
if [ "$RS_INIT_CHECK" != "1" ]; then
  echo "? Инициализируем replica set с host=${MONGODB_REPLICA_SET_HOST}:${MONGODB_PORT}..."
  mongosh --host "127.0.0.1" --port "${MONGODB_PORT}" --eval "rs.initiate({_id:'${MONGODB_REPLICA_SET}', members:[{_id:0, host:'${MONGODB_REPLICA_SET_HOST}:${MONGODB_PORT}'}]});"
  echo "✅ replica set инициализирован"
else
  echo "ℹ️ replica set уже инициализирован"
fi

echo "⏳ Ждем, пока node станет PRIMARY..."
timeout=30
while true; do
  PRIMARY=$(mongosh --host "127.0.0.1" --port "${MONGODB_PORT}" --quiet --eval "rs.isMaster().ismaster" || echo "false")
  echo "Текущее состояние PRIMARY: $PRIMARY"
  if [ "$PRIMARY" = "true" ]; then
    echo "✅ Node стал PRIMARY"
    break
  fi
  sleep 1
  timeout=$((timeout - 1))
  if [ "$timeout" -le 0 ]; then
    echo "❌ Таймаут ожидания PRIMARY"
    exit 1
  fi
done

EXISTS=$(mongosh --host "127.0.0.1" --port "${MONGODB_PORT}" --quiet --eval "db.getSiblingDB('${MONGODB_DATABASE_AUTH_NAME}').system.users.find({user:'${MONGO_INITDB_ROOT_USERNAME}'}).count()")
if [ "$EXISTS" -eq 0 ]; then
  echo "? Создаем администратора..."
  mongosh --host "127.0.0.1" --port "${MONGODB_PORT}" "${MONGODB_DATABASE_AUTH_NAME}" --eval "db.createUser({user:'${MONGO_INITDB_ROOT_USERNAME}', pwd:'${MONGO_INITDB_ROOT_PASSWORD}', roles:[{role:'root', db:'${MONGODB_DATABASE_AUTH_NAME}'}]});"
  echo "✅ Администратор создан"
else
  echo "ℹ️ Администратор уже существует"
fi

echo "? Останавливаем mongod init..."
mongosh --host "127.0.0.1" --port "${MONGODB_PORT}" --eval "db.getSiblingDB('admin').shutdownServer()" || true
sleep 5

echo "? Запуск mongod с auth, replSet и keyFile..."
exec mongod \
  --replSet "${MONGODB_REPLICA_SET}" \
  --auth \
  --keyFile "$KEYFILE" \
  --bind_ip_all \
  --port "${MONGODB_PORT}" \
  --dbpath /data/db

Пример логов при подключении из докер контейнера с поднятным MongoDb:

# mongosh "mongodb://admin:[email protected]:29017/intellectika-5?authSource=admin&replicaSet=rs0"
Current Mongosh Log ID: 686193482cad7c672469e327
Connecting to:          mongodb://<credentials>@127.0.0.1:29017/intellectika-5?authSource=admin&replicaSet=rs0&serverSelectionTimeoutMS=2000&appName=mongosh+2.5.2
Using MongoDB:          8.0.10
Using Mongosh:          2.5.2

For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/

------
   The server generated these startup warnings when booting
   2025-06-29T19:22:35.971+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
   2025-06-29T19:22:36.446+00:00: You are running this process as the root user, which is not recommended
   2025-06-29T19:22:36.446+00:00: For customers running the current memory allocator, we suggest changing the contents of the following sysfsFile
   2025-06-29T19:22:36.446+00:00: For customers running the current memory allocator, we suggest changing the contents of the following sysfsFile
   2025-06-29T19:22:36.446+00:00: We suggest setting the contents of sysfsFile to 0.
   2025-06-29T19:22:36.446+00:00: vm.max_map_count is too low
   2025-06-29T19:22:36.446+00:00: We suggest setting swappiness to 0 or 1, as swapping can cause performance problems.
------

rs0 [primary] intellectika-5> rs.status()
{
  set: 'rs0',
  date: ISODate('2025-06-29T19:28:52.686Z'),
  myState: 1,
  term: Long('18'),
  syncSourceHost: '',
  syncSourceId: -1,
  heartbeatIntervalMillis: Long('2000'),
  majorityVoteCount: 1,
  writeMajorityCount: 1,
  votingMembersCount: 1,
  writableVotingMembersCount: 1,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1751225326, i: 1 }), t: Long('18') },
    lastCommittedWallTime: ISODate('2025-06-29T19:28:46.481Z'),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1751225326, i: 1 }), t: Long('18') },
    appliedOpTime: { ts: Timestamp({ t: 1751225326, i: 1 }), t: Long('18') },
    durableOpTime: { ts: Timestamp({ t: 1751225326, i: 1 }), t: Long('18') },
    writtenOpTime: { ts: Timestamp({ t: 1751225326, i: 1 }), t: Long('18') },
    lastAppliedWallTime: ISODate('2025-06-29T19:28:46.481Z'),
    lastDurableWallTime: ISODate('2025-06-29T19:28:46.481Z'),
    lastWrittenWallTime: ISODate('2025-06-29T19:28:46.481Z')
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1751225316, i: 1 }),
  electionCandidateMetrics: {
    lastElectionReason: 'electionTimeout',
    lastElectionDate: ISODate('2025-06-29T19:22:36.471Z'),
    electionTerm: Long('18'),
    lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 0, i: 0 }), t: Long('-1') },
    lastSeenWrittenOpTimeAtElection: { ts: Timestamp({ t: 1751224949, i: 4 }), t: Long('17') },
    lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1751224949, i: 4 }), t: Long('17') },
    numVotesNeeded: 1,
    priorityAtElection: 1,
    electionTimeoutMillis: Long('10000'),
    newTermStartDate: ISODate('2025-06-29T19:22:36.478Z'),
    wMajorityWriteAvailabilityDate: ISODate('2025-06-29T19:22:36.586Z')
  },
  members: [
    {
      _id: 0,
      name: '127.0.0.1:29017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 377,
      optime: { ts: Timestamp({ t: 1751225326, i: 1 }), t: Long('18') },
      optimeDate: ISODate('2025-06-29T19:28:46.000Z'),
      optimeWritten: { ts: Timestamp({ t: 1751225326, i: 1 }), t: Long('18') },
      optimeWrittenDate: ISODate('2025-06-29T19:28:46.000Z'),
      lastAppliedWallTime: ISODate('2025-06-29T19:28:46.481Z'),
      lastDurableWallTime: ISODate('2025-06-29T19:28:46.481Z'),
      lastWrittenWallTime: ISODate('2025-06-29T19:28:46.481Z'),
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1751224956, i: 1 }),
      electionDate: ISODate('2025-06-29T19:22:36.000Z'),
      configVersion: 1,
      configTerm: 18,
      self: true,
      lastHeartbeatMessage: ''
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1751225326, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('ll5oZrWBZuFkOydpBVRyHFfAZmw=', 0),
      keyId: Long('7521413133244563461')
    }
  },
  operationTime: Timestamp({ t: 1751225326, i: 1 })
}
rs0 [primary] intellectika-5>

Одновременно с этим с абсолютно такими же данными при подключении через Node.js ничего не происходит:

import { MongoClient } from "mongodb";

async function checkReplicaSetConnection(uri) {
  const client = new MongoClient(uri, {
    serverSelectionTimeoutMS: 5000,
  });

  try {
    await client.connect();
    console.log("Подключение к MongoDB успешно");

    const adminDb = client.db().admin();

    const replStatus = await adminDb.command({ replSetGetStatus: 1 });
    console.log("Статус replica set:");
    console.dir(replStatus, { depth: 3 });

    const primaryMember = replStatus.members.find(m => m.stateStr === "PRIMARY");
    if (primaryMember) {
      console.log(`PRIMARY: ${primaryMember.name}`);
    } else {
      console.log("PRIMARY не найден");
    }
  } catch (error) {
    console.error("Ошибка подключения или запроса к MongoDB:", error);
  } finally {
    await client.close();
  }
}

const uri = "mongodb://admin:[email protected]:29017/intellectika-5?authSource=admin&replicaSet=rs0";

checkReplicaSetConnection(uri);

Приходит такая ошибка:

Ошибка подключения или запроса к MongoDB: MongoServerSelectionError: connection <monitor> to 127.0.0.1:29017 closed
 {
  errorLabelSet: Set(0) {},
  reason: TopologyDescription {
    type: 'ReplicaSetNoPrimary',
    servers: Map(1) { '127.0.0.1:29017' => [ServerDescription] },
    stale: false,
    compatible: true,
    heartbeatFrequencyMS: 10000,
    localThresholdMS: 15,
    setName: 'rs0',
    maxElectionId: null,
    maxSetVersion: null,
    commonWireVersion: 0,
    logicalSessionTimeoutMinutes: null
  },
  code: undefined,
  [cause]: MongoNetworkError: connection <monitor> to 127.0.0.1:29017 closed
      at Connection.onClose   
      at Socket.emit (node:events:530:35)
      at TCP.<anonymous> (node:net:351:12) {
    errorLabelSet: Set(2) { 'HandshakeError', 'ResetPool' },
    beforeHandshake: false,
    [cause]: undefined
  }
}

Вроде бы в самой бд я пишу rs.status() и в members находится элемент с {name: '127.0.0.1:29017', stateStr: 'PRIMARY'}.
Но, подключение почему то не работает. В гайдах в интернете пишут, что нужно указывать localhost или 127.0.0.1.
Но ничего не работает. Кто сталкивался стаким?


Ответы (1 шт):

Автор решения: Михаил Камахин

Проблема решилась так:

  1. Нужно было в replica set записывать member с host равным названию сервиса c mongodb в docker-compose
  2. Так как я запускал в Windows через WSL докер - у меня ещё стоял десктопный MongoDb сервис, который стартовал при каждом запуске Windows. При попытке подключения с хоста к 127.0.0.1 я подключался не к MongoDb в докере, а к MongoDb в Windows, так как он был приоритетнее. Я удалил автозадачу в Windows для этого сервиса и сам сервис отключил и заработало локальное подключение
  3. При работе внутри докера и общении с другими контейнера - всё будет работать, но, внешне не будет, если MONGODB_PORT не является 27017. Поэтому при работе с репликой нельзя прокидывать другой порт, кроме дефолтного
→ Ссылка