我在云服务上有一个 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
...
我能够从公共网络连接到副本集的每个成员独立模式:
mongoUri = new MongoClientURI("mongodb://usr:pwd@proxy-server-public-ip:27017/db") ;
client = MongoClient(mongoUri);
但是当我尝试将其连接到副本集模式:
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);
我会因为连接错误而失败,因为副本集告诉驱动程序使用每个成员的内部地址(无法从公共网络访问)。
ps:我可以在代理服务器上以副本集模式连接到副本集。
如何连接到代理服务器后面的副本集?
更新:连接时我使用代理服务器的公共地址。
答案1
我最近也遇到过类似的情况。考虑一些 DNS 欺骗,其中客户端使用“真实”DNS 名称,而 mongodb 副本集成员使用相同的名称,但在 /etc/hosts 中覆盖它们以指向自身。
如果您有 3 个成员,则有三个 DNS 名称,您的客户端将使用这些名称来路由到代理,例如:
member1.mynetwork.mydomain -> (代理地址)
member2.mynetwork.mydomain -> (代理地址)
member3.mynetwork.mydomain -> (代理地址)
然后,在您的 mongodb 副本集成员上,在每个匹配的框上创建一个 /etc/hosts 条目,但指向它们自己的主机 IP,例如:
在 /etc/hosts 中:
10.1.1.11 member1.mynetwork.mydomain
10.1.1.22 member2.mynetwork.mydomain
10.1.1.33 member3.mynetwork.mydomain
member1.mynetwork.mydomain:27017
使用每个成员的“主机”字段等构建副本集配置。
配置客户端来连接类似的东西:
[member1.mynetwork.mydomain:27017,member2.mynetwork.mydomain:27017,member2.mynetwork.mydomain:27017]
副本集会根据自己的副本集成员列表,向驱动程序返回一个集群定义,这些成员名称相同:
hosts=[member1.mynetwork.mydomain:27017,member2.mynetwork.mydomain:27017, member3.mynetwork.mydomain:27017]
你应该做生意。
如果您的代理环境无法托管多个 DNS 名称,您可以更改端口全部配置(包括 mongodb 成员本身的本地绑定端口)到 27017/27018/27019 方案。
包括我在内的一些人会认为本地 /etc/hosts 覆盖很讨厌并且是一种黑客行为,这取决于您的服务器/VM/容器管理情况。
但是如果你陷入困境,我认为这是一个比重写 mongodb 响应更优雅的破解方法。
答案2
我早在五月份就问过这个问题,但现在我在这里回答我自己的问题。
正如评论中所讨论的,MongoDB 服务器将返回其成员及其配置地址的列表。由于托管的 MongoDB 副本集配置了私有地址,因此 MongoDB 服务器将提供成员的私有地址。
为了解决这个问题,我们需要一个专门用于 Mongo 客户端连接的代理。代理应该拦截 MongoDB 服务器对是大师命令并将私有地址覆盖为服务器的公共地址或代理服务器的地址。客户端收到这些拦截的地址后,他们将能够以副本集模式连接这些地址。
以下是 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;
}
}
更改客户端库以将私有地址映射到公共地址应该是一种替代解决方案。但支持所有语言并将您的自定义库推送到合作伙伴的开发机器可能是一项艰巨的工作。