Consul学习笔记1-简介与Demo

1. Consul 简介

ConsulHashiCorp 开发的分布式服务管理框架,提供服务发现、健康检查、配置中心和完整的分布式并行性维护技术。它特别适合应用于小型到大型的分布式集群环境,通过同步机制维护集群内定义好的服务和信息。

官方文档: https://www.consul.io/
代码仓库: https://github.com/hashicorp/consul

核心特性

  • 服务发现:支持服务注册与自动发现,便于其他服务快速找到可用的服务实例。
  • 健康检查:内置对服务和节点的健康状态监控,能动态剔除不健康的节点。
  • KV 存储:提供键值存储功能,可用于动态配置管理和协调分布式系统的行为。
  • 多数据中心支持:天然支持多数据中心部署,确保跨区域服务的高可用性和一致性。
  • 安全通信:通过加密和访问控制保障服务间通信的安全性。

应用场景

  • 微服务架构中的服务注册与发现。
  • 动态负载均衡和服务路由。
  • 分布式系统的配置管理。
  • 健康监控与故障转移。

Consul 的设计目标是简化分布式系统的管理难度,并提供一套统一的工具链来提升开发和运维效率。Consul 自推出以来,因其强大的功能和易用性被广泛应用于多个知名项目和企业中, 例如Netflix在其微服务架构中使用 Consul 进行服务发现与健康检查。

但由于Consul的特性和功能太多, 而官方的Tutorial使用了AWS等云服务进行讲解, 对于国内的小伙伴不是很友好, 因此这里我使用VMWare虚拟机做一个Consul的小demo, 从这个demo的开发演示中,学习如何使用Consul进行服务注册与发现。

本章是学习Consul的第一部分, 因此除了demo外不会涉及太多琐碎的知识点, 目的是让大家对Consul的功能、使用方式有一个大体的局具象感知,更详细的使用和原理介绍将在后续章节中进行。

2. 基于虚拟机的 Demo

由于官方的Tutorial比较复杂, 这里我采用一个极简的demo进行演示。

2.1 环境准备

环境准备包括以下关键步骤:

2.1.1 VMWare虚拟机的下载与安装**

VMWare Workstation Player 可以从官网免费下载(适用于非商业用途),安装后可用于创建和运行虚拟机。

官方下载地址:https://www.vmware.com/products/workstation-player.html

这里采用VMWare而不是WSL的原因是, VMWare默认会给创建的虚拟机分配一个随机的 IP 地址,且不同虚拟机实例的IP不同, 而WSL需要进行一定的配置, 相对麻烦一点。另一方面, VMWare提供虚拟机的快速克隆, 我们可以在一台虚拟机上部署好所有环境, 然后直接克隆即可, 不需要在4个虚拟机实例上分别布置环境依赖

2.1.2 创建4份 Ubuntu 22.04 Server 实例 安装

创建 4 台 Ubuntu Server 22.04 虚拟机,用于部署 Consul 集群。这里之所以要创建4台虚拟机, 是因为consulsever集群采用的是基于raftKV存储, 因此至少要3个节点, 才能形成server集群, 另外我们还需要一个client节点,其是被server服务的对象。

这里不推荐Desktop版本的Ubuntu, 因为我们要安装4份虚拟机实例, 要是使用Desktop版本的话, 内存压力会非常大! 同时, VMWare虚拟机的安装方式我也不介绍了, 默认大家有基础

2.1.3 安装开发工具链(Go、jq 等)

  • Go: 用于编译和运行服务注册代码, 我们的服务用Go写起来最方便, 因为consul本身使用Go作为开发语言, 可以直接import官方源码仓库的apigo代码中直接进行集群相关操作。
  • jq: 用于命令行下对 JSON 数据格式化查看,便于调试和验证 Consul 网络接口返回的数据。
  • OpenSSh-Server: 用于在宿主机和虚拟机之间进行 SSH 登录,方便调试和验证, 总不可能在虚拟机这样丑陋的环境里敲命令吧…
  • VSCode:远程连接虚拟机编写配置文件和代码

至于 SSH秘钥配置、VSCode插件配置, jq的使用方法等, 我就不具体介绍了, 默认大家有一定的基础

2.1.4 获取 Consul 可执行文件

可以直接从 Consul 官网 下载对应操作系统的二进制文件,解压后将 consul 放入系统路径 /usr/local/bin/ 即可使用;也可以通过源码编译生成可执行文件。

2.2 启动 agent

2.2.1 agent 的基本概念

这里先对 Consul 中的重要概念 agent 进行介绍:

Consulagent 的形式运行在每一个节点上,agent 可以是 serverclient 角色。server 负责参与 Raft 协议、维护集群状态和处理请求;client 则负责注册服务、运行健康检查,并将请求转发给 server 集群。

每个 agent 独立运行,通过配置文件(如 .hcl 文件)定义其行为,包括节点名、IP 地址、运行模式、数据目录、通信端口等信息。agent 启动后会尝试加入集群,并持续与其他节点保持通信以维护集群视图和服务状态。

在本 demo 中,我们分别启动了 3 个 server agent 和 1 个 client agent,构成一个最小化的 Consul 集群。

2.2.2 server agent 配置文件

这里serverconsul aent的启动命令如下:

1
consul agent -config-file=server.hcl

这里的server.hcl是配置文件, 其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
datacenter = "dc1"
node_name = "consul-server-1"
data_dir = "/home/toni/consul-tutorial/consul-data"
bind_addr = "192.168.110.129" # ! 当前节点的内网地址
client_addr = "0.0.0.0"

server = true
bootstrap_expect = 3

ui_config {
enabled = true
}

log_level = "INFO"

# ! 其他 Server 节点的地址, 需要排除自身
retry_join = [
"192.168.110.130",
"192.168.110.132"
]

这里说明如下:

  • datacenter: 设置当前节点所属的数据中心名称为 dc1。Consul 支持多数据中心部署,该配置用于标识该节点归属于哪个数据中心。数据中心是部署的最高级别单位,通常对应于一个物理数据中心或云提供商的一个区域
    -node_name: 指定当前 Consul 节点(agent)的唯一名称,在集群中通过这个名称来识别不同的节点。
  • data_dir: 定义 Consul 持久化数据的存储目录,例如节点状态、服务信息和 Raft 日志等都保存在此路径下。
  • bind_addr: 设置当前节点与其他 Consul agent 通信时绑定的 IP 地址。不需要指定端口, 内部会指定一个默认端口。
  • client_addr: 指定客户端接口监听的地址,用于接收 HTTP/DNS/gRPC 等客户端请求。设置为 0.0.0.0 表示允许从任意 IP 访问 API 接口。
  • server = true: 表示该节点是以 server 角色运行。serverConsul 集群的核心组件,负责参与 Raft 协议、维护集群一致性等关键任务。
  • bootstrap_expect: 声明集群中预期加入的 server 节点总数。当第一个 server 启动时,它会等待直到有 3 个 server 加入后才进行 Raft 初始化,从而避免脑裂问题。
  • ui_config: 启用内置 Web UI 界面,可以通过浏览器访问 Consul 的可视化界面,查看节点、服务、健康状态等信息。
  • log_level: 设置日志输出级别为 INFO,即显示正常运行日志。也可以设为 DEBUGERROR 来控制日志详细程度。
  • retry_join: 配置自动重试加入的其他 Consul 节点地址。在启动过程中,如果首次连接失败,Consul 会持续尝试与这些节点建立连接,以加入集群。此处列出的是其他两个 server 节点的 IP 地址。

注意:每个 server 节点的 bind_addrnode_name 应根据实际机器进行修改,而 retry_join 列表应包含除自身外的所有 server 节点地址。

2.2.3 client agent 配置文件

这里clientconsul aent的启动命令如下:

1
consul agent -config-file=client.hcl

这里的client.hcl是配置文件, 其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
datacenter = "dc1"
node_name = "consul-client-1"
data_dir = "/home/toni/consul-tutorial/consul-data"
bind_addr = "192.168.110.133"
client_addr = "0.0.0.0"
log_level = "INFO"

retry_join = [
"192.168.110.129",
"192.168.110.130",
"192.168.110.132"
]

client这里的配置文件内容和 server 配置文件基本一致,只是去掉了 server = truebootstrap_expect 这两个配置项。并且需要指定整个serverretry_join 列表,以包含所有 server 节点地址。

2.2.4 启动集群后的状态

在3个节点启动server agnet以及1个节点启动client agent后, 我们的集群就已经正常搭建好了。可以在任意一个节点上云霄下面的命令查看集群节点状态:

1
2
3
4
5
6
l$ consul members
Node Address Status Type Build Protocol DC Partition Segment
consul-server-1 192.168.110.129:8301 alive server 1.22.0dev 2 dc1 default <all>
consul-server-2 192.168.110.130:8301 alive server 1.22.0dev 2 dc1 default <all>
consul-server-3 192.168.110.132:8301 alive server 1.22.0dev 2 dc1 default <all>
consul-client-1 192.168.110.131:8301 alive client 1.22.0dev 2 dc1 default <default>

其输出包含健康状态、IP、类型等信息。

我们也可以只查看server这样参与选举的节点信息:

1
2
3
4
5
$ consul operator raft list-peers
Node ID Address State Voter RaftProtocol Commit Index Trails Leader By
consul-server-3 83d7a1c9-442d-beaf-82dd-8409952bd0bb 192.168.110.132:8300 follower true 3 486 0 commits
consul-server-2 6a464d5c-eda5-4d93-11e6-842292468c1d 192.168.110.130:8300 leader true 3 486 -
consul-server-1 e61233cd-e9f4-3fac-b76b-016f1125b76b 192.168.110.129:8300 follower true 3 486 0 commits

这是我们可以杀掉其中某个``server`节点,然后观察集群状态变化:

1
2
3
4
$ consul operator raft list-peers
Node ID Address State Voter RaftProtocol Commit Index Trails Leader By
consul-server-3 83d7a1c9-442d-beaf-82dd-8409952bd0bb 192.168.110.132:8300 follower true 3 1195 0 commits
consul-server-1 e61233cd-e9f4-3fac-b76b-016f1125b76b 192.168.110.129:8300 leader true 3 1195 -

发现掉了一个leaderconsul-server-2点之后触发了重新选举, 集群仍然可以正常工作(这方面的只是可以搜索关键字raft了解)

当我们重启consul-server-2后再次查询:

1
2
3
4
5
$ consul operator raft list-peers
Node ID Address State Voter RaftProtocol Commit Index Trails Leader By
consul-server-3 83d7a1c9-442d-beaf-82dd-8409952bd0bb 192.168.110.132:8300 follower true 3 1213 0 commits
consul-server-1 e61233cd-e9f4-3fac-b76b-016f1125b76b 192.168.110.129:8300 leader true 3 1213 -
consul-server-2 6a464d5c-eda5-4d93-11e6-842292468c1d 192.168.110.130:8300 follower false 3 1213 0 commits

其能够在此加入集群。

你也可以测试杀掉client节点试试看看

2.3 服务注册与发现

现在我们有了集群, 但还没有服务, 我们需要先编写一个服务注册的代码, 然后运行这个服务并注册

2.3.1 Go 服务代码

这里我们用Go写一段简单的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from service A!")
})
fmt.Println("Starting server on :5000...")
http.ListenAndServe("0.0.0.0:5000", nil)
}

这段 Go 代码实现了一个简单的 HTTP Web 服务,主要作用是监听本地的 5000 端口,并对所有访问根路径 / 的请求返回 “Hello from service A!” 的响应。

写完之后我们直接:

1
2
go build -o app
./app >/dev/null &

这段代码当然是在 consul-client-1 这个节点上编写并运行的。

3.2 命令行方式注册服务

我们现在在consul-client-1 这个节点已经运行了一个服务, 我们现在需要将其注册到consul集群中。Consul支持通过http接口发送注册服务的请求, 这里我们把Post请求的body写在一个json文件里,然后通过curl发送请求:
service.json:

1
2
3
4
5
6
7
8
9
10
{
"Name": "service-a",
"ID": "service-a-1",
"Address": "192.168.110.131",
"Port": 5000,
"Check": {
"HTTP": "http://192.168.110.131:5000/",
"Interval": "10s"
}
}

这里重点讲解Check, 其就是规定了Consul通过什么网络接口判断服务是否健康。

发送请求:

1
curl --request PUT   --data @service.json   http://127.0.0.1:8500/v1/agent/service/register

8500 是consulhttp端口

然后我们就可以在consul集群中看到这个服务了:

1
2
3
$ consul catalog services
consul
service-a

另一方面, 我们也可以通过http查询服务的具体健康信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ curl http://localhost:8500/v1/health/service/service-a | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1448 100 1448 0 0 88948 0 --:--:-- --:--:-- --:--:-- 90500
[
{
"Node": {
...
},
"Service": {
"ID": "service-a-1",
"Service": "service-a",
"Tags": [],
...
...
},
"Checks": [
{
...
},
{
"Node": "consul-client-1",
"CheckID": "service:service-a-1",
"Name": "Service 'service-a' check",
"Status": "passing",
...
}
]
}
]

原始的结果比较长, 这里我省略了部分, 需要重点关注的就是Checks字段的Status, 案例的Status字段为passing, 表示服务正常。

现在我们杀掉服务:

1
2
$ kill -9 $(ps aux | grep 'app' | awk '{print $2}')
$ curl http://localhost:8500/v1/health/service/service-a | jq

此时返回的结果中:

1
2
3
4
5
6
7
{
"Node": "consul-client-1",
"CheckID": "service:service-a-1",
"Name": "Service 'service-a' check",
"Status": "critical",
...
}

Status字段为critical, 表示服务已经挂了。

当我们重新启动服务后再次检查, Status字段变为passing, 这就是Consul对服务状态维护的检测功能

3.3 Go 代码引入第三方库注册服务

尽管我们也可以通过http接口注册服务, 但Consul提供了SDK来简化操作, 我们可以引入第三方库来注册服务,这样运行代码就可以自动进行注册服务等基础的操作, 不需要额外通过人工命令行的方式进行操作。

安装 SDK:

1
go get github.com/hashicorp/consul/api

服务代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package main

import (
"fmt"
"log"
"net/http"

"github.com/hashicorp/consul/api"
)

func main() {
// 注册服务到 Consul
registerServiceWithConsul()

http.HandleFunc("/", helloHandler)

fmt.Println("Starting server on :5000...")
err := http.ListenAndServe("0.0.0.0:5000", nil)
if err != nil {
fmt.Println("Error starting server:", err)
}
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from service A!")
}

func registerServiceWithConsul() {
// 创建 Consul 客户端
config := api.DefaultConfig()
config.Address = "127.0.0.1:8500" // Consul agent 地址
client, err := api.NewClient(config)
if err != nil {
log.Fatalf("Failed to create Consul client: %v", err)
}

// 定义服务注册信息
registration := &api.AgentServiceRegistration{
ID: "service-a-1",
Name: "service-a",
Address: "127.0.0.1",
Port: 5000,
Check: &api.AgentServiceCheck{
HTTP: "http://127.0.0.1:5000/",
Interval: "10s",
},
}

// 注册服务
err = client.Agent().ServiceRegister(registration)
if err != nil {
log.Fatalf("Failed to register service with Consul: %v", err)
}
}

现在我们编译后运行服务,其会自动进行注册

3 小结

通过本章的实践,我们完成了以下关键内容:

  • 了解 Consul 的基本概念与核心功能
    Consul 是一个分布式服务管理工具,提供服务发现、健康检查、KV 存储、多数据中心支持和安全通信等能力。它适用于从单机开发环境到大规模生产集群的各种场景。

  • 搭建 Consul 集群环境
    我们基于 VMWare 创建了 4 台 Ubuntu 虚拟机,其中 3 台作为 server agent 组成 Raft 集群,1 台作为 client agent 接入集群。通过 .hcl 配置文件分别配置其角色和网络信息,并成功启动了 Consul 集群。

  • 理解 Agent 的运行机制
    Consul 以 agent 的形式运行在每个节点上,server agent 负责集群状态维护,client agent 负责注册服务并转发请求。agent 启动后会持续与其他节点通信,维护集群一致性。

  • 配置 Consul Server 与 Client
    详细讲解了 server.hclclient.hcl 配置文件中各个参数的作用,包括数据中心设置、节点名称、数据目录、监听地址、集群加入方式等,为搭建高可用集群打下基础。

  • 验证集群状态与容错能力
    使用 consul membersconsul operator raft list-peers 查看集群成员与 Raft 状态。测试 kill 掉某个 server 节点后,集群仍能正常进行 leader 选举和状态同步,体现了 Consul 的高可用特性。

  • 实现服务注册与健康检查
    编写了一个简单的 HTTP Web 服务,并通过两种方式将其注册到 Consul:

    • 命令行方式:使用 curl 发送 JSON 请求注册服务,并配置健康检查。
    • Go SDK 方式:引入 github.com/hashicorp/consul/api 包,在代码中自动完成服务注册与健康上报。
  • 演示 Consul 的服务健康监测能力
    当服务正常运行时,Consul 显示其状态为 passing;当服务被 kill 掉后,Consul 检测到异常并将状态变为 critical,实现了对服务状态的动态感知。

本章通过一个极简但完整的 Demo,展示了 Consul 在服务注册与发现中的核心作用,也为我们后续深入学习其原理和高级用法打下了良好基础。

但我们目前还缺少对这个服务的具体应用,Consul是一个功能非常多的微服务管理框架, 不同微服务简可以通过Consul机械能各种类型的通信, 这些将在后续章节中进一步介绍。(如果我不鸽。。。。。。)