1、Admin 简介 Spring Boot Admin 是一个开源社区项目,用于管理和监控 Spring Boot 应用程序。 应用程序作为 Spring Boot Admin Client 向为 Spring Boot Admin Server 注册(通过 HTTP 协议)或使用 Spring Cloud 注册中心(例如 Eureka、Consul)的服务发现。UI 是的 AngularJs 应用程序,用于展示 Spring Boot Admin Client 的 Actuator 端点上的一些监控数据。Spring Boot Admin 默认提供了如下功能(包括但不限于):
显示健康状态及详细信息,如 JVM 和内存指标、数据源指标、缓存指标 显示构建信息编号 跟踪并下载日志文件 查看 JVM 系统和环境属性 查看 Spring Boot 配置属性 轻松的日志级别管理 与 JMX-Beans 交互 查看线程转储 查看 Http 跟踪 查看 auditevents 查看 http-endpoints 查看计划任务 查看和删除活动会话(基于 Spring-Session) 查看 Flyway/Liquibase 数据库迁移 下载 heapdump 文件 状态变更通知(支持电子邮件、Slack、Hipchat …) 状态更改的事件日志(非持久性) 特别注意:Spring Boot Admin 默认不支持监控数据的持久化,若对数据的持久化有要求,建议考虑使用 Metrics、CAT、Prometheus + Grafana 等监控平台。
2、Admin 快速入门 2.1、版本说明 在本文中,使用的 Spring Cloud 版本是 Hoxton.SR1,对应的 Spring Boot 版本是 2.2.2.RELEASE,特别声明除外,点击下载 完整的案例代码。
2.2、创建 Maven 父级 Pom 工程 在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 Maven 配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.2.RELEASE</version > </parent > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > Hoxton.SR1</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <repositories > <repository > <id > spring-milestones</id > <name > Spring Milestones</name > <url > https://repo.spring.io/libs-milestone</url > <snapshots > <enabled > false</enabled > </snapshots > </repository > </repositories >
2.3、创建 Admin Server 工程 创建 Admin Server 的 Maven 工程,配置工程里的 pom.xml
文件:
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-server</artifactId > <version > 2.3.0</version > </dependency >
添加 Admin Server 需要的 application.yml
配置文件到工程中:
1 2 3 4 5 6 server: port: 9001 spring: application: name: admin-server
创建 Admin Server 的主启动类,引入 @EnableAdminServer
注解:
1 2 3 4 5 6 7 8 @EnableAdminServer @SpringBootApplication public class AdminServerApplication { public static void main (String[] args) { SpringApplication.run(AdminServerApplication.class, args); } }
2.4、创建 Admin Client 工程 创建 Admin Client 的 Maven 工程,配置工程里的 pom.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-client</artifactId > <version > 2.3.0</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
添加 Admin Client 需要的 application.yml
配置文件到工程中,其中 spring.boot.admin.client.url
是 Admin Server 的地址,目的是将 Admin Client 注册到 Admin Server 中,最后暴露 Admin Client 的 Actuator 的所有端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 server: port: 9002 spring: application: name: admin-client boot: admin: client: url: http://127.0.0.1:9001 instance: prefer-ip: true management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS
提示
将微服务应用注册到 Admin Server 上时,若希望使用 IP 地址来替代主机名,Spring Admin 的旧版本(例如 2.3.0
)可以使用 prefer-ip: true
,而在新版本(例如 2.7.9
)里使用的是 service-host-type: ip
。
创建 Admin Client 的主启动类:
1 2 3 4 5 6 7 @SpringBootApplication public class AdminClientApplication { public static void main (String[] args) { SpringApplication.run(AdminClientApplication.class, args); } }
2.5、测试结果
3)点击实例信息链接跳转到详细页面,可以查看实例的详细监控信息,如图 所示 3、Admin 在线查看日志文件 3.1、配置日志文件 Spring Boot Admin 提供了基于 Web 页面的方式实时查看业务服务输出的本地日志(如下图),前提是在业务服务中配置了 logging.file
,即在被监控的业务模块的 application.yml
配置文件中增加下面的内容:
1 2 logging: file: /tmp/shop/auth.log
特别注意
上述的配置内容只适用于 Spring Admin 旧版本(例如 2.3.0),而在新版本(例如 2.7.9)里需要改用以下配置内容来指定日志文件的路径,否则 Admin 的控制台界面会提示 Fetching logfile failed
的错误信息。 在线查看日志文件之前,必须确保日志文件不是空白文件,否则 Admin 无法正常读取日志文件的内容,此时请求日志文件的 HTTP 响应状态为 416 Requested range not satisfiable
。 1 2 3 4 5 6 7 8 9 10 11 12 management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS logfile: external-file: /tmp/shop/auth.log enabled: true
3.2、源码分析 其核心在 LogFileWebEndpointAutoConfiguration
自动配置类上,所以 logging.file.name
、logging.file.path
、management.endpoint.logfile.external-file
都可以作为开启条件。使用 logging.file.path
配置需要注意,因为默认会读取 spring.log
作为日志文件,而 logging.file.name
则不会。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Configuration( proxyBeanMethods = false ) @ConditionalOnAvailableEndpoint( endpoint = LogFileWebEndpoint.class ) @EnableConfigurationProperties({LogFileWebEndpointProperties.class}) public class LogFileWebEndpointAutoConfiguration { public LogFileWebEndpointAutoConfiguration () { } @Bean @ConditionalOnMissingBean @Conditional({LogFileWebEndpointAutoConfiguration.LogFileCondition.class}) public LogFileWebEndpoint logFileWebEndpoint (ObjectProvider<LogFile> logFile, LogFileWebEndpointProperties properties) { return new LogFileWebEndpoint((LogFile)logFile.getIfAvailable(), properties.getExternalFile()); } private static class LogFileCondition extends SpringBootCondition { private LogFileCondition () { } public ConditionOutcome getMatchOutcome (ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String config = this .getLogFileConfig(environment, "logging.file.name" , "logging.file" ); Builder message = ConditionMessage.forCondition("Log File" , new Object[0 ]); if (StringUtils.hasText(config)) { return ConditionOutcome.match(message.found("logging.file.name" ).items(new Object[]{config})); } else { config = this .getLogFileConfig(environment, "logging.file.path" , "logging.path" ); if (StringUtils.hasText(config)) { return ConditionOutcome.match(message.found("logging.file.path" ).items(new Object[]{config})); } else { config = environment.getProperty("management.endpoint.logfile.external-file" ); return StringUtils.hasText(config) ? ConditionOutcome.match(message.found("management.endpoint.logfile.external-file" ).items(new Object[]{config})) : ConditionOutcome.noMatch(message.didNotFind("logging file" ).atAll()); } } } private String getLogFileConfig (Environment environment, String configName, String deprecatedConfigName) { String config = environment.resolvePlaceholders("${" + configName + ":}" ); return StringUtils.hasText(config) ? config : environment.resolvePlaceholders("${" + deprecatedConfigName + ":}" ); } } }
另外可以看到 LogFileWebEndpointProperties
这个类,所以 management.endpoint.logfile.externalFile
也是可以作为开启条件
实际上 Spring 在解析 Properties 时会在 Spring 缓存的 Map 中,把 management.endpoint.logfile.external-file
的 Key 转换成 management.endpoint.logfile.externalFile
4、Admin 整合 Eureka 注册中心 在上述的快速入门案例里,是直接将 Admin Client 注册到了 Admin Server 中,而企业开发中更多的是将服务注册到注册中心(Eureka、Consul),以下的案例将演示如何整合 Admin 和 Eureka,点击下载 完整的案例代码。
4.1、版本说明 在本文中,使用的 Spring Cloud 版本是 Hoxton.SR1,对应的 Spring Boot 版本是 2.2.2.RELEASE,特别声明除外,点击下载完整的案例代码。
4.2、创建 Maven 父级 Pom 工程 在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 Maven 配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.2.RELEASE</version > </parent > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > Hoxton.SR1</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <repositories > <repository > <id > spring-milestones</id > <name > Spring Milestones</name > <url > https://repo.spring.io/libs-milestone</url > <snapshots > <enabled > false</enabled > </snapshots > </repository > </repositories >
4.3、创建 Eureka Server 工程 创建 Eureka Server 的 Maven 工程,配置工程里的 pom.xml
文件:
1 2 3 4 5 6 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-server</artifactId > </dependency > </dependencies >
创建 Eureka Server 的启动主类:
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main (String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
添加 Eureka Server 需要的 application.yml
配置文件到工程:
1 2 3 4 5 6 7 8 9 10 11 server: port: 9003 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
4.4、创建 Admin Server 工程 创建 Admin Server 的 Maven 工程,配置工程里的 pom.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-server</artifactId > <version > 2.3.0</version > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
添加 Admin Server 需要的 application.yml
配置文件到工程中,将 Admin Server 注册到 Eureka 注册中心,并暴露 Admin Server 的 Actuator 的所有端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 server: port: 9001 spring: application: name: admin-server eureka: client: registryFetchIntervalSeconds: 5 service-url: defaultZone: http://127.0.0.1:9003/eureka/ instance: leaseRenewalIntervalInSeconds: 10 health-check-url-path: /actuator/health instance-id: ${spring.application.name}-${server.port} prefer-ip-address: true management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS
创建 Admin Server 的主启动类,添加 @EnableAdminServer
注解开启监控功能,添加 @EnableDiscoveryClient
注解让 Admin Server 可以发现注册到 Eureka 里的其他服务实例:
1 2 3 4 5 6 7 8 9 @EnableAdminServer @EnableDiscoveryClient @SpringBootApplication public class AdminServerApplication { public static void main (String[] args) { SpringApplication.run(AdminServerApplication.class, args); } }
4.5、创建 Admin Client 工程 创建 Admin Client 的 Maven 工程,配置工程里的 pom.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-client</artifactId > <version > 2.3.0</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
添加 Admin Client 需要的 application.yml
配置文件到工程中,将 Admin Client 注册到 Eureka 注册中心,而不是使用上述快速入门案例里的 spring.boot.admin.client.url
将 Admin Client 注册到 Admin Server 中,最后暴露 Admin Client 的 Actuator 的所有端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 server: port: 9002 spring: application: name: admin-client eureka: client: registryFetchIntervalSeconds: 5 service-url: defaultZone: http://127.0.0.1:9003/eureka/ instance: leaseRenewalIntervalInSeconds: 10 health-check-url-path: /actuator/health instance-id: ${spring.application.name}-${server.port} prefer-ip-address: true management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS
创建 Admin Client 的主启动类,引入 @EnableDiscoveryClient
注解:
1 2 3 4 5 6 7 8 @EnableDiscoveryClient @SpringBootApplication public class AdminClientApplication { public static void main (String[] args) { SpringApplication.run(AdminClientApplication.class, args); } }
4.6、测试结果
3)点击实例信息链接跳转到详细页面,可以查看实例的详细监控信息,这里不再累述 5、Admin 整合 Spring Security 生产环境中由于考虑到安全问题,一般不允许直接访问 Admin Server 的 Web 界面,建议整合 Admin + Spring Security,为 Admin Server 新增登录界面,点击下载 完整的案例代码。
在 Admin Server 工程的 pom.xml
配置文件中引入以下的依赖:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency >
在 Admin Server 工程的 application.yml
中配置 Spring Security 的用户名和密码,同时在服务注册时带上 metadata-map
的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server: port: 9001 spring: application: name: admin-server security: user: name: "admin" password: "admin" eureka: client: registryFetchIntervalSeconds: 5 service-url: defaultZone: http://127.0.0.1:9003/eureka/ instance: leaseRenewalIntervalInSeconds: 10 health-check-url-path: /actuator/health instance-id: ${spring.application.name}-${server.port} prefer-ip-address: true metadata-map: user.name: ${spring.security.user.name} user.password: ${spring.security.user.password} management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS
在 Admin Server 工程中创建 Spring Security 的配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final String adminContextPath; public SecurityConfiguration (AdminServerProperties adminServerProperties) { this .adminContextPath = adminServerProperties.getContextPath(); } @Override protected void configure (HttpSecurity http) throws Exception { SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); successHandler.setTargetUrlParameter("redirectTo" ); http.authorizeRequests() .antMatchers(adminContextPath + "/assets/**" ).permitAll() .antMatchers(adminContextPath + "/login" ).permitAll() .anyRequest().authenticated() .and() .formLogin().loginPage(adminContextPath + "/login" ).successHandler(successHandler).and() .logout().logoutUrl(adminContextPath + "/logout" ).and() .httpBasic().and() .csrf().disable(); } }
重启 Admin Server 服务,在浏览器上访问 http://127.0.0.1:9001/
后,页面会被重定向到登录界面,登录的用户名和密码分别为上面配置的 admin
和 admin
,界面显示如下:
6、Admin 整合邮箱报警 Spring Boot Admin 中可以集成邮箱报警功能,比如服务不健康了、下线了,都可以给指定邮箱发送邮件。集成的步骤非常简单,首先在 Admin Server 中引入以下依赖:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-mail</artifactId > </dependency >
在 Admin Server 的 application.yml
配置文件中,添加邮件相关的配置内容,其中 username
与 notify.mail.from
的内容必须一致:
1 2 3 4 5 6 7 8 9 10 11 12 spring: mail: port: 25 host: smtp.qq.com username: 158747124 @qq.com password: xxxxxxx boot: admin: notify: mail: to: 389723578 @qq.com from: 158747124 @qq.com
由于国内腾讯云、阿里云默认封了 25 端口,若项目是部署在云服务器,使用上述的配置是无法正常发送邮件的,需要更改为使用 465
端口,并启用 SSL 邮件加密,最后系统防火墙别忘了开放 465
端口,配置示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 spring: mail: port: 465 protocol: smtp host: smtp.qq.com username: 158747124 @qq.com password: xxxxxxx properties: mail: smtp: auth: true socketFactory: port: 465 class: javax.net.ssl.SSLSocketFactory ssl: enable: true starttls: enable: true required: true boot: admin: notify: mail: to: 389723578 @qq.com from: 158747124 @qq.com
以上配置,当已注册的服务的状态从 UP 变为 OFFLINE 或其他状态时,Admin Server 会自动将告警邮件发送到对应的邮箱,更多邮箱相关的配置示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 spring.mail.host=smtp.qq.com spring.mail.username=xx@qq.com spring.mail.password=xxxxxx spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true spring.mail.properties.mail.smtp.ssl.enable=true spring.mail.properties.mail.smtp.socket.factory.class=javax.net.ssl.SSLSocketFactory spring.mail.properties.mail.smtp.socket.factory.fallback=false spring.mail.properties.mail.smtp.port=465 spring.mail.properties.mail.transport.protocol=smtp #需要忽略的状态改变通知,逗号分隔,例如不通知离线到上线的状态,则填写为OFFLINE:UP #spring.boot.admin.notify.mail.ignore-changes= #接收通知的邮箱地址,逗号分隔 spring.boot.admin.notify.mail.to=yangzhilong@qq.com #需要抄送的邮箱地址,逗号分隔 #spring.boot.admin.notify.mail.cc=test1@qq.com #邮件发送者,大部分情况与登录名相同 spring.boot.admin.notify.mail.from=${spring.mail.username} #邮件主题,默认是:#{application.name} (#{application.id}) is #{to.status} spring.boot.admin.notify.mail.subject=${spring.profiles.active} profile's #{application.name} (#{application.id}) is #{to.status} #邮件内容,默认是:#{application.name} (#{application.id})\nstatus changed from #{from.status} to #{to.status}\n\n#{application.healthUrl} spring.boot.admin.notify.mail.text=${spring.profiles.active} profile's #{application.name} (#{application.id})\nstatus changed from #{from.status} to #{to.status} #Comma-delimited list of status changes to be ignored. Format: "<from-status>:<to-status>". Wildcards allowed.默认值:"UNKNOWN:UP" #spring.boot.admin.notify.mail.ignore-changes=
7、参考资料