先决条件

两台sever,一台client模拟集群情况
docker-compose如下,直接启动即可.

services:
  # Consul Server 1
  consul-server-1:
    image: hashicorp/consul:latest
    container_name: consul-server-1
    ports:
      - "8500:8500"  # HTTP API
      - "8600:8600/udp"  # DNS
    command: >
      consul agent -server -bootstrap-expect=2 -datacenter=dc1 -data-dir=/consul/data
      -retry-join=consul-server-2 -client=0.0.0.0 -bind=0.0.0.0 -ui
    volumes:
      - consul-server-1-data:/consul/data
    networks:
      - dev-network

  # Consul Server 2
  consul-server-2:
    image: hashicorp/consul:latest
    container_name: consul-server-2
    ports:
      - "8501:8500"  # HTTP API (different port to avoid conflict)
    command: >
      consul agent -server -bootstrap-expect=2 -datacenter=dc1 -data-dir=/consul/data
      -retry-join=consul-server-1 -client=0.0.0.0 -bind=0.0.0.0
    volumes:
      - consul-server-2-data:/consul/data
    networks:
      - dev-network
    depends_on:
      - consul-server-1

  # Consul Client
  consul-client:
    image: hashicorp/consul:latest
    container_name: consul-client
    ports:
      - "8502:8500"  # HTTP API (different port to avoid conflict)
    command: >
      consul agent -datacenter=dc1 -data-dir=/consul/data
      -retry-join=consul-server-1 -retry-join=consul-server-2
      -client=0.0.0.0 -bind=0.0.0.0
    volumes:
      - consul-client-data:/consul/data
    networks:
      - dev-network
    depends_on:
      - consul-server-1
      - consul-server-2
volumes:
  consul-server-1-data:
  consul-server-2-data:
  consul-client-data:

networks:
  dev-network:
    driver: bridge

跑起来后访问:http://localhost:8500/ui/dc1/nodes 会有3个节点
 title=


启动命令中的-server和-client:

-server

以服务方式启动,这个节点可以加入集群,参与选举,接收服务发现信息同步等.
启动时会顺带注册一个consul服务,所有consul程序都会同步这个consul服务.因此在nodes里面可以看到以server启动的consul,service为 1

-client

简单理解成尿袋.即每个pod/宿主机都需要部署一个client端,client启动不会注册consul服务.但是会向leader节点同步service信息
客户端负责接收程序发来的请求,转发给server端,这也就解决了如果是硬编码consul server的ip,如果刚好编码的ip地址服务挂了/换了,就歇逼的问题(如果真出这个问题,那就不是咱们能解决的了 XD ).

Speak you mother,show me your code

package main

import (
    "fmt"
    consulapi "github.com/hashicorp/consul/api"
    "log"
    "net/http"
)

var client *consulapi.Client

func startConsul() {
    baseConfig := consulapi.DefaultConfig()
    baseConfig.Address = "127.0.0.1:8502"
    var err error
    client, err = consulapi.NewClient(baseConfig)
    if err != nil {
        log.Fatal(err)
        return
    }
    registration := consulapi.AgentServiceRegistration{
        Tags:    []string{"user"},
        Port:    8080,
        Address: "host.docker.internal",
        Name:    "user-service-east-1",
    }

    check := consulapi.AgentServiceCheck{
        Interval:                       "15s",
        Timeout:                        "5s",
        HTTP:                           fmt.Sprintf("http://%s:%d", registration.Address, registration.Port),
        DeregisterCriticalServiceAfter: "30s",
    }
    registration.Check = &check

    err = client.Agent().ServiceRegister(&registration)
    if err != nil {
        log.Fatal(err)
        return
    }

    log.Println("consul service registered")

}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })
    startConsul()
    if client == nil {
        log.Fatal("consul client is nil")
    }

    testDifferentNodes()
    testConsulServiceDetails()
    testClientNodeInfo()

    log.Fatal(http.ListenAndServe(":8080", nil))
}

func testDifferentNodes() {
    addresses := []string{"127.0.0.1:8500", "127.0.0.1:8501", "127.0.0.1:8502"}

    for _, addr := range addresses {
        config := consulapi.DefaultConfig()
        config.Address = addr
        client, _ := consulapi.NewClient(config)

        services, _, _ := client.Catalog().Services(nil)
        log.Printf("节点 %s 的服务:", addr)
        for name := range services {

            log.Printf("  - %s", name)
        }
    }
}

func testConsulServiceDetails() {
    addresses := []string{"127.0.0.1:8500", "127.0.0.1:8501", "127.0.0.1:8502"}

    for _, addr := range addresses {
        config := consulapi.DefaultConfig()
        config.Address = addr
        client, _ := consulapi.NewClient(config)

        // 获取consul服务的详细信息
        consulServices, _, _ := client.Health().Service("consul", "", false, nil)

        log.Printf("节点 %s 上的consul服务:", addr)
        for _, service := range consulServices {
            log.Printf("  - 服务ID: %s", service.Service.ID)
            log.Printf("  - 节点名: %s", service.Node.Node)
            log.Printf("  - 节点地址: %s", service.Node.Address)
            log.Printf("  - 服务地址: %s:%d", service.Service.Address, service.Service.Port)
        }
        log.Println("---")
    }
}

func testClientNodeInfo() {
    config := consulapi.DefaultConfig()
    config.Address = "127.0.0.1:8502" // Client节点
    client, _ := consulapi.NewClient(config)

    // 查看当前节点信息
    self, _ := client.Agent().Self()
    log.Printf("Client节点信息:")
    log.Printf("  - 节点名: %s", self["Config"]["NodeName"])
    log.Printf("  - 节点类型: %s", self["Config"]["Server"])

    // 查看本地注册的服务
    services, _ := client.Agent().Services()
    log.Printf("Client本地服务:")
    for name := range services {
        log.Printf("  - %s", name)
    }
}

输出示例

2025/07/18 11:11:52 consul service registered
2025/07/18 11:11:52 节点 127.0.0.1:8500 的服务:
2025/07/18 11:11:52   - user-service-east-1
2025/07/18 11:11:52   - consul
2025/07/18 11:11:52 节点 127.0.0.1:8501 的服务:
2025/07/18 11:11:52   - consul
2025/07/18 11:11:52   - user-service-east-1
2025/07/18 11:11:52 节点 127.0.0.1:8502 的服务:
2025/07/18 11:11:52   - consul
2025/07/18 11:11:52   - user-service-east-1
2025/07/18 11:11:52 节点 127.0.0.1:8500 上的consul服务:
2025/07/18 11:11:52   - 服务ID: consul
2025/07/18 11:11:52   - 节点名: c05b2365cbe8
2025/07/18 11:11:52   - 节点地址: 172.20.0.3
2025/07/18 11:11:52   - 服务地址: :8300
2025/07/18 11:11:52   - 服务ID: consul
2025/07/18 11:11:52   - 节点名: c65d941ab42d
2025/07/18 11:11:52   - 节点地址: 172.20.0.2
2025/07/18 11:11:52   - 服务地址: :8300
2025/07/18 11:11:52 ---
2025/07/18 11:11:52 节点 127.0.0.1:8501 上的consul服务:
2025/07/18 11:11:52   - 服务ID: consul
2025/07/18 11:11:52   - 节点名: c05b2365cbe8
2025/07/18 11:11:52   - 节点地址: 172.20.0.3
2025/07/18 11:11:52   - 服务地址: :8300
2025/07/18 11:11:52   - 服务ID: consul
2025/07/18 11:11:52   - 节点名: c65d941ab42d
2025/07/18 11:11:52   - 节点地址: 172.20.0.2
2025/07/18 11:11:52   - 服务地址: :8300
2025/07/18 11:11:52 ---
2025/07/18 11:11:52 节点 127.0.0.1:8502 上的consul服务:
2025/07/18 11:11:52   - 服务ID: consul
2025/07/18 11:11:52   - 节点名: c05b2365cbe8
2025/07/18 11:11:52   - 节点地址: 172.20.0.3
2025/07/18 11:11:52   - 服务地址: :8300
2025/07/18 11:11:52   - 服务ID: consul
2025/07/18 11:11:52   - 节点名: c65d941ab42d
2025/07/18 11:11:52   - 节点地址: 172.20.0.2
2025/07/18 11:11:52   - 服务地址: :8300
2025/07/18 11:11:52 ---
2025/07/18 11:11:52 Client节点信息:
2025/07/18 11:11:52   - 节点名: 7830f95db485
2025/07/18 11:11:52   - 节点类型: %!s(bool=false)
2025/07/18 11:11:52 Client本地服务:
2025/07/18 11:11:52   - user-service-east-1

从上面可以看到所有节点都同步到了 user-service和consul服务的节点信息.并且consul服务中是没有client节点的consul服务的.

面板中的service和nodes

service

以服务名分类.

nodes

展示所有节点,并显示从其节点注册的服务数.
参阅顶部 nodes图,当你初始化完3个docker后,默认server有1个service,client有0个service.
当你运行go代码注册一个服务后client有1个service,server依旧还是1个service.这也说明了节点下的service数量是从其节点注册的服务数,与存储了多个少service信息无关.

文章目录