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 地址,默认是 localhostspring.cloud.consul.port
配置 consul 端口,默认是 8500spring.cloud.consul.config.enabled
是否启用 consul 的配置中心功能spring.cloud.consul.config.prefix
设置配置所在目录,默认值 configspring.cloud.consul.config.format
配置值的格式 YAML、FILES、PROPERTIES、默认 KEY-VALUEspring.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而不是hostnamespring.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使用笔记
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: