如何连接到代理后面的 MongoDB 副本集?

Alb*_*ong 7 mongodb

我在云服务上有一个 MongoDB 副本集。出于安全原因,副本集可用于云的内部网络。

我遵循了该云服务的指南,并在代理服务器上为副本集的每个成员设置了一个代理:

0.0.0.0:27017 -> member1-private-ip:27107
0.0.0.0:27018 -> member2-private-ip:27107
0.0.0.0:27019 -> member3-private-ip:27017
...
Run Code Online (Sandbox Code Playgroud)

我能够以独立模式从公共网络连接到副本集的每个成员:

mongoUri = new MongoClientURI("mongodb://usr:pwd@proxy-server-public-ip:27017/db")  ;
client = MongoClient(mongoUri);
Run Code Online (Sandbox Code Playgroud)

但是当我尝试以副本集模式连接它时:

mongoUri = new MongoClientURI("mongodb://usr:pwd@proxy-server-public-ip:27017,proxy-server-public-ip:27018,proxy-server-public-ip:27019/db?replicaSet=replcaSetName");
client = MongoClient(mongoUri);
Run Code Online (Sandbox Code Playgroud)

我会因连接错误而失败,因为复制集告诉驱动程序使用每个成员的内部地址(无法从公共网络访问)。

ps:我可以在代理服务器上以副本集模式连接到副本集。

如何连接到代理服务器后面的副本集?


更新:我在连接时使用代理服务器的公共地址。

小智 6

我最近也遇到过类似的情况。考虑一些 DNS 作弊,其中客户端使用“真实”DNS 名称,并且 mongodb 副本集成员使用相同的名称,但在 /etc/hosts 中覆盖它们以指向它们自己。

如果您有 3 个成员,那么您的客户端将使用三个 DNS 名称来路由到代理,例如:

member1.mynetwork.mydomain ->(代理地址)
member2.mynetwork.mydomain ->(代理地址)
member3.mynetwork.mydomain ->(代理地址)

然后,在 mongodb 副本集成员上,在每个匹配的框上创建一个 /etc/hosts 条目,但指向它们自己的主机 IP,例如:

/etc/主机:

10.1.1.11 member1.mynetwork.mydomain
10.1.1.22 member2.mynetwork.mydomain
10.1.1.33 member3.mynetwork.mydomain
Run Code Online (Sandbox Code Playgroud)

使用每个成员的“主机”字段构建副本集配置,依此member1.mynetwork.mydomain:27017类推。

配置客户端以连接到以下内容:

[member1.mynetwork.mydomain:27017,member2.mynetwork.mydomain:27017,member2.mynetwork.mydomain:27017]
Run Code Online (Sandbox Code Playgroud)

副本集将根据其自己的副本集成员列表使用集群定义来响应驱动程序,该列表将具有相同的名称:

hosts=[member1.mynetwork.mydomain:27017,member2.mynetwork.mydomain:27017, member3.mynetwork.mydomain:27017]
Run Code Online (Sandbox Code Playgroud)

而且你应该做生意。

如果您的代理情况无法托管多个 DNS 名称,您可以将所有配置中的端口(包括 mongodb 成员本身的本地绑定端口)更改为 27017/27018/27019 方案。

有些人,包括我自己,会认为本地 /etc/hosts 覆盖是令人讨厌的和黑客行为,具体取决于您的服务器/虚拟机/容器管理情况。

但如果你陷入困境,我认为这是比重写 mongodb 响应更优雅的技巧。


Alb*_*ong 4

我在五月份问过这个问题,但现在我在这里回答我自己的问题。

正如评论中所讨论的,MongoDB 服务器将返回其成员列表及其配置的地址。由于托管 MongoDB 副本集配置了私有地址,因此 MongoDB 服务器将提供成员的私有地址。

为了解决这个问题,我们需要一个专用的 Mongo 客户端连接代理。代理应该拦截 MongoDB 服务器对isMaster命令的响应,并将私有地址覆盖为服务器的公共地址或代理服务器的地址。客户端收到这些截获的地址后,就可以以replicaSet模式连接这些地址。

这是 Node.js 中的一些代码:

clientConn.on("data", dataHandler(proxyConn, function(data) {
  const msg = new WireMessage(data);
  if (msg.isCommand("isMaster")) {
    remoteClient.recordForInterception(msg);
  }
  mongoConn.write(data);
}));

mongoConn.on("data", dataHandler(proxyConn, function(data) {
  const msg = new WireMessage(data, {skipBody: true});
  var changed = false;
  if (remoteClient.shouldInterceptReply(msg)) {
    // To Intercept message, we need a full parse
    msg.parseBody();
    changed = remoteClient.interceptReply(clientConn, msg);
  }
  if (changed) {
    // Serialize intercepted data
    data = msg.serialize();
  }
  clientConn.write(data);
}));

interceptReply = function (conn, replyMessage) {
  if ('isMaster'.toLowerCase() !== replyMessage.toLowerCase()) {
    return false;
  }
  var doc;
  if (replyMessage.body.metadata) {
    doc = replyMessage.body.metadata;
  } else if (replyMessage.body.documents instanceof Array && 
    replyMessage.body.documents.length > 0) {
    doc = replyMessage.body.documents[0];
  } else {
    this.logger.warn("No document to handle: %s.", replyMessage.toString());
    return false;
  }    
  return interceptHosts(doc, conn,  hostMappings);
};

interceptHosts = function (doc, conn, mappings) {
  if (doc.hosts) {
    var hosts = doc.hosts;
    for (var i = 0; i < hosts.length; ++i) {
      var host = hosts[i];
      hosts[i] = getReverseAddress(host, conn, mappings);
    }
  }
  if (doc.primary) {
    doc.primary = getReverseAddress(doc.primary, conn, mappings);
  }
  if (doc.me) {
    doc.me = getReverseAddress(doc.me, conn, mappings);
  }
  return doc;
};

function getReverseAddress(endpoint, conn, reverseAddressMapping) {
  var hostMap = reverseAddressMapping[endpoint];
  if (hostMap && hostMap.host === "0.0.0.0") {
    // If we are listening on ANY address, use 
    //   the effective address the client connect us.
    return conn.localAddress + ":" + hostMap.port;
  } else if (hostMap) {
    return hostMap.host + ":" + hostMap.port;
  } else {
      return endpoint;
  }
}
Run Code Online (Sandbox Code Playgroud)

更改客户端库以将私有地址映射到公共地址应该是一种替代解决方案。但支持所有语言并将您的定制库推送到合作伙伴的开发机器可能是一项艰巨的工作。