Version: 1.31.1

概述

Consul 被官方定义为多网络工具,提供功能齐全的服务网格解决方案。Consul 提供了一种软件驱动的路由和分段方法。它还带来了额外的好处,例如故障处理、重试和网络可观察性。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建完整的服务网格并实现零信任安全。

最早 Consul 的就是一个高可用的服务注册和发现的注册中心,近些年来,Consul 引入了服务网格(service mesh)。从其官方网站来开,官方希望 Consul 可以提供一整套服务网格的解决方案。

本文,主要从使用者的角度,来介绍 Consul 服务发现相关的特性的基本使用以及安装和部署。其他关于 kv 存储、服务网格等特性,本文暂不涉及。此外,文本也不重点介绍 Consul 底层的高可用的原理和算法。

快速开始

单机运行

由于 Consul 是 Go 实现的,而具有 Go 优秀的跨平台特性,因此在任何平台安装和运行 Consul 非常容易,只需从其 官方下载站点 ,下载相应平台的 zip 包,解压后即可直接运行 ./consul 命令即可运行(或加入 PATH),关于下载,更多参见:Install Consul

cd ~/Downloads
unzip consul_1.13.1_darwin_amd64.zip
sudo mv consul /usr/local/bin

安装完成后,通过如下命令,以 dev 模式运行:

consul agent -dev -node machine

核心输出如下:

==> Starting Consul agent...
           Version: '1.13.1'
        Build Date: '2022-08-11 19:07:00 +0000 UTC'
           Node ID: 'd4f7830e-00e8-9405-fa38-ec873c85b295'
         Node name: 'machine'
        Datacenter: 'dc1' (Segment: '<all>')
            Server: true (Bootstrap: false)
       Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
      Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
           Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false
  • Node Name:是 Agent 的唯一名称。默认情况下,这是机器的主机名,但您可以使用 -node 标志对其进行自定义。
  • Datacenter:配置代理运行的数据中心。对于单 DC 配置,代理将默认为 dc1,您可以使用 -datacenter 配置。 Consul 对多个数据中心具有一流的支持,但配置每个节点以报告其数据中心可提高代理效率。
  • Server:表明 Agent 是在 Server 还是 Client 模式下运行。在服务器模式下运行代理需要额外的开销。这是因为它们参与了共识仲裁、存储集群状态并处理查询。服务器也可能处于 “bootstrap” 模式,这使服务器能够选举自己作为 Raft 领导者。多个服务器不能处于引导模式,因为它会使集群处于不一致的状态。
  • Client Addr:这是用于 Client 连接的接口的地址。这包括 HTTP 和 DNS 接口的端口。默认情况下,这仅绑定到 localhost。如果更改此地址或端口,则在运行 consul members 等命令时指定 -http-addr 以指示如何访问代理。其他应用程序也可以使用 HTTP 地址和端口来控制 Consul。
  • Cluster Addr:这是用于集群中 Consul Agent 之间通信的地址和端口集。并不要求集群中的所有 Consul Agent 都必须使用相同的端口,但所有其他节点必须可以访问此地址。

注意:如果使用 systemd 运行 Consul 且配置了 -join 时,service 配置文件需配置 Type=notify

该模式将启用单个 Server Agent,并打印 Debug 日志,不能在真实生产环境使用,如何在生产模式部署,参见:下文

此时,通过浏览器打开 http://127.0.0.1:8500 即可打开 Consul 的 WebUI。

下文将,通过 http://localhost:8500 端口的 HTTP API 注册和查询服务。需要注意的是:

  • 注册和取消服务,需使用 /v1/agent 接口。
  • 发现服务,需使用 /v1/catalog/v1/health 接口。

基本使用

本部分主要介绍如何通过 HTTP API 进行 Consul 的服务注册和发现的基本使用方法。

假设我们有两个服务,分别是 test-service-1test-service-2

test-service-1 包含两个实例,一个是健康的 (9000),一个是未启动的 (9001)。test-service-2 有两个健康实例 (90109011),使用 nc 命令模拟这两个服务。

  • while true; do echo -e "HTTP/1.1 200 OK\n\ntest-service-1 (instance1): $(date)" | nc -l 9000; if [ $? -ne 0 ]; then break; fi; done
  • while true; do echo -e "HTTP/1.1 200 OK\n\ntest-service-2 (instance1): $(date)" | nc -l 9010; if [ $? -ne 0 ]; then break; fi; done
  • while true; do echo -e "HTTP/1.1 200 OK\n\ntest-service-2 (instance2): $(date)" | nc -l 9011; if [ $? -ne 0 ]; then break; fi; done

注册服务

更多参见: Service - Agent HTTP API - 注册服务

# 注册第 1 个服务的第 1 个实例
curl  \
    --request PUT \
    --data '
        {
            "ID": "test-service-1-instance-1",
            "Name": "test-service-1",
            "Address": "127.0.0.1",
            "Port": 9000,
            "Check": {
                "HTTP": "http://127.0.0.1:9000",
                "Interval": "10s"
            }
        }
    ' \
    http://localhost:8500/v1/agent/service/register

# 注册第 1 个服务的第 2 个实例
curl  \
    --request PUT \
    --data '
        {
            "ID": "test-service-1-instance-2",
            "Name": "test-service-1",
            "Address": "127.0.0.1",
            "Port": 9001,
            "Check": {
                "HTTP": "http://127.0.0.1:9001",
                "Interval": "10s"
            }
        }
    ' \
    http://localhost:8500/v1/agent/service/register

# 注册第 2 个服务的第 1 个实例
curl  \
    --request PUT \
    --data '
        {
            "ID": "test-service-2-instance-1",
            "Name": "test-service-2",
            "Address": "127.0.0.1",
            "Port": 9010,
            "Check": {
                "HTTP": "http://127.0.0.1:9010",
                "Interval": "10s"
            }
        }
    ' \
    http://localhost:8500/v1/agent/service/register

# 注册第 2 个服务的第 2 个实例
curl  \
    --request PUT \
    --data '
        {
            "ID": "test-service-2-instance-2",
            "Name": "test-service-2",
            "Address": "127.0.0.1",
            "Port": 9011,
            "Check": {
                "HTTP": "http://127.0.0.1:9011",
                "Interval": "10s"
            }
        }
    ' \
    http://localhost:8500/v1/agent/service/register

发现服务

参见:Query services

方式 1:通过 DNS

只返回健康的实例。

dig @127.0.0.1 -p 8600 test-service-1.service.consul SRV

输出如下:

; <<>> DiG 9.10.6 <<>> @127.0.0.1 -p 8600 test-service-1.service.consul SRV
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9930
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 3
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;test-service-1.service.consul. IN      SRV

;; ANSWER SECTION:
test-service-1.service.consul. 0 IN     SRV     1 1 9000 7f000001.addr.dc1.consul.

;; ADDITIONAL SECTION:
7f000001.addr.dc1.consul. 0     IN      A       127.0.0.1
machine.node.dc1.consul. 0      IN      TXT     "consul-network-segment="

;; Query time: 0 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sun Aug 21 14:58:43 CST 2022
;; MSG SIZE  rcvd: 167

方式 2:catalog HTTP API

返回所有实例(细节参见:List Nodes for Service | Filtering)。

curl http://localhost:8500/v1/catalog/service/test-service-1

输出部分内容如下所示:

[
    {
        "ID": "3033c057-b976-28bb-1666-02fbf3dd00d2",
        "Node": "machine",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "ServiceID": "test-service-1-instance-1",
        "ServiceName": "test-service-1",
        "ServiceTags": [],
        "ServicePort": 9000,
        // ...
    },
    {
        "ID": "3033c057-b976-28bb-1666-02fbf3dd00d2",
        "Node": "machine",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "ServiceID": "test-service-1-instance-2",
        "ServiceName": "test-service-1",
        "ServiceTags": [],
        "ServicePort": 9001,
        // ...
    }
]

方式 3:health HTTP API

返回所有实例,并可以获取到健康检查的状态(细节参见:List Nodes for Service | Filtering)。

curl http://127.0.0.1:8500/v1/health/service/test-service-1

输出部分内容如下所示:

[
    {
        "Node": {
            "ID": "3033c057-b976-28bb-1666-02fbf3dd00d2",
            "Node": "machine",
            "Address": "127.0.0.1",
            "Datacenter": "dc1",
            // ...
        },
        "Service": {
            "ID": "test-service-1-instance-1",
            "Service": "test-service-1",
            "Tags": [],
            "Address": "127.0.0.1",
            "Port": 9000,
        },
        "Checks": [
            {
                "Node": "machine",
                "CheckID": "serfHealth",
                "Name": "Serf Health Status",
                "Status": "passing",
                // ...
            },
            {
                "Node": "machine",
                "CheckID": "service:test-service-1-instance-1",
                "Name": "Service 'test-service-1' check",
                "Status": "passing",
                // ...
            }
        ]
    },
    {
        "Node": {
            "ID": "3033c057-b976-28bb-1666-02fbf3dd00d2",
            "Node": "machine",
            "Address": "127.0.0.1",
            "Datacenter": "dc1",
            // ...
        },
        "Service": {
            "ID": "test-service-1-instance-2",
            "Service": "test-service-1",
            "Tags": [],
            "Address": "127.0.0.1",
            "Port": 9001,
        },
        "Checks": [
            {
                "Node": "machine",
                "CheckID": "serfHealth",
                "Name": "Serf Health Status",
                "Status": "passing",
                // ...
            },
            {
                "Node": "machine",
                "CheckID": "service:test-service-1-instance-2",
                "Name": "Service 'test-service-1' check",
                "Status": "critical",
                // ...
            }
        ]
    }
]

取消注册

curl --request PUT http://127.0.0.1:8500/v1/agent/service/deregister/test-service-1-instance-1
curl --request PUT http://127.0.0.1:8500/v1/agent/service/deregister/test-service-1-instance-2
curl --request PUT http://127.0.0.1:8500/v1/agent/service/deregister/test-service-2-instance-1
curl --request PUT http://127.0.0.1:8500/v1/agent/service/deregister/test-service-2-instance-2

安装和部署

参考: 数据中心部署

架构和说明

参考: What is Consul? | 部署架构

Consul 是一个分布式系统,在 Consul 的概念中,一个 Consul 集群被称为数据中心 (datacenter)。

一个 Consul 数据中心由多个节点(被称为 Agent)组成,这些节点可以部署在物理机、虚拟机、或容器中。这些 Agent 可以分为如下两类:

  • 3~5 台 Server Agent,包括一个 Leader 和多个 Follower,部署独立的性能强劲的机器上。
  • 0~n 台 Client Agent,部署在每一台需要部署业务应用的机器上。

在 Consul 中,Server 还是 Client 只是 consul 这个二进制文件 agent 子命令的一个模式。区别在于:

  • Server Agent
    • 跟踪可用服务、它们的 IP 地址以及它们当前的运行状况和状态。
    • 跟踪可用节点、它们的 IP 地址以及它们当前的运行状况和状态。
    • 构建一个了解服务和节点可用性的服务目录 (DNS)。
    • 维护和更新 K/V 存储。
    • 采用 gossip protocol 协议向所有 Agent 传达更新。
    • 对通过该 Agent 注册的服务进行健康检查。
  • Client Agent
    • 将请求通过 RPC 转发到 Server Agent,以及一些缓存策略。
    • 对通过该 Agent 注册的服务进行健康检查。

也就是说,Client Agent 的健康检查和 Server Agent 能力是相同的,其他的 API 请求都会转发到 ServerAgent 中。

在 API 层面。在使用者看来,不需要区分 Client 和 Server,不管连到 Client 还是 Server,都可以使用 Consul 的全部的功能。

另外,一个只有 Server 的 Consul 集群也是可以正常工作的,但是 Consul 的推荐架构是为每个服务所在的节点 (如 k8s node) 部署一个 Client Agent 原因在于(来自:网络):

  • Agent 可以减轻 Server 的健康检查的压力。
  • 对应用层隐藏 Server Agent 的分布式复杂性:应用层只需要知道服务发现的地址永远是 localhost:8500。
  • 当 Server Agent 故障时, Client Agent 可以利用缓存继续提供服务。
  • Client Agent 的缓存机制,极大提高了集群的吞吐和性能。

因此一个推荐的单数据中心的 Consul 集群的架构如下图所示(图片来源:一篇文章了解Consul服务发现实现原理):

image

Consul Agent 会暴露很多个服务地址,可以分为两类 Client 和 Cluster,默认端口为(详见:Required Ports):

  • Client (给应用程序使用)
    • 8500(tcp): HTTP api 和 WebUI
    • 8600(tcp/udp): DNS 服务
    • 8501(tcp): HTTPS(默认不开启)
    • 8502(tcp): gRPC API(默认不开启)
  • Cluster (集群 Agent 使用,通过 serf 库实现)
    • 8300(tcp): Server RPC 地址。
    • 8301(tcp/udp):集群内部 Client & Server Agent 间相互通讯协调的端口。
    • 8302(tcp/udp):跨集群(跨数据中心) 的 Server Agent 间相互通讯协调的端口。

consul agent 的配置可以通过 命令行参数 (consul agent --help) 和 配置文件。本部分仅介绍部分常用的命令行参数:

参数默认值说明
-datacenter=<value>dc1同一个集群的数据中心名应该是一致的
-serveragent 模式是否为 server
-bootstrap-expect=<value>server 模式有效,当 server agent 达到该数值后,集群开始引导启动,需要注意的是,这个集群中所有 server agent 的该参数的值必须一致
-node=<value>主机的 hostname此节点的名称。 在集群中必须是唯一的。
-bind=<value>0.0.0.0Cluster 端口绑定的地址,可以通过 go-sockaddr 语法运行时获取。如果为默认值,通告地址(指的是别的节点访问本节点时的 ip 地址)将为该机器的私有地址,因此如果有多个私有地址将报错
-retry-join需要加入的集群中的其他节点的地址,会一致重试直到成功,可以通过 go-sockaddr 语法运行时获取,支持域名,可以指定多次。
-data-dir=<value>数据持久化目录,需要保证在 agent 挂掉数据仍然是持久化。 client 和 server 模式都需要,client 用来实现停止后重新注册服务。
-client=<value>127.0.0.1Client 端口绑定的地址,可以通过 go-sockaddr 语法运行时获取。
-ui是否启用 webui
-domainconsul.通过 DNS 做服务发现时的域名。

手动部署

上文 快速开始 - 单机运行 介绍了如何启动一个开发模式(单 Server Agent 节点)的 Consul 集群。本部分,介绍如何手动部署一个 Consul 集群。集群规划如下:

  • 3 台机器:搭建一个包含 3 个 Server Agent 节点的 Consul 集群。
  • 2 台机器:模拟用于部署服务的节点,在这两台机器上部署 Client Agent 进程。

需要特别说明的是,Consul 所有 Agent 节点必须是相互可通的同一内网。

简单起见,使用 Docker 容器模拟这 5 台机器(参见:官方镜像说明,配置文件内容可以通过 CONSUL_LOCAL_CONFIG 配置)。

创建网络

docker network create consul-cluster

部署 Server Agent

# 启动第 1 个 server
docker run --network=consul-cluster --hostname=consul-server-1 --name=consul-server-1 -d \
    consul agent -server -bootstrap-expect=3 -retry-join=consul-server-1 -retry-join=consul-server-2 -retry-join=consul-server-3
# 启动第 2 个 server
docker run --network=consul-cluster --hostname=consul-server-2 --name=consul-server-2 -d \
    consul agent -server -bootstrap-expect=3 -retry-join=consul-server-1 -retry-join=consul-server-2 -retry-join=consul-server-3
# 启动第 3 个 server
docker run --network=consul-cluster --hostname=consul-server-3 --name=consul-server-3 -d \
    consul agent -server -bootstrap-expect=3 -retry-join=consul-server-1 -retry-join=consul-server-2 -retry-join=consul-server-3

注意:

  • 可以添加 -client 0.0.0.0 -ui 简单起见使用默认值。
  • 生产环境一定要指定 -data-dir 持久化数据。

部署 Client Agent

# 启动第 1 个 client (为了方便观察,启用 webui、并将 http api 和 DNS 暴露到宿主机)
docker run --network=consul-cluster --hostname=consul-client-1 --name=consul-client-1 -p 8500:8500 -p 8600:8600 -d \
    consul agent -retry-join=consul-server-1 -retry-join=consul-server-2 -retry-join=consul-server-3 -client=0.0.0.0 -ui
# 启动第 2 个 client
docker run --network=consul-cluster --hostname=consul-client-2 --name=consul-client-2 -d \
    consul agent -retry-join=consul-server-1 -retry-join=consul-server-2 -retry-join=consul-server-3

观察测试

  • 打开 client 1 的 webui ( http://localhost:8500 ) 可以看到
    • Services 面板: 3 个 server 组成了 consul 服务。
    • Node 面板: 5 个 agent 全部健康。
  • 在 client 1 上注册一个测试服务。

    # 不健康的服务
    curl  \
        --request PUT \
        --data '
            {
                "ID": "test-service-1-instance-1",
                "Name": "test-service-1",
                "Address": "127.0.0.1",
                "Port": 9000,
                "Check": {
                    "HTTP": "http://127.0.0.1:9000",
                    "Interval": "10s"
                }
            }
        ' \
        http://localhost:8500/v1/agent/service/register
    # 健康的服务(不启用健康检查)
    curl  \
        --request PUT \
        --data '
            {
                "ID": "test-service-1-instance-2",
                "Name": "test-service-1",
                "Address": "127.0.0.1",
                "Port": 9001
            }
        ' \
        http://localhost:8500/v1/agent/service/register
  • 服务发现

    • 通过 DNS: dig @127.0.0.1 +tcp -p 8600 test-service-1.service.consul SRV(注意 +tcp),可以返回 9001 的服务。
    • 通过 Catalog API: curl http://localhost:8500/v1/catalog/service/test-service-1,返回全部两个服务。
    • 通过 Health API: curl http://localhost:8500/v1/health/service/test-service-1,返回全部两个服务以及状态。
  • 服务发现 (在其他 client 中)

    • 进入 client2 docker exec -it consul-client-2 sh
    • 通过 Catalog API: curl http://localhost:8500/v1/catalog/service/test-service-1,返回全部两个服务。
    • 通过 Health API: curl http://localhost:8500/v1/health/service/test-service-1,返回全部两个服务以及状态。
    • 通过 Service Local Health APIcurl http://localhost:8500/v1/agent/health/service/name/test-service-1 找不到,可以看出该 API 只能查找到注册到当前 Node 上的服务。
  • Client Agent 正常退出

    • 停止 client1 docker stop consul-client-1
    • 进入 client2 docker exec -it consul-client-2 sh
    • 通过 Health API: curl http://localhost:8500/v1/health/service/test-service-1,观察状态可知,服务已经被取消注册了。
    • 重新启动 client1 docker stop consul-client-1
    • 进入 client2 docker exec -it consul-client-2 sh
    • 通过 Health API: curl http://localhost:8500/v1/health/service/test-service-1,观察状态可知,服务已经又被重新注册了。
  • Client Agent 异常退出

    • 停止 client1 docker kill -9 consul-client-1
    • 进入 client2 docker exec -it consul-client-2 sh
    • 通过 Health API: curl http://localhost:8500/v1/health/service/test-service-1,观察状态可知,服务仍然存在,但是 Checks 状态可以看出 Node 退出了。
    • 重新启动 client1 docker start consul-client-1
    • 进入 client2 docker exec -it consul-client-2 sh
    • 通过 Health API: curl http://localhost:8500/v1/health/service/test-service-1,观察状态可知,服务已经又被重新注册了。
  • Server Agent (Leader) 异常退出

    • 停止 server1 (从 webui 中找到 leader): docker kill consul-server-1
    • 打开 webui,经过一段时间后,可以看出 Consul 集群 2 个 server 仍然正常工作
    • 重新启动 server2:docker start consul-server-1
    • 打开 webui,经过一段时间后,可以看出 Consul 集群 3 个 server 仍然正常工作
  • Server Agent (Leader) 退出

    • 停止 server2 (从 webui 中找到 leader): docker stop consul-server-2
    • 打开 webui 可以看出 Consul 集群 2 个 server 仍然正常工作
    • 重新启动 server2:docker start consul-server-2
    • 打开 webui 可以看出 Consul 集群 3 个 server 仍然正常工作
  • Server Agent (follower) 退出

    • 停止 server3 (从 webui 中找到非 leader): docker stop consul-server-3
    • 打开 webui 可以看出 Consul 集群 2 个 server 仍然正常工作
    • 重新启动 server2:docker start consul-server-3
    • 打开 webui 可以看出 Consul 集群 3 个 server 仍然正常工作

清理现场

docker rm -f consul-client-1 consul-client-2 consul-server-1 consul-server-2 consul-server-3

云原生部署

在官方的将 Consul 部署到 Kubernetes 的文档中,重点介绍的是 Service Mesh 相关的教程。

本部分,不会介绍 Service Mesh 相关的内容,而介绍如何在 Kubernetes 部署一套仅提供服务发现注册中心能力的 Consul 集群。可能的规划如下:

  • 使用 Kubernetes StatefulSet 部署具有 3~5 个 Consul Server Agent 的 Consul 集群。
  • 该 Consul 集群的 Client 的部署有如下两种选择:
    • (推荐) 使用 Kubernetes DaemonSet 为 Kubernete 集群的每个节点,部署 Consul Client Agent。
    • 如果没有 Kubernetes 集群 DaemonSet 的权限,则可以使用 Kubernetes StatefulSet 部署一个 Consul Client Agent 集群,并通过 Kubernetes 的 Service (type=Cluster) 提供服务。
  • 需要使用 Consul 服务注册和发现能力的 Pod,针对如上 Consul 集群 Client 的部署方式的不同,有不同的使用方式:

    • DaemonSet:

      • (推荐)挂载宿主机的文件(Consul Client Agent 的配置文件添加 addresses { http = "0.0.0.0 unix:///var/run/consul/socket/http.sock"})。
        • 挂载宿主机 /var/run/consul/socket 目录
        • 导出环境变量 CONSUL_HTTP_ADDR=unix:///var/run/consul/socket/http.sock
      • 使用 host ip。

                env:
                - name: HOST_IP
                  valueFrom:
                    fieldRef:
                    apiVersion: v1
                    fieldPath: status.hostIP
                - name: CONSUL_HTTP_ADDR
                  value: http://$(HOST_IP):8500
    • StatefulSet + Service (type=Cluster):导出环境变量 CONSUL_HTTP_ADDR=http://$ConsulClientAgentService.$Namespace.svc.cluster.local

本部分示例选择:以 DaemonSet 的方式部署 Consul Client Agent,通过挂载宿主机文件 unix daemon socket 文件的方式给其他 pod 提供服务。

执行 kubectl apply -f consul.yaml,其中 consul.yaml 内容如下:

# Namespace: consul
apiVersion: v1
kind: Namespace
metadata:
  name: consul
---
# Headless Service: consul-server
# 为 StatefulSet 的 consul-server 准备
apiVersion: v1
kind: Service
metadata:
  name: consul-server
  namespace: consul
spec:
  clusterIP: None
  selector:
    app: consul-server
  ports:
    - name: http
      port: 8500
      targetPort: 8500
    - name: dns-udp
      protocol: "UDP"
      port: 8600
      targetPort: 8600
    - name: dns-tcp
      protocol: "TCP"
      port: 8600
      targetPort: 8600
    - name: server
      port: 8300
      targetPort: 8300
    - name: serflan-tcp
      protocol: "TCP"
      port: 8301
      targetPort: 8301
    - name: serflan-udp
      protocol: "UDP"
      port: 8301
      targetPort: 8301
    - name: serfwan-tcp
      protocol: "TCP"
      port: 8302
      targetPort: 8302
    - name: serfwan-udp
      protocol: "UDP"
      port: 8302
      targetPort: 8302
---
# StatefulSet: consul-server
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: consul-server
  namespace: consul
spec:
  selector:
    matchLabels:
      app: consul-server
  serviceName: "consul-server"
  replicas: 3  # TODO: 节点数目根据情况而定
  template:
    metadata:
      labels:
        app: consul-server
    spec:
      containers:
      - name: consul-server
        image: "consul:1.13.1"
        args:
          - "agent"
          - "-server"
          - "-bootstrap-expect=3"  # TODO: 节点数目根据情况而定
          # https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/statefulset/#stable-network-id
          # url 为 $podName-{0..N-1}.$(serviceName).$(namespace).svc.cluster.local
          - "-retry-join=consul-server-0.consul-server.consul.svc.cluster.local"
          - "-retry-join=consul-server-1.consul-server.consul.svc.cluster.local"
          - "-retry-join=consul-server-2.consul-server.consul.svc.cluster.local"  # TODO: 节点数目根据情况而定
          # TODO: 其他参数根据自身情况修改
        volumeMounts:
          - name: consul-server-data
            mountPath: /consul/data
  volumeClaimTemplates:
  - metadata:
      name: consul-server-data
      namespace: consul
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"  # TODO: 根据自身情况修改
      resources:
        requests:
          storage: 5Gi                      # TODO: 根据自身情况修改
---
# DaemonSet: consul-client
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: consul-client
  namespace: consul
spec:
  selector:
    matchLabels:
      name: consul-client
  template:
    metadata:
      labels:
        name: consul-client
    spec:
      initContainers:
      - name: init-mount
        image: "consul:1.13.1"
        command: ['/bin/sh', '-c', 'chown -R consul:consul /var/run/consul/socket']
        volumeMounts:
        - name: consul-client-socket
          mountPath: /var/run/consul/socket
      containers:
      - name: consul-client
        image: "consul:1.13.1"
        args:
          - "agent"
          - "-retry-join=consul-server-0.consul-server.consul.svc.cluster.local"
          - "-retry-join=consul-server-1.consul-server.consul.svc.cluster.local"
          - "-retry-join=consul-server-2.consul-server.consul.svc.cluster.local"
          # TODO: 其他参数根据自身情况修改
          - "-ui"
        env:
        - name: CONSUL_LOCAL_CONFIG
          value: '{ "addresses": { "http": "0.0.0.0 unix:///var/run/consul/socket/http.sock" } }'
        volumeMounts:
        - name: consul-client-data
          mountPath: /consul/data
        - name: consul-client-socket
          mountPath: /var/run/consul/socket
      volumes:
      - name: consul-client-data
        hostPath:
          path: /var/run/consul/data
          type: DirectoryOrCreate
      - name: consul-client-socket
        hostPath:
          path: /var/run/consul/socket
          type: DirectoryOrCreate

通过 kubectl port-forward pods/consul-client-4mtdb 8500:8500 -n consulconsul-client 的 8500 端口进行端口转发,并打开 WebUI http://localhost:8500,观察集群情况。

执行 kubectl create -f test-consul-pod.yaml 创建测试 pod,其中 test-consul-pod.yaml 内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  volumes:
  - name: consul-client-socket
    hostPath:
      path: /var/run/consul/socket
  containers:
  - name: busybox
    image: busybox:1.28.4
    command: [ "sleep", "100000000"]
    imagePullPolicy: IfNotPresent
    env:
    - name: CONSUL_HTTP_ADDR
      value: unix:///var/run/consul/socket/http.sock
    volumeMounts:
    - name: consul-client-socket
      mountPath: /var/run/consul/socket
  restartPolicy: Always

启动测试服务:

kubectl exec -it busybox -- /bin/sh
while true; do echo -e "HTTP/1.1 200 OK\n\ntest-service-1 (instance1): $(date)" | nc -l -p 9000; if [ $? -ne 0 ]; then break; fi; done

注册测试服务:

kubectl exec -it busybox -- /bin/sh
ip addr
# 将如下的 127.0.0.1 替换为 ip addr 的输出
curl  \
    --request PUT \
    --unix-socket ${CONSUL_HTTP_ADDR#*//} \
    --data '
        {
            "ID": "test-service-1-instance-1",
            "Name": "test-service-1",
            "Address": "127.0.0.1",
            "Port": 9000,
            "Check": {
                "HTTP": "http://127.0.0.1:9000",
                "Interval": "10s"
            }
        }
    ' \
    http://localhost:8500/v1/agent/service/register

通过 WebUI 观察:http://localhost:8500/ui/dc1/services

参考