You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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会优先使用初始连接的地址,因此可以正常工作。

修复步骤

  1. 启动MongoDB时绑定所有接口
    修改启动容器的命令,添加--bind_ip_all参数,确保MongoDB监听所有可用接口:

    args := []string{
        "run", "-d", "-p", fmt.Sprintf("%d:27017", mongoPort),
        "mongo",
        "mongod", "--replSet=rs0", "--bind_ip_all",
    }
    
  2. 初始化副本集时指定宿主机可访问的地址
    手动指定副本集配置,将节点地址设置为宿主机的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),
    }
    
  3. 可选:单节点副本集添加直连参数
    如果是测试用的单节点副本集,可以在连接URL中添加directConnection=true,让驱动直接连接指定地址,跳过副本集节点发现:

    mongoURL := fmt.Sprintf("mongodb://127.0.0.1:%d?directConnection=true", mongoPort)
    

修改后,Go驱动就能获取到可正常解析的节点地址,顺利完成连接和操作。


内容的提问来源于stack exchange,提问作者Peterlits Zo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:06:24