前言
上一章说了Spring Boot Admin(SBA)的client端自定义management.server.servlet.context-path、management.endpoints.web.base-path来解决一个Tomcat多个实例的问题。但是这个配置eureka instance是SBA Admin端通过eureka server获取的配置不能识别的,SBA Admin从client的配置获取不到监控管理路径,会使用默认/actuator,这显然不然拿到我们定制的信息采集接口。
1. 模拟client
笔者就使用上一章的demo,分别配置management.server.servlet.context-path、management.endpoints.web.base-path来调试源码来确认Spring Cloud的这个坑
配置management.endpoints.web.base-path,management.server.servlet.context-path同理(上一章就是配置的这个)
#server.port=8180
spring.application.name=BootClient
#spring.boot.admin.client.url=http://127.0.0.1:8082
eureka.client.service-url.defaultZone=http://127.0.0.1:8088/eureka
eureka.client.registry-fetch-interval-seconds=30
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.health-check-url-path=/bootClient/actuator/health
#eureka.instance.metadata-map.management.context-path=ROOT2
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoints.web.base-path=/bootClient/actuator
这里把 eureka.instance.metadata-map.management.context-path注释了
2. SBA Admin源码分析
在admin 端打上断点,查看Spring Boot Admin的官方说明,实际上是client与admin保持心跳方式,我们看admin端监听器,可以看到使用了webflux技术
/**
* Listener for Heartbeats events to publish all services to the instance registry.
*
* @author Johannes Edmeier
*/
public class InstanceDiscoveryListener {
跟踪registerInstance注册方法,这个方法是异步执行的定期心跳,可配置
protected Mono<InstanceId> registerInstance(ServiceInstance instance) {
try {
Registration registration = converter.convert(instance).toBuilder().source(SOURCE).build();
log.debug("Registering discovered instance {}", registration);
return registry.register(registration);
}
catch (Exception ex) {
log.error("Couldn't register instance for discovered instance ({})", toString(instance), ex);
return Mono.empty();
}
}
跟踪注册方法
@Override
public Registration convert(ServiceInstance instance) {
LOGGER.debug("Converting service '{}' running at '{}' with metadata {}", instance.getServiceId(),
instance.getUri(), instance.getMetadata());
return Registration.create(instance.getServiceId(), getHealthUrl(instance).toString())
.managementUrl(getManagementUrl(instance).toString()).serviceUrl(getServiceUrl(instance).toString())
.metadata(getMetadata(instance)).build();
}
这里面有注册信息的拼接,getHealthUrl(instance)与getManagementUrl(instance)
getHealthUrl(instance),是心跳接口获取URL,也调用了getManagementUrl(instance)
protected URI getHealthUrl(ServiceInstance instance) {
return UriComponentsBuilder.fromUri(getManagementUrl(instance)).path("/").path(getHealthPath(instance)).build()
.toUri();
}
getManagementUrl(instance), 获取数据信息接口
这里很关键
protected URI getManagementUrl(ServiceInstance instance) {
return UriComponentsBuilder.newInstance().scheme(getManagementScheme(instance))
.host(getManagementHost(instance)).port(getManagementPort(instance)).path("/")
.path(getManagementPath(instance)).build().toUri();
}
关键在于
.path(getManagementPath(instance)
前面是拼接http://hostname:port/;关键在于path,就是admin识别的client暴露的actuator接口信息URL
protected String getManagementPath(ServiceInstance instance) {
String managementPath = instance.getMetadata().get(DefaultServiceInstanceConverter.KEY_MANAGEMENT_PATH);
if (!isEmpty(managementPath)) {
return managementPath;
}
return this.managementContextPath;
}
调试代码,是从client的注册中心的客户端配置读取的,为空就使用默认,eureka server,consul配置都是
management.context-path
只是配置的前缀不一样,这个配置是map里获取的,配置的metadata是map结构。
源码看出
可配置项如上。
默认路径,单独的应用肯定是没问题的
我们如果没有配置,就使用默认
见证本质的地方,这个URL是错误的
页面上也可以看到
3. 解决方法
源码看了,解决方法就很简单了,只要SBA识别的路径正确就可以了。需要配置
eureka.instance.metadata-map.management.context-path
使这个路径地址 = management.server.servlet.context-path + management.endpoints.web.base-path + "/actuator"
配置
management.server.servlet.context-path
要注意,这个是servlet 的content-path,不能和Spring boot的server.context-path同时配置,否则在心跳时会拼接两个contentPath,但是eureka instance都是配置eureka.instance.metadata-map.management.context-path
#这年头Spring Boot的基础配置也变了
server.context-path #Spring Boot 1.X
server.servlet.context-path #Spring Boot 2.X
比如我配置
#server.port=8180
spring.application.name=BootClient
#spring.boot.admin.client.url=http://127.0.0.1:8082
eureka.client.service-url.defaultZone=http://127.0.0.1:8088/eureka
eureka.client.registry-fetch-interval-seconds=30
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.health-check-url-path=/bootClient/actuator/health
eureka.instance.status-page-url-path=/bootClient/actuator/info
eureka.instance.metadata-map.management.context-path=/bootClient/actuator
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoints.web.base-path=/bootClient/actuator
#management.server.servlet.context-path=/bootClient
页面正常
比如
#server.port=8180
spring.application.name=BootClient
server.servlet.context-path=/boot
#spring.boot.admin.client.url=http://127.0.0.1:8082
eureka.client.service-url.defaultZone=http://127.0.0.1:8088/eureka
eureka.client.registry-fetch-interval-seconds=30
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.health-check-url-path=/bootClient/actuator/health
eureka.instance.status-page-url-path=/bootClient/actuator/info
eureka.instance.metadata-map.management.context-path=/boot/bootClient/actuator
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoints.web.base-path=/bootClient/actuator
#management.server.servlet.context-path=/boot
笔者配置上
management.server.servlet.context-path=/boot
就会有两个contentpath了,心跳不通过,数据是可以获取到的,上一章这个参数在Tomcat多实例服务器发挥作用了。
总结
调试源码就可以解决这种奇特的问题,但是Spring Boot Admin的路径管理实在太麻烦了,各种拼接;当然使用默认不用配置,但必须 单应用单实例。不过一般微服务就是这样,所以估计Spring Boot Admin设计的时候就没深度的设计这块。