当前位置 : 首页 » 文章分类 :  开发  »  Consul

Consul

Consul 使用笔记

Consul 是 HashiCorp 公司推出的开源软件,提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之 Consul 提供了一种完整的服务网格解决方案。

Consul 简介和快速入门
https://book-consul-guide.vnzmi.com/


Consul 内部的选举实现/CAP保证

Consensus Protocol
https://www.consul.io/docs/architecture/consensus

通过consul的session实现leader选举

Application Leader Election with Sessions
https://learn.hashicorp.com/tutorials/consul/application-leader-elections


consul分布式锁

基于Consul的分布式锁主要利用Key/Value存储API中的acquire和release操作来实现。acquire和release操作是类似Check-And-Set的操作:

acquire操作只有当锁不存在持有者时才会返回true,并且set设置的Value值,同时执行操作的session会持有对该Key的锁,否则就返回false
release操作则是使用指定的session来释放某个Key的锁,如果指定的session无效,那么会返回false,否则就会set设置Value值,并返回true

基于Consul的分布式锁实现
http://blog.didispace.com/spring-cloud-consul-lock-and-semphore/


OperationException

com.ecwid.consul.v1.OperationException: OperationException(statusCode=500, statusMessage=’Internal Server Error’, statusContent=’Check ‘service:app-8778’ is in critical state’)
at com.ecwid.consul.v1.session.SessionConsulClient.sessionCreate(SessionConsulClient.java:59)
at com.ecwid.consul.v1.session.SessionConsulClient.sessionCreate(SessionConsulClient.java:44)
at com.ecwid.consul.v1.ConsulClient.sessionCreate(ConsulClient.java:782)


Spring Cloud Consul

Spring Cloud Consul 具有如下特性:
支持服务治理:Consul 作为注册中心时,微服务中的应用可以向 Consul 注册自己,并且可以从 Consul 获取其他应用信息;
支持客户端负责均衡:包括 Ribbon 和 Spring Cloud LoadBalancer;
支持 Zuul:当 Zuul 作为网关时,可以从 Consul 中注册和发现应用;
支持分布式配置管理:Consul 作为配置中心时,使用键值对来存储配置信息;
支持控制总线:可以在整个微服务系统中通过 Control Bus 分发事件消息。

默认 instance-id 生成逻辑

    public static String getInstanceId(ConsulDiscoveryProperties properties, ApplicationContext context) {
        if (!StringUtils.hasText(properties.getInstanceId())) {
            return normalizeForDns(
                    IdUtils.getDefaultInstanceId(context.getEnvironment(), properties.isIncludeHostnameInInstanceId()));
        }
        return normalizeForDns(properties.getInstanceId());
    }

SpringBoot 配置 Consul 注册中心

maven添加依赖

spring-cloud-starter-consul-discovery 的版本信息在 spring-cloud-dependencies -> spring-cloud-consul-dependencies 中被 Managed ,如果引入了 spring-cloud-dependencies 就不需要单独配置版本信息。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

consul 配置中心配置

如果要使用 consul 的配置中心,要添加 bootstrap 配置文件 bootstrap.yml ,因为要在其他配置之前获取配置中心的配置项
所以这里主要是 consul 配置中心的配置项
spring.cloud.consul.host 配置 consul 地址,默认是 localhost
spring.cloud.consul.port 配置 consul 端口,默认是 8500
spring.cloud.consul.config.enabled 是否启用 consul 的配置中心功能
spring.cloud.consul.config.prefix 设置配置所在目录,默认值 config
spring.cloud.consul.config.format 配置值的格式 YAML、FILES、PROPERTIES、默认 KEY-VALUE
spring.cloud.consul.config.data-key 表示 consul 上面的 KEY 值(或者说文件的名字),默认是 data

consul 服务注册配置

之后在应用配置 application.yml 或 application.properties 中增加 spring cloud consul 相关配置
spring.cloud.consul.discovery.enabled 启用服务发现
spring.cloud.consul.discovery.register 启用服务注册
spring.cloud.consul.discovery.deregister 服务停止时取消注册

spring.cloud.consul.discovery.prefer-ip-address 表示注册时使用IP而不是hostname
spring.cloud.consul.discovery.health-check-interval 健康检查频率
spring.cloud.consul.discovery.health-check-path 健康检查路径
spring.cloud.consul.discovery.health-check-critical-timeout 健康检查失败多长时间后,取消注册
spring.cloud.consul.discovery.instance-id 服务注册标识

如果只用做注册中心,下面配置即可

spring.application.name=my-app
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.enabled=true
spring.cloud.consul.discovery.register=true
spring.cloud.consul.discovery.service-name=${spring.application.name}

不加 @EnableDiscoveryClient 也能将自己注册到 consul


Check 健康检查

Checks
https://www.consul.io/docs/discovery/checks

check 是用来做服务的健康检查的,可以有多个,也可以没有,支持多种方式的检查。check 定义在配置文件中,或运行时通过 HTTP 接口添加。

script, http, tcp 等类型的 check 是 consul 主动去检查服务的健康状况,需要提供 interval 变量;ttl check 是服务主动向 consul 报告自己的健康状况

CheckID 默认是 service:<service-id>

Script + Interval

Script 类型 check 需要提供 Script 脚本和 interval 变量
例如

{
  "check": {
    "id": "mem-util",
    "name": "Memory utilization",
    "args": ["/usr/local/bin/check_mem.py", "-limit", "256MB"],
    "interval": "10s",
    "timeout": "1s"
  }
}

HTTP + Interval

HTTP 类型 check 需要提供 http 接口地址和 Interval 字段
例如

{
  "check": {
    "id": "api",
    "name": "HTTP API on port 5000",
    "http": "https://localhost:5000/health",
    "tls_server_name": "",
    "tls_skip_verify": false,
    "method": "POST",
    "header": {"Content-Type": ["application/json"]},
    "body": "{\"method\":\"health\"}",
    "interval": "10s",
    "timeout": "1s"
  }
}

TCP + Interval

TCP 类型 check 需要提供 tcp 的 ip:port 和 Interval 字段
例如

{
  "check": {
    "id": "ssh",
    "name": "SSH TCP on port 22",
    "tcp": "localhost:22",
    "interval": "10s",
    "timeout": "1s"
  }
}

Consul 自动注册时 Http Check中的host获取错误

问题:
非主机网络的 pod 中,Spring Consul 自动注册的 Http Check url 中的 hostname 错误,获取的不是机器 hostname 而是 podname

排查:
Spring Consul 自动注册在 ConsulAutoRegistration 中,其中设置 http check 部分如下:

package org.springframework.cloud.consul.serviceregistry;

public class ConsulAutoRegistration extends ConsulRegistration {
    public static NewService.Check createCheck(Integer port, HeartbeatProperties ttlConfig,
            ConsulDiscoveryProperties properties) {
        ...
        check.setHttp(String.format("%s://%s:%s%s", properties.getScheme(), properties.getHostname(), port,
                    properties.getHealthCheckPath()));
        ...
    }
}

hostname 来自 ConsulDiscoveryProperties.hostname,这个属性在构造方法中通过 IP 工具类 InetUtils 获取 hostname 进行赋值

package org.springframework.cloud.consul.discovery;

@ConfigurationProperties(ConsulDiscoveryProperties.PREFIX)
public class ConsulDiscoveryProperties {
    public static final String PREFIX = "spring.cloud.consul.discovery";

    private String hostname;
    private String healthCheckPath = "/actuator/health";
    private boolean preferIpAddress = false;

    public String getHostname() {
        return this.preferIpAddress ? this.ipAddress : this.hostname;
    }

    private ConsulDiscoveryProperties() {
        this(new InetUtils(new InetUtilsProperties()));
    }

    public ConsulDiscoveryProperties(InetUtils inetUtils) {
        this.managementTags.add(MANAGEMENT);
        // 获取 ip 和 host
        this.hostInfo = inetUtils.findFirstNonLoopbackHostInfo();
        this.ipAddress = this.hostInfo.getIpAddress();
        this.hostname = this.hostInfo.getHostname();
    }
}

InetUtils 工具类获取 host 过程如下,最终是通过 java.net.InetAddress.getHostName() 获取 host

package org.springframework.cloud.commons.util;

public class InetUtils implements Closeable {
    public HostInfo findFirstNonLoopbackHostInfo() {
        // 第一个非回环ip地址,一般都可获取到
        InetAddress address = findFirstNonLoopbackAddress();
        if (address != null) {
            // 获取 HostInfo
            return convertAddress(address);
        }
        HostInfo hostInfo = new HostInfo();
        hostInfo.setHostname(this.properties.getDefaultHostname());
        hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
        return hostInfo;
    }

    public HostInfo convertAddress(final InetAddress address) {
        HostInfo hostInfo = new HostInfo();
        // InetAddress.getHostName() 获取 host
        Future<String> result = this.executorService.submit(address::getHostName);

        String hostname;
        try {
            hostname = result.get(this.properties.getTimeoutSeconds(), TimeUnit.SECONDS);
        }
        catch (Exception e) {
            this.log.info("Cannot determine local hostname");
            hostname = "localhost";
        }
        hostInfo.setHostname(hostname);
        hostInfo.setIpAddress(address.getHostAddress());
        return hostInfo;
    }
}

原因:
最后发现 consul 注册时 host 获取过程没问题,是 非主机网络 pod 容器内 hostname 命令结果本身就是 pod名,不是主机名。

解决:
设置 spring.cloud.consul.discovery.preferIpAddress = true,优先使用 ip 代理 hostname 来组成注册服务的健康检查url

Time to Live (TTL)

{
  "check": {
    "id": "web-app",
    "name": "Web App Status",
    "notes": "Web app does a curl internally every 10 seconds",
    "ttl": "30s"
  }
}

Docker + Interval

gRPC + Interval

H2ping + Interval

Alias


Intel/M1 Mac brew 安装 Consul

安装
brew install consul
Intel Mac 安装目录 /usr/local/Cellar/consul/1.10.0
M1 Mac 安装目录 /opt/homebrew/Cellar/consul/1.11.4

consul -v
Consul v1.10.0
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

后台启动 consul brew services start consul
重启 consul brew services restart consul

前台启动
consul agent -dev

访问 consul web 界面
http://localhost:8500/

https://learn.hashicorp.com/consul/getting-started/install


Mac brew Consul ACL 配置

1、在安装目录 /usr/local/Cellar/consul/1.10.0 中创建 config.d 配置目录
/usr/local/Cellar/consul/1.10.0/config.d

2、在 config.d 配置目录中创建 acl.json 文件,内容如下:

{
  "acl":{
      "enabled" : true,
      "default_policy" : "deny",
      "enable_token_persistence":true,
      "enable_key_list_policy":true,
      "down_policy" : "extend-cache",
      "tokens" :{
          "master": "my-custom-token"
      }
  }
}

3、修改 /usr/local/Cellar/consul/1.10.0/homebrew.mxcl.consul.plist
修改后的 ProgramArguments 如下,增加了 -config-dir 参数

<key>ProgramArguments</key>
<array>
  <string>/usr/local/opt/consul/bin/consul</string>
  <string>agent</string>
  <string>-dev</string>
  <string>-bind</string>
  <string>127.0.0.1</string>
  <string>-config-dir</string>
  <string>/usr/local/Cellar/consul/1.10.0/config.d</string>
</array>

4、brew 启动 consul
brew services start consul


Consul 开启 ACL Token

Secure Consul with Access Control Lists (ACLs)
https://learn.hashicorp.com/tutorials/consul/access-control-setup-production

权限控制列表 ACL
https://kingfree.gitbook.io/consul/day-1-operations/acl-guide

在所有 Consul 结点上启用ACL

首先需要开启 Consul 的 ACL 功能,如果有多个 Consul 结点,不管是 Server 还是 Agent, 全都要开启 ACL.

1、创建一个 Consul 配置文件目录,Consul 会读取配置目录中的所有配置文件。
通常是创建一个 /etc/consul.d 目录( Unix 系统中 .d 后缀隐含的意思就是“这是一个包含配置文件的目录”)

2、创建 acl.json 文件,内容如下,放入 config.d 目录

{
  "acl":{
      "enabled" : true,
      "default_policy" : "deny",
      "enable_token_persistence":true,
      "enable_key_list_policy":true,
      "down_policy" : "extend-cache",
      "tokens" :{
          "master": "my-custom-token"
      }
  }
}

这里设置了默认策略是 deny, 表示我们运行在白名单模式。并开启了 token 持久化。

3、然后通过启动命令的 -config-dir 参数指定配置文件目录。
/usr/local/opt/consul/bin/consul agent -dev -bind 127.0.0.1 -config-dir /usr/local/Cellar/consul/1.10.0/config.d


创建 bootstrap token

Consul 中的 bootstrap token 相当于管理员密码,是一个有无限权限的管理 token.

有两种方式创建 bootstrap token:
1、可以直接在启动配置 acl.json 中配置 acl.tokens.master, 写入固定值。
2、开启 Consul ACL 并启动 Consul 后,通过 consul acl bootstrap 命令设置初始 bootstrap token, 自动生成。

注意,bootstrap token 只能创建一个,在创建 master token 之后该 token 就会被禁用。当 ACL 成功引导并启动后,可以通过 ACL API 来管理这些 token。

consul acl bootstrap
AccessorID:       c4dfbc5c-25aa-ed72-de1b-1e99627de423
SecretID:         9d2f49d1-907f-76b9-bd5a-72c61e9862cd
Description:      Bootstrap Token (Global Management)
Local:            false
Create Time:      2021-08-19 14:45:23.912955 +0800 CST
Policies:
   00000000-0000-0000-0000-000000000001 - global-management

consul 命令加 -token 参数

开启 ACL 后所有的 consul 命令都必须带上 token 参数,参数值就是这里生成的 SecretID, 否则无结果。
比如 consul members 不带 token 参数就查不到成员了,要改为 consul members -token xx

consul members -token 9d2f49d1-907f-76b9-bd5a-72c61e9862cd
Node             Address         Status  Type    Build   Protocol  DC   Segment
B000000133178DP  127.0.0.1:8301  alive   server  1.10.0  2         dc1  <all>

HTTP 接口加 token 参数或 X-Consul-Token 头

http 接口也都需要带上 token 参数。不带 token 参数无法获取数据。
比如 http 列出 node 的接口,不带 token 参数时返回为空

# curl "http://localhost:8500/v1/catalog/nodes"
[]

增加 token 参数后可看到集群节点。

可以放入 query 参数 token 中,例如:

curl "localhost:8500/v1/catalog/nodes?token=9d2f49d1-907f-76b9-bd5a-72c61e9862cd"
[
    {
        "ID": "68d1a95d-acda-60ea-9f41-88164ccd74b7",
        "Node": "B000000133178DP",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "TaggedAddresses": null,
        "Meta": null,
        "CreateIndex": 13,
        "ModifyIndex": 13
    }
]

或者放入 X-Consul-Token header 中

curl localhost:8500/v1/catalog/nodes --header 'X-Consul-Token:9d2f49d1-907f-76b9-bd5a-72c61e9862cd'
[
    {
        "ID": "68d1a95d-acda-60ea-9f41-88164ccd74b7",
        "Node": "B000000133178DP",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "TaggedAddresses": null,
        "Meta": null,
        "CreateIndex": 13,
        "ModifyIndex": 13
    }
]

设置 CONSUL_HTTP_TOKEN 环境变量

3、或者设置 CONSUL_HTTP_TOKEN 环境变量,export CONSUL_HTTP_TOKEN=xxx
之后 consul 命令不需要单独带 -token 参数,但 HTTP 接口好像还是得加 token 才行(token 参数或 header 形式)。


Consul 命令行

Consul Commands (CLI)
https://www.consul.io/commands

开启 ACL 后,所有 consul 命令都要带上 -token xxx 参数,这里的 xxx 是 consul acl bootstrap 生成的 token


members 成员

Consul Members
https://www.consul.io/commands/members

consul members 查看成员

如果开启了认证,需要加 -token xxx

consul members -token 9d2f49d1-907f-76b9-bd5a-72c61e9862cd
Node             Address         Status  Type    Build   Protocol  DC   Segment
B000000133178DP  127.0.0.1:8301  alive   server  1.10.0  2         dc1  <all>

ACL/Token

Consul ACL Tokens
https://www.consul.io/commands/acl/token

consul acl token list 查看所有token

Consul ACL Token List
https://www.consul.io/commands/acl/token/list

或者通过 bootstrap 登录 Consul ui 界面后,在界面上也可以看。

consul acl token list -token 9d2f49d1-907f-76b9-bd5a-72c61e9862cd
AccessorID:       c4dfbc5c-25aa-ed72-de1b-1e99627de423
Description:      Bootstrap Token (Global Management)
Local:            false
Create Time:      2021-08-19 14:45:23.912955 +0800 CST
Legacy:           false
Policies:
   00000000-0000-0000-0000-000000000001 - global-management

AccessorID:       00000000-0000-0000-0000-000000000002
Description:      Anonymous Token
Local:            false
Create Time:      2021-08-19 14:25:57.694559 +0800 CST
Legacy:           false

第一个 bootstrap 令牌,则是一个有无限权限的管理 token,因为它拥有 global-management 策略
第二个 002 账号是由 Consul 自动创建的,但它没有任何权限,表示匿名账号。


Consul HTTP API

API Introduction
https://www.consul.io/api-docs

开启 ACL 后,所有 HTTP 接口都要带上 ?token=xxx--header 'X-Consul-Token:xxx',这里的 xxx 是 consul acl bootstrap 生成的 token,或者通过启动参数 acl.tokens.master 生成的 token

Consul HTTP API 结构

HTTP API Structure
https://www.consul.io/api-docs/index

认证

版本前缀/v1

所有 URL 都要加上版本前缀 /v1/

HTTP方法


Catalog 目录

/v1/catalog/nodes 列出节点

List Nodes
https://www.consul.io/api-docs/catalog#list-nodes

curl "localhost:8500/v1/catalog/nodes?token=9d2f49d1-907f-76b9-bd5a-72c61e9862cd"
[
    {
        "ID": "68d1a95d-acda-60ea-9f41-88164ccd74b7",
        "Node": "B000000133178DP",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "TaggedAddresses": null,
        "Meta": null,
        "CreateIndex": 13,
        "ModifyIndex": 13
    }
]

/v1/catalog/services 列出service

List Services
https://www.consul.io/api-docs/catalog#list-services
返回数据中心的全部 service

curl “http://localhost:8500/v1/catalog/services?token=xx"


Agent/Service 服务

/v1/agent/services 列出Service

https://www.consul.io/api-docs/agent/service#list-services
返回所有注册在本地 agent 上的 service。
注意本地 agent 上的 service 列表和 catalog 中的 service 列表可能不同,这通常是由于暂时没有选出 leader 导致的分布式不一致

curl "http://localhost:8500/v1/agent/services"
curl "http://localhost:8500/v1/agent/services?token=9d2f49d1-907f-76b9-bd5a-72c61e9862cd"
curl  "http://localhost:8500/v1/agent/services?token=9d2f49d1-907f-76b9-bd5a-72c61e9862cd"
{
    "myapp1-8768": {
        "ID": "myapp1-8768",
        "Service": "myapp1",
        "Tags": [
            "secure=false"
        ],
        "Meta": {},
        "Port": 8768,
        "Address": "172.24.220.179",
        "SocketPath": "",
        "TaggedAddresses": {
            "lan_ipv4": {
                "Address": "172.24.220.179",
                "Port": 8768
            },
            "wan_ipv4": {
                "Address": "172.24.220.179",
                "Port": 8768
            }
        },
        "Weights": {
            "Passing": 1,
            "Warning": 1
        },
        "EnableTagOverride": false,
        "Datacenter": "dc1"
    },
    "myapp2-8631": {
        "ID": "myapp2-8631",
        "Service": "myapp2",
        "Tags": [
            "secure=false"
        ],
        "Meta": {},
        "Port": 8631,
        "Address": "172.24.220.179",
        "SocketPath": "",
        "TaggedAddresses": {
            "lan_ipv4": {
                "Address": "172.24.220.179",
                "Port": 8631
            },
            "wan_ipv4": {
                "Address": "172.24.220.179",
                "Port": 8631
            }
        },
        "Weights": {
            "Passing": 1,
            "Warning": 1
        },
        "EnableTagOverride": false,
        "Datacenter": "dc1"
    }
}

PUT /v1/agent/service/deregister 注销Service

Deregister Service
https://www.consul.io/api-docs/agent/service#deregister-service

curl -X PUT http://localhost:8500/v1/agent/service/deregister/{service-id}
curl -X PUT "http://localhost:8500/v1/agent/service/deregister/{service-id}?token=9d2f49d1-907f-76b9-bd5a-72c61e9862cd"

用 PUT 请求 Consul 的这个 deregister 接口,附上实例的 id 就可以成功注销掉实例了(注意是实例的 id, 不是服务名,即服务名+一段唯一字符串)。
有 ACL 认证的 Consul 需要加 token 参数或 header, 否则会报 permission denied
实例 id 可以在 consul 页面上看到。

注意:要删哪个Service id实例,先登录到对应的机器上,再执行,在其他机器上的 local consul agent可能删不掉,报 Unknown service xxx

consul不会自动deregister无效服务

当在 Spring Cloud 应用中使用 Consul 来实现服务治理时,由于 Consul 不会自动将不可用的服务实例注销掉(deregister), 这使得在实际使用过程中,可能因为一些操作失误、环境变更等原因让 Consul 中存在一些无效实例信息,而这些实例在 Consul 中会长期存在,并处于断开状态。它们虽然不会影响到正常的服务消费过程,但是它们会干扰我们的监控,所以我们可以实现一个清理接口,在确认故障实例可以清理的时候进行调用来将这些无效信息清理掉。


Agent/Check 检查

/v1/agent/checks 列出check

List Checks
https://www.consul.io/api-docs/agent/check#list-checks

curl "localhost:8500/v1/agent/checks"
curl "localhost:8500/v1/agent/checks?token=my-token"

结果是一个 map, key 是 check-id, value 是 check 结构,例如

{
    "service:myapp1-127.0.0.1-8081":{
        "Node":"centos",
        "CheckID":"service:myapp1-127.0.0.1-8081",
        "Name":"Service 'myapp1' check",
        "Status":"passing",
        "Notes":"",
        "Output":"HTTP GET http://127.0.0.1:8081/actuator/health: 200  Output: {\"status\":\"UP\"}",
        "ServiceID":"myapp1-8081",
        "ServiceName":"myapp1",
        "ServiceTags":[
            "secure=false"
        ],
        "Definition":{

        },
        "CreateIndex":0,
        "ModifyIndex":0
    },
    "service:myapp2-127.0.0.1-8082":{
        "Node":"centos",
        "CheckID":"service:myapp2-127.0.0.1-8082",
        "Name":"Service 'myapp2' check",
        "Status":"passing",
        "Notes":"",
        "Output":"TCP connect 127.0.0.1:8082: Success",
        "ServiceID":"myapp2-127.0.0.1-8082",
        "ServiceName":"myapp2",
        "ServiceTags":[

        ],
        "Definition":{

        },
        "CreateIndex":0,
        "ModifyIndex":0
    }
}

Health 健康

Health HTTP Endpoint
https://www.consul.io/api-docs/health

/health 端点可查询健康相关的信息

/health/service/service-id 查询服务实例

查询指定的健康服务实例

curl "http://localhost:8500/v1/health/service/{service-id}"
curl "http://localhost:8500/v1/health/service/{service-id}?token=my-token"

failFast

spring.cloud.consul.config.failFast=false 设置为 false 时,允许 consul 配置中心不可用时不影响 spring 服务启动。


上一篇 Spring-AOP

下一篇 Chrome使用笔记

阅读
评论
4.6k
阅读预计22分钟
创建日期 2019-06-07
修改日期 2023-02-08
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论