Skip to content

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() 方法,详细代码如下。

java
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 时才会出现在元数据中)。

具体的代码见 NacosRegistrationinit() 方法:

java
/**
 * 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 元数据而已。

下面举个具体的例子,如果这两个字段做了如下配置:

yaml
management:
  endpoints:
    web:
      base-path: /path2
  server:
    port: 9999
    servlet:
      context-path: /path1

则服务暴露的健康端口地址为 /path1/path2/health

此时 metadata 的内容为:

json
{
    "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 代码如下:

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

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 可采用如下形式做兼容,以保持两个属性的值一致。

yaml
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(只是这样修改的地方会比较多,并不推荐)。

对此也有两种实现方式:

  1. 自定义一个 NacosRegistration 来覆盖默认的注册处理;
  2. 在各个服务的 spring.cloud.nacos.discovery.metadata 配置中自定义 management.context-path 元数据的值。

参考链接

Page Layout Max Width

Adjust the exact value of the page width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the page layout
A ranged slider for user to choose and customize their desired width of the maximum width of the page layout can go.

Content Layout Max Width

Adjust the exact value of the document content width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the content layout
A ranged slider for user to choose and customize their desired width of the maximum width of the content layout can go.