前言

      上一章说了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设计的时候就没深度的设计这块。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐