Golang hashicorp/serf库Docker Compose环境下连接故障排查
问题背景
我有一组基于Golang的服务,使用Serf实现服务发现。本地运行时服务间可正常发现并加入集群,但在Docker Compose环境中,容器外的服务无法加入容器内的集群,服务发现失败。
相关代码与配置
初始启动Serf进程代码
func startSD() { addr := "127.0.0.1:50055" h := &SDHandler{} var err error mems, err = discovery.New(h, discovery.Config{ NodeName: fmt.Sprintf("sd-%s", addr), BindAddr: addr, StartJoinAddrs: []string{}, }) if err != nil { log.Fatalln(err) } }
Serf节点配置代码
func (m *Membership) setupSerf() (err error) { addr, err := net.ResolveTCPAddr("tcp", m.BindAddr) if err != nil { return err } config := serf.DefaultConfig() config.Init() config.MemberlistConfig.BindAddr = addr.IP.String() config.MemberlistConfig.BindPort = addr.Port m.events = make(chan serf.Event) config.EventCh = m.events config.Tags = m.Tags config.NodeName = m.Config.NodeName m.serf, err = serf.Create(config) if err != nil { return err } go m.eventHandler() if m.StartJoinAddrs != nil { _, err = m.serf.Join(m.StartJoinAddrs, true) if err != nil { return err } } return nil }
Docker Compose配置
version: "3.9" services: media-api: build: context: . dockerfile: Dockerfile ports: - "50050:50050" - "50055:50055" - "50055:50055/udp"
初始错误信息
[DEBUG] memberlist: Failed to join 127.0.0.1:50055: EOF
更新后的问题
将BindAddr修改为:50055后,节点加入集群几秒就退出,报错:
2023/05/31 14:54:47 [ERR] memberlist: Failed to send gossip to [::]:50055: write udp 127.0.0.1:50051->[::]:50055: address ::: non-IPv4 address
2023/05/31 14:54:48 [ERR] memberlist: Failed to send UDP ping: write udp 127.0.0.1:50051->[::]:50055: address ::: non-IPv4 address
2023/05/31 14:54:49 [WARN] memberlist: Was able to connect to f34f34g34g34gerg over TCP but UDP probes failed, network may be misconfigured
2023/05/31 14:54:50 [ERR] memberlist: Failed to send UDP ping: write udp 127.0.0.1:50051->[::]:50055: address ::: non-IPv4 address
问题根源与解决方案
问题分析
- 初始配置
BindAddr: "127.0.0.1:50055"让Serf仅绑定容器内回环地址,容器外服务无法访问该端口,导致连接失败。 - 修改为
:50055后,Serf默认绑定所有网络接口(含IPv6),但容器内外UDP通信在IPv4环境下出现协议不兼容,节点被集群踢除。
解决步骤
1. 绑定所有IPv4接口
修改启动代码中的BindAddr为0.0.0.0:50055,明确指定绑定所有IPv4接口,避免IPv6干扰:
func startSD() { addr := "0.0.0.0:50055" h := &SDHandler{} var err error mems, err = discovery.New(h, discovery.Config{ NodeName: fmt.Sprintf("sd-%s", addr), BindAddr: addr, StartJoinAddrs: []string{}, }) if err != nil { log.Fatalln(err) } }
2. 强制Serf使用IPv4协议
在setupSerf函数中添加禁用IPv6的配置,确保UDP通信仅使用IPv4:
func (m *Membership) setupSerf() (err error) { addr, err := net.ResolveTCPAddr("tcp", m.BindAddr) if err != nil { return err } config := serf.DefaultConfig() config.Init() config.MemberlistConfig.BindAddr = addr.IP.String() config.MemberlistConfig.BindPort = addr.Port // 禁用IPv6,强制使用IPv4 config.MemberlistConfig.DisableIPv6 = true // 仅允许IPv4节点加入 config.MemberlistConfig.AllowAllIPv4 = true m.events = make(chan serf.Event) config.EventCh = m.events config.Tags = m.Tags config.NodeName = m.Config.NodeName m.serf, err = serf.Create(config) if err != nil { return err } go m.eventHandler() if m.StartJoinAddrs != nil { _, err = m.serf.Join(m.StartJoinAddrs, true) if err != nil { return err } } return nil }
3. 容器外节点使用宿主机IP加入集群
容器内Serf通过端口映射暴露后,容器外节点需使用宿主机实际IP(而非127.0.0.1,跨机器时需用宿主机局域网IP)加入集群:
// 容器外服务启动代码示例,替换为实际宿主机IP StartJoinAddrs: []string{"192.168.1.100:50055"},
验证要点
- 确认Docker Compose已正确映射TCP和UDP的50055端口(当前配置已满足)。
- 检查宿主机防火墙是否放行50055端口的TCP和UDP流量。
- 确保容器内外节点网络互通,容器外节点可ping通宿主机IP。
内容的提问来源于stack exchange,提问作者Alexander Z.

