spring-cloud(二十三)集成admin监控
分类:spring-cloud
阅读:16
作者:皇太极
发布:2019-09-06 16:39:29

最近在将服务往spring-cloud上迁移是也遇到了很多问题

记录下在迁移spring-boot-admin时遇到的两个问题 ?

  1. 微服务在往eureka上注册时使用的https,但是证书是自己颁发的,导致无法监控
  2. 在spring-boot-admin页面监控的服务无法加载到Logfile这个选项卡

https证书问题

由于证书装载部分有公司标示信息,此处简单处理为信任全部证书(即忽略证书验证)

首先第一反应就是在调用请求的地方把证书装载进去,经查找spring服务调用采用的是webclient,这就so easy了,直接这个webclient初始化的地方进行重写。
然而真实场景并非如此,查看源代码发现在 AdminServerAutoConfiguration 中有配置 InstanceWebClient 但是这个实例没有提供扩展点让我们来将ssl证书装载进去。

  1. de.codecentric.boot.admin.server.confi.AdminServerAutoConfiguration
  2. @Bean
  3. @ConditionalOnMissingBean
  4. public InstanceWebClient instanceWebClient(HttpHeadersProvider httpHeadersProvider, ObjectProvider<List<InstanceExchangeFilterFunction>> filtersProvider) {
  5. List<InstanceExchangeFilterFunction> filters = (List)filtersProvider.getIfAvailable(Collections::emptyList);
  6. WebClientCustomizer customizer = (webClient) -> {
  7. filters.forEach((instanceFilter) -> {
  8. webClient.filter(InstanceExchangeFilterFunctions.toExchangeFilterFunction(instanceFilter));
  9. });
  10. };
  11. return new InstanceWebClient(httpHeadersProvider, this.adminServerProperties.getMonitor().getConnectTimeout(), this.adminServerProperties.getMonitor().getReadTimeout(), customizer);
  12. }

再看看InstanceWebClient代码如下

  1. de.codecentric.boot.admin.server.web.client.InstanceWebClient
  2. //关键代码
  3. private static WebClient createDefaultWebClient(Duration connectTimeout, Duration readTimeout, WebClientCustomizer customizer) {
  4. ReactorClientHttpConnector connector = new ReactorClientHttpConnector((options) -> {
  5. Builder var10000 = (Builder)((Builder)options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int)connectTimeout.toMillis())).compression(true).afterNettyContextInit((ctx) -> {
  6. ctx.addHandlerLast(new ReadTimeoutHandler(readTimeout.toMillis(), TimeUnit.MILLISECONDS));
  7. });
  8. });
  9. org.springframework.web.reactive.function.client.WebClient.Builder builder = WebClient.builder().clientConnector(connector).defaultHeader("Accept", new String[]{"application/vnd.spring-boot.actuator.v2+json", "application/vnd.spring-boot.actuator.v1+json", "application/json"});
  10. customizer.customize(builder);
  11. return builder.build();
  12. }

可以看出这个地方在创建 WebClient 的时候将修饰符置为私有了,导致无法执行在外部进行重写。没有提供好的扩展点让我们在外部可以对 ClientHttpConnector 进行扩展。

所以此时我们能做的就只能是重写new WebClient() 操作。
现在有两种方案来解决

  1. 将整个类拷贝到项目中,包名也要保留和原类一致
  1. package de.codecentric.boot.admin.server.web.client;
  2. import de.codecentric.boot.admin.server.domain.entities.Instance;
  3. import io.netty.channel.ChannelOption;
  4. import io.netty.handler.ssl.SslContext;
  5. import io.netty.handler.ssl.SslContextBuilder;
  6. import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
  7. import io.netty.handler.timeout.ReadTimeoutHandler;
  8. import reactor.core.publisher.Mono;
  9. import java.time.Duration;
  10. import java.util.concurrent.TimeUnit;
  11. import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
  12. import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
  13. import org.springframework.http.HttpHeaders;
  14. import org.springframework.http.MediaType;
  15. import org.springframework.http.client.reactive.ReactorClientHttpConnector;
  16. import org.springframework.web.reactive.function.client.WebClient;
  17. import javax.net.ssl.SSLException;
  18. /**
  19. * 修复eureka中有注册为https的服务,导致证书不受信任,此处演示环境仅仅做忽略证书处理
  20. *
  21. * 由于官方提供的WebClient.createDefaultWebClient 为私有方法,外部无法进行方法覆盖,故只能将整个类重写
  22. *
  23. * @author JingjingMa
  24. */
  25. public class InstanceWebClient {
  26. private final WebClient webClient;
  27. public InstanceWebClient(HttpHeadersProvider httpHeadersProvider) {
  28. this(httpHeadersProvider, Duration.ofSeconds(2), Duration.ofSeconds(5));
  29. }
  30. public InstanceWebClient(HttpHeadersProvider httpHeadersProvider, Duration connectTimeout, Duration readTimeout) {
  31. this(httpHeadersProvider, connectTimeout, readTimeout, builder -> {
  32. });
  33. }
  34. public InstanceWebClient(HttpHeadersProvider httpHeadersProvider,
  35. Duration connectTimeout,
  36. Duration readTimeout,
  37. WebClientCustomizer customizer) {
  38. this(createDefaultWebClient(connectTimeout, readTimeout, customizer), httpHeadersProvider);
  39. }
  40. public InstanceWebClient(WebClient webClient, HttpHeadersProvider httpHeadersProvider) {
  41. this.webClient = webClient.mutate().filters(filters -> {
  42. filters.add(InstanceExchangeFilterFunctions.addHeaders(httpHeadersProvider));
  43. filters.add(InstanceExchangeFilterFunctions.rewriteEndpointUrl());
  44. filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.health()));
  45. filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.info()));
  46. filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.env()));
  47. filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.httptrace()));
  48. filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.threaddump()));
  49. filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.liquibase()));
  50. filters.add(InstanceExchangeFilterFunctions.convertLegacyEndpoint(LegacyEndpointConverters.flyway()));
  51. }).build();
  52. }
  53. public WebClient instance(Mono<Instance> instance) {
  54. return webClient.mutate()
  55. .filters(filters -> filters.add(0, InstanceExchangeFilterFunctions.setInstance(instance)))
  56. .build();
  57. }
  58. public WebClient instance(Instance instance) {
  59. return webClient.mutate()
  60. .filters(filters -> filters.add(0, InstanceExchangeFilterFunctions.setInstance(instance)))
  61. .build();
  62. }
  63. /**
  64. * 重写构造WebClient
  65. *
  66. * @param connectTimeout
  67. * @param readTimeout
  68. * @param customizer
  69. * @return
  70. */
  71. private static WebClient createDefaultWebClient(Duration connectTimeout,
  72. Duration readTimeout,
  73. WebClientCustomizer customizer) {
  74. //配置ssl信任
  75. SslContext sslContext = null;
  76. try {
  77. sslContext = SslContextBuilder
  78. .forClient()
  79. .trustManager(InsecureTrustManagerFactory.INSTANCE)
  80. .build();
  81. } catch (SSLException e) {
  82. e.printStackTrace();
  83. }
  84. final SslContext _sslContext = sslContext;
  85. ReactorClientHttpConnector connector = new ReactorClientHttpConnector(
  86. options -> options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) connectTimeout.toMillis())
  87. .compression(true)
  88. .afterNettyContextInit(ctx -> {
  89. ctx.addHandlerLast(
  90. new ReadTimeoutHandler(readTimeout.toMillis(), TimeUnit.MILLISECONDS));
  91. })
  92. .sslContext(_sslContext)
  93. );
  94. WebClient.Builder builder = WebClient.builder()
  95. .clientConnector(connector)
  96. .defaultHeader(HttpHeaders.ACCEPT, ActuatorMediaType.V2_JSON,
  97. ActuatorMediaType.V1_JSON, MediaType.APPLICATION_JSON_VALUE);
  98. customizer.customize(builder);
  99. return builder.build();
  100. }
  101. }
  1. 上面的方式太low了,需要包名完全保持一致,所以提供下面这种重新注入 InstanceWebClient ,更加 make sense ,
  1. @Autowired
  2. private AdminServerProperties adminServerProperties;
  3. @Bean
  4. public InstanceWebClient instanceWebClient(HttpHeadersProvider httpHeadersProvider, ObjectProvider<List<InstanceExchangeFilterFunction>> filtersProvider) {
  5. List<InstanceExchangeFilterFunction> filters = (List)filtersProvider.getIfAvailable(Collections::emptyList);
  6. WebClientCustomizer customizer = (webClient) -> {
  7. filters.forEach((instanceFilter) -> {
  8. webClient.filter(InstanceExchangeFilterFunctions.toExchangeFilterFunction(instanceFilter));
  9. });
  10. };
  11. WebClient defaultWebClient = createDefaultWebClient(this.adminServerProperties.getMonitor().getConnectTimeout(), this.adminServerProperties.getMonitor().getReadTimeout(), customizer);
  12. return new InstanceWebClient(defaultWebClient, httpHeadersProvider);
  13. }
  14. /**
  15. * 修复eureka中有注册为https的服务,导致证书不受信任,此处演示环境仅仅做忽略证书处理
  16. * 由于官方提供的WebClient.createDefaultWebClient 为私有方法,外部无法进行方法覆盖
  17. *
  18. * @param connectTimeout
  19. * @param readTimeout
  20. * @param customizer
  21. * @return
  22. */
  23. private static WebClient createDefaultWebClient(Duration connectTimeout,
  24. Duration readTimeout,
  25. WebClientCustomizer customizer) {
  26. //配置ssl信任
  27. SslContext sslContext = null;
  28. try {
  29. sslContext = SslContextBuilder
  30. .forClient()
  31. .trustManager(InsecureTrustManagerFactory.INSTANCE)
  32. .build();
  33. } catch (SSLException e) {
  34. e.printStackTrace();
  35. }
  36. final SslContext _sslContext = sslContext;
  37. ReactorClientHttpConnector connector = new ReactorClientHttpConnector(
  38. options -> options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) connectTimeout.toMillis())
  39. .compression(true)
  40. .afterNettyContextInit(ctx -> {
  41. ctx.addHandlerLast(
  42. new ReadTimeoutHandler(readTimeout.toMillis(), TimeUnit.MILLISECONDS));
  43. })
  44. .sslContext(_sslContext)
  45. );
  46. WebClient.Builder builder = WebClient.builder()
  47. .clientConnector(connector)
  48. .defaultHeader(HttpHeaders.ACCEPT, ActuatorMediaType.V2_JSON,
  49. ActuatorMediaType.V1_JSON, MediaType.APPLICATION_JSON_VALUE);
  50. customizer.customize(builder);
  51. return builder.build();
  52. }

在spring-boot-admin页面监控的服务无法加载到Logfile这个选项卡

解决这个问题还是相对简单一点,在官网查阅下文档即可找到解决方法
logging.file 只需要配置这个

  1. logging:
  2. file: /majj/share/xxx/yyy.log