Spring Boot Admin & management.endpoints.web.base-path
基于前一篇 Spring Boot Admin & Nacos 的博客,如果有服务通过 management.endpoints.web.base-path
属性修改了 Actuator 暴露的端点路径,SBA Server 检查服务健康状态时会失败。原因是 SBA Server 请求的路径和实际暴露的路径不一致。
假设我们将 management.endpoints.web.base-path 设置为 act ,则对应 health 端点实际的路径为 /act/health ,但是 SBA Server 仍然访问的是 /actuator/health 。
查看 Nacos 中注册的服务信息,可以看到服务的元数据( metadata )中是有 management.endpoints.web.base-path=/act
这条记录的。
在 SBA Server 中,服务实例的端点地址是通过 DefaultServiceInstanceConverter
转换的,获取端点路径的是 getManagementPath()
方法,详细代码如下。
private static final String KEY_MANAGEMENT_PATH = "management.context-path";
/**
* Default context-path to be appended to the url of the discovered service for the
* managment-url.
*/
private String managementContextPath = "/actuator";
protected String getManagementPath(ServiceInstance instance) {
String managementPath = instance.getMetadata()
.get(DefaultServiceInstanceConverter.KEY_MANAGEMENT_PATH);
if (hasText(managementPath)) {
return managementPath;
}
return this.managementContextPath;
}
可以看到 SBA Server 使用了 management.context-path 元数据作为自定义端点地址的手段(SBA Server 结合 Spring Cloud Discovery 时支持的元数据见官方文档)。
另外还有一个要注意的是 managementContextPath 字段的值,这个值默认为
/actuator
,但是可以通过 spring.boot.admin.discovery.converter.management-context-path 属性配置。
management.context-path 这个属性在 Nacos 服务的元数据中也是有的,但对应的并不是 management.endpoints.web.base-path 的值,而是 management.server.servlet.context-path 的值(仅在配置了 management.server.port 时才会出现在元数据中)。
具体的代码见 NacosRegistration
的 init()
方法:
/**
* The metadata key of management port.
*/
public static final String MANAGEMENT_PORT = "management.port";
/**
* The metadata key of management context-path.
*/
public static final String MANAGEMENT_CONTEXT_PATH = "management.context-path";
/**
* The metadata key of management address.
*/
public static final String MANAGEMENT_ADDRESS = "management.address";
/**
* The metadata key of management endpoints web base path.
*/
public static final String MANAGEMENT_ENDPOINT_BASE_PATH = "management.endpoints.web.base-path";
@PostConstruct
public void init() {
Map<String, String> metadata = nacosDiscoveryProperties.getMetadata();
Environment env = context.getEnvironment();
String endpointBasePath = env.getProperty(MANAGEMENT_ENDPOINT_BASE_PATH);
if (StringUtils.hasLength(endpointBasePath)) {
metadata.put(MANAGEMENT_ENDPOINT_BASE_PATH, endpointBasePath);
}
Integer managementPort = ManagementServerPortUtils.getPort(context);
if (null != managementPort) {
metadata.put(MANAGEMENT_PORT, managementPort.toString());
String contextPath = env
.getProperty("management.server.servlet.context-path");
String address = env.getProperty("management.server.address");
if (StringUtils.hasLength(contextPath)) {
metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath);
}
if (StringUtils.hasLength(address)) {
metadata.put(MANAGEMENT_ADDRESS, address);
}
}
if (null != nacosDiscoveryProperties.getHeartBeatInterval()) {
metadata.put(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
nacosDiscoveryProperties.getHeartBeatInterval().toString());
}
if (null != nacosDiscoveryProperties.getHeartBeatTimeout()) {
metadata.put(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
nacosDiscoveryProperties.getHeartBeatTimeout().toString());
}
if (null != nacosDiscoveryProperties.getIpDeleteTimeout()) {
metadata.put(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
nacosDiscoveryProperties.getIpDeleteTimeout().toString());
}
customize(registrationCustomizers);
}
从 SBA Server 和 Nacos Discovery 各自的角度来看,这样的设计其实并没有问题,只是 SBA Server 默认的 ServiceInstanceConverter
-- DefaultServiceInstanceConverter
-- 没有支持 management.endpoints.web.base-path 元数据而已。
下面举个具体的例子,如果这两个字段做了如下配置:
management:
endpoints:
web:
base-path: /path2
server:
port: 9999
servlet:
context-path: /path1
则服务暴露的健康端口地址为 /path1/path2/health 。
此时 metadata 的内容为:
{
"management.endpoints.web.base-path": "/path2",
"preserved.register.source": "SPRING_CLOUD",
"management.port": "9999",
"management.context-path": "/path1"
}
而在 SBA Server 中访问的端点地址则是 /path1/health 。
解决方案
既然默认的 DefaultServiceInstanceConverter
不支持,添加一个 Nacos Discovery 专用的 ServiceInstanceConverter
就可以了。
自定义的 NacosServiceInstanceConverter.java 代码如下:
import com.alibaba.nacos.api.utils.StringUtils;
import de.codecentric.boot.admin.server.cloud.discovery.DefaultServiceInstanceConverter;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import static org.springframework.util.StringUtils.*;
/**
* @author 佳佳
*/
public class NacosServiceInstanceConverter extends DefaultServiceInstanceConverter {
private static final String KEY_MANAGEMENT_CONTEXT_PATH = "management.context-path";
private static final String KEY_MANAGEMENT_BASE_PATH = "management.endpoints.web.base-path";
public static final char URI_SEGMENT_CHARACTER = '/';
@Override
protected String getManagementPath(ServiceInstance instance) {
String contextPath = instance.getMetadata().get(KEY_MANAGEMENT_CONTEXT_PATH);
String basePath = instance.getMetadata().get(KEY_MANAGEMENT_BASE_PATH);
return UriComponentsBuilder.newInstance().pathSegment(
hasText(contextPath) ? trimPath(contextPath) : StringUtils.EMPTY,
hasText(basePath) ? trimPath(basePath) : this.getManagementContextPath()
).build().getPath();
}
private static String trimPath(String path) {
return trimTrailingCharacter(
trimLeadingCharacter(path, URI_SEGMENT_CHARACTER),
URI_SEGMENT_CHARACTER
);
}
}
对应的配置类 ServiceInstanceConverterConfiguration.java :
import de.codecentric.boot.admin.server.cloud.discovery.DefaultServiceInstanceConverter;
import de.codecentric.boot.admin.server.cloud.discovery.ServiceInstanceConverter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
/**
* @author 佳佳
*/
@Configuration
public class ServiceInstanceConverterConfiguration {
@Bean
@ConditionalOnMissingBean({ServiceInstanceConverter.class})
@ConfigurationProperties(prefix = "spring.boot.admin.discovery.converter")
@Order(0)
public DefaultServiceInstanceConverter serviceInstanceConverter() {
return new NacosServiceInstanceConverter();
}
}
附 1. management.server.servlet.context-path 已废弃
在 Spring Boot 2.6.11 (具体从哪个版本开始的不太清楚)中 management.server.servlet.context-path 属性已经废弃了,取而代之的是 management.server.base-path 属性( management.server 的其他配置项见 ManagementServerProperties
)。但是在 nacos-discovery 2021.0.4.0 中仍然使用的是这个属性值,此时如果要配置 context-path 可采用如下形式做兼容,以保持两个属性的值一致。
management:
endpoints:
web:
base-path: /path2
server:
port: 9999
base-path: /path1
servlet:
context-path: ${management.server.base-path}
附 2. 另一种解决方案
上面的解决的方案是修改 SBA Server 服务的代码,以兼容 Nacos Discovery 的元数据。那么反过来其实也可以修改 Nacos Discovery 的元数据来兼容 SBA Server(只是这样修改的地方会比较多,并不推荐)。
对此也有两种实现方式:
- 自定义一个
NacosRegistration
来覆盖默认的注册处理; - 在各个服务的 spring.cloud.nacos.discovery.metadata 配置中自定义 management.context-path 元数据的值。