概述
架构设计里面很重要的一点是程序的可观测性,那么对微服务集群的所有微服务的进程消息进行监控,动态配置就很重要了。例如虚拟机的堆栈、线程等信息,特别是希望能够实时的查看到服务启动后的配置信息,希望每个开发人员都能够通过统一入口去观测微服务,去动态调整一些服务数据。需要成本低,所以排除了自研的可能性。最后我选择了SpringBoot Admin来实现这一要求。
依赖的框架以及版本
工具 | 版本 |
---|---|
spring-boot-admin-starter-client | 2.3.0 |
spring-boot-starter-web | 2.3.1.RELEASE |
spring-boot-admin-starter-server | 2.3.0 |
spring-boot-starter-security | 2.3.1.RELEASE |
实现步骤
1,搭建服务器端
-
引入对应的依赖
de.codecentric spring-boot-admin-starter-server 2.3.0 io.projectreactor.netty reactor-netty io.projectreactor.netty reactor-netty 0.9.9.RELEASE org.springframework.boot spring-boot-starter-web 2.3.1.RELEASE org.springframework.boot spring-boot-starter-security 2.3.1.RELEASE -
配置security安全类
package com.lqd.configuration; import de.codecentric.boot.admin.server.config.AdminServerProperties; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { private final String adminContextPath; public SecurityConfig(AdminServerProperties adminServerProperties) { this.adminContextPath = adminServerProperties.getContextPath(); } @Override protected void configure(HttpSecurity http) throws Exception { // @formatter:off SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); successHandler.setTargetUrlParameter("redirectTo"); successHandler.setDefaultTargetUrl("/"); http.authorizeRequests() .antMatchers("/assets/**").permitAll() .antMatchers("/login").permitAll() .anyRequest().authenticated().and() .formLogin().loginPage("/login") .successHandler(successHandler).and() .logout().logoutUrl("/logout").and() .httpBasic() .and() .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .ignoringAntMatchers( "/instances", "/actuator/**" ); // @formatter:on } }
-
配置文件添加相应配置
server.port=38999 server.servlet.context-path=/ spring.application.name=monitor-server spring.profiles.active=dev spring.security.user.name=admin spring.security.user.password=666666 spring.boot.admin.ui.brand=Monitor spring.boot.admin.ui.title=Monitor
2,搭建客户端
-
添加相应依赖
de.codecentric spring-boot-admin-starter-client 2.3.0 org.springframework.boot spring-boot-starter-web 2.3.1.RELEASE -
添加相应的配置类,这里由于原代码没法从容器获取ip,我重写了ip获取的规则。
package com.lqd; import de.codecentric.boot.admin.client.config.InstanceProperties; import de.codecentric.boot.admin.client.registration.ApplicationFactory; import de.codecentric.boot.admin.client.registration.metadata.CompositeMetadataContributor; import de.codecentric.boot.admin.client.registration.metadata.MetadataContributor; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Primary; import javax.servlet.ServletContext; import java.util.Collections; import java.util.List; @Configuration @ConditionalOnProperty(value = "spring.boot.admin.client.enabled" , havingValue = "true") public class ApplicationConfiguration { @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @AutoConfigureAfter(DispatcherServletAutoConfiguration.class) public static class ServletConfiguration { @Bean @Lazy(false) @ConditionalOnMissingBean @Primary public ApplicationFactory applicationFactory(InstanceProperties instance, ManagementServerProperties management, ServerProperties server, ServletContext servletContext, PathMappedEndpoints pathMappedEndpoints, WebEndpointProperties webEndpoint, ObjectProvider
- > metadataContributors,
DispatcherServletPath dispatcherServletPath) {
return new CustomApplicationFactory(instance, management, server, servletContext, pathMappedEndpoints,
webEndpoint,
new CompositeMetadataContributor(metadataContributors.getIfAvailable(Collections::emptyList)),
dispatcherServletPath);
}
}
}
package com.lqd; import de.codecentric.boot.admin.client.config.InstanceProperties; import de.codecentric.boot.admin.client.registration.ServletApplicationFactory; import de.codecentric.boot.admin.client.registration.metadata.MetadataContributor; import io.micrometer.core.instrument.util.StringUtils; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import javax.servlet.ServletContext; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; //@Slf4j public class CustomApplicationFactory extends ServletApplicationFactory { public CustomApplicationFactory(InstanceProperties instance, ManagementServerProperties management, ServerProperties server, ServletContext servletContext, PathMappedEndpoints pathMappedEndpoints, WebEndpointProperties webEndpoint, MetadataContributor metadataContributor, DispatcherServletPath dispatcherServletPath) { super(instance, management, server, servletContext, pathMappedEndpoints, webEndpoint, metadataContributor, dispatcherServletPath); } @Override protected String getHost(InetAddress address) { String ip = getIpAddress(); return StringUtils.isNotBlank(ip)?ip:super.getHost(address); } public static String getIpAddress() { try { Enumeration
allNetInterfaces = NetworkInterface.getNetworkInterfaces(); InetAddress ip = null; while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = allNetInterfaces.nextElement(); if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) { continue; } else { Enumeration addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { ip = addresses.nextElement(); if (ip != null && ip instanceof Inet4Address) { return ip.getHostAddress(); } } } } } catch (Exception e) { //log.error("IP地址获取失败" + e.toString()); } return ""; } } -
配置文件添加相应配置
server.port=8091 server.servlet.context-path=/ spring.application.name=monitor-client spring.profiles.active=dev spring.boot.admin.client.username=admin spring.boot.admin.client.password=666666 spring.boot.admin.client.enabled=true spring.boot.admin.client.url=http://localhost:38999/ spring.boot.admin.client.instance.prefer-ip=true management.endpoints.web.exposure.include=* management.endpoint.health.show-details=always
3,登录验证
- 浏览器访问http://localhost:38999/ ,输入对应的账号密码admin/666666,可以看到满足我们的要求。
异常报错
-
springboot -admin 2.3.0搭建的server端部署linux后,会出现如下图所示错误,这是由于他依赖的reactor-netty有bug,会导致打开文件数激增,最后抛出too many open files错误。需要将reactor-netty的版本升级到0.9.9.RELEASE版本。
-
springboot admin若采用容器部署,获取的监控微服务的ip是127.0.0.1,需要如上所述重写客户端ip获取规则。
-
客户端带账号密码正确请求报错:401,这是因为服务端security框架对所有的接口都验证权限,解决办法是需要在服务端配置指定的接口安全过滤。
.ignoringAntMatchers( "/instances", "/actuator/**" );
-
点击微服务的/actuator/health接口,返回的状态始终为down,服务处于离线状态。这时需要获取接口的详情消息,2.3.0版本的springboot可以在客户端配置如下参数,即可查找到down的原因。
management.endpoint.health.show-details=always
参考
例子代码