MongoDB副本集无法创建索引:ReplicaSetNoPrimary问题求助
Docker部署MongoDB副本集后Go驱动连接失败:主机无法解析
错误详情
启动Docker部署的MongoDB副本集后,Go程序连接时出现如下错误:
server selection error: server selection timeout, current topology: { Type: ReplicaSetNoPrimary, Servers: [ { Addr: b3ec1125321a:27017, Type: Unknown, Last error: connection() error occurred during connection handshake: dial tcp: lookup b3ec1125321a on 10.225.109.87:53: no such host }, ] }
最小可复现代码
package main import ( "bytes" "context" "errors" "fmt" "net" "os/exec" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // getDockerPath 返回docker的路径 func getDockerPath() (string, error) { return exec.LookPath("docker") } // getPort 返回一个可用端口(需尽快使用) func getPort() (int, error) { l, err := net.Listen("tcp", ":0") if err != nil { return 0, fmt.Errorf("listen to port 0: %w", err) } defer l.Close() return l.Addr().(*net.TCPAddr).Port, nil } // runMongo 在docker中启动MongoDB容器 func runMongo(ctx context.Context) error { dockerPath, err := getDockerPath() if err != nil { return fmt.Errorf("get docker path: %w", err) } mongoPort, err := getPort() if err != nil { return fmt.Errorf("get mongo port: %w", err) } mongoURL := fmt.Sprintf("mongodb://127.0.0.1:%d", mongoPort) fmt.Printf("Starting a mongoDB listening on %s\n\n", mongoURL) // 启动MongoDB容器 args := []string{ "run", "-d", "-p", fmt.Sprintf("%d:27017", mongoPort), "mongo", "mongod", "--replSet=rs0", } startCmd := exec.CommandContext(ctx, dockerPath, args...) stdout := &bytes.Buffer{} startCmd.Stdout = stdout if err := startCmd.Run(); err != nil { return fmt.Errorf("run MongoDB container by docker: %w", err) } // 获取容器名称(取前6位) containerName, err := stdout.ReadString('\n') for err != nil { containerName, err = stdout.ReadString('\n') } containerName = containerName[:6] // ********************************************************************** // 注意:如果去掉下面初始化副本集的代码,同时移除--replSet rs0参数,程序可以正常运行 // ********************************************************************** // 初始化MongoDB副本集 args = []string{ "exec", containerName, "mongo", "--eval", "rs.initiate();", } err = errors.New("in loop") for err != nil { <-time.After(1 * time.Second) initCmd := exec.CommandContext(ctx, dockerPath, args...) fmt.Println(initCmd) err = initCmd.Run() if err != nil { fmt.Println(err) } } // 连接MongoDB并创建索引 client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURL)) if err != nil { return fmt.Errorf("cannot connect to MongoDB: %w", err) } indexName := "Namespace's name's unique index" True := true index, err := client.Database("tmp").Collection("namespace").Indexes().CreateOne(ctx, mongo.IndexModel{ Keys: bson.D{{Key: "name", Value: 1}}, Options: &options.IndexOptions{ Name: &indexName, Unique: &True, }, }) if err != nil { return fmt.Errorf("create index for namespace: %w", err) } fmt.Println(index) return nil } func main() { err := runMongo(context.Background()) if err != nil { fmt.Printf("Fail to run MongoDB: %v\n", err) } }
程序运行输出
$ go run . Starting a mongoDB listening on mongodb://127.0.0.1:29597 /usr/bin/docker exec b3ec11 mongo --eval rs.initiate(); Fail to run MongoDB: create index for namespace: server selection error: server selection timeout, current topology: { Type: ReplicaSetNoPrimary, Servers: [{ Addr: b3ec1125321a:27017, Type: Unknown, Last error: connection() error occurred during connection handshake: dial tcp: lookup b3ec1125321a on 10.225.109.87:53: no such host }, ] }
补充信息
使用mongosh可以正常连接并执行操作:
$ mongosh mongodb://127.0.0.1:29597 > use tmp > ns = db.getCollection("namespace") > ns.createIndex({name: 1}, {unique: true, name: "Namespace's name's unique index"})
执行rs.isMaster()显示该节点是主节点,但Go驱动始终报ReplicaSetNoPrimary且无法解析容器主机名。
解决方案
问题根源:MongoDB副本集初始化时,默认会将容器的主机名(如b3ec1125321a)作为节点地址返回给客户端。Go驱动会严格尝试连接这个地址,但宿主机网络无法解析容器内部的主机名,导致连接失败。而mongosh会优先使用初始连接的地址,因此可以正常工作。
修复步骤
启动MongoDB时绑定所有接口
修改启动容器的命令,添加--bind_ip_all参数,确保MongoDB监听所有可用接口:args := []string{ "run", "-d", "-p", fmt.Sprintf("%d:27017", mongoPort), "mongo", "mongod", "--replSet=rs0", "--bind_ip_all", }初始化副本集时指定宿主机可访问的地址
手动指定副本集配置,将节点地址设置为宿主机的127.0.0.1:<映射端口>,替换默认的容器主机名:args = []string{ "exec", containerName, "mongo", "--eval", fmt.Sprintf(`rs.initiate({_id:"rs0",members:[{_id:0,host:"127.0.0.1:%d"}]})`, mongoPort), }可选:单节点副本集添加直连参数
如果是测试用的单节点副本集,可以在连接URL中添加directConnection=true,让驱动直接连接指定地址,跳过副本集节点发现:mongoURL := fmt.Sprintf("mongodb://127.0.0.1:%d?directConnection=true", mongoPort)
修改后,Go驱动就能获取到可正常解析的节点地址,顺利完成连接和操作。
内容的提问来源于stack exchange,提问作者Peterlits Zo
相关产品推荐
相关产品推荐

