Spring Boot Admin 基础使用教程

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 #Spring Boot Admin Server 的地址
instance:
prefer-ip: true #将IP注册到Admin Server上,若不配置默认使用机器的主机名

management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS

创建 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、测试结果

  • 1)依次启动 admin-serveradmin-client 应用程序

  • 2)浏览器访问 http://127.0.0.1:9001/,打开 Admin Server 的主界面,如下图所示:

spring-boot-admin-ui-1

  • 3)点击实例信息链接跳转到详细页面,可以查看实例的详细监控信息,如图所示

3、Admin 在线查看日志文件

Spring Boot Admin 提供了基于 Web 页面的方式实时查看业务服务输出的本地日志(如下图),前提是业务服务中配置了 logging.file,即在被监控的业务模块的 application.yml 配置文件中增加下面的内容:

1
2
logging:
file: /tmp/admin/client.log

spring-boot-admin-ui-3

3.1、源码分析

其核心在 LogFileWebEndpointAutoConfiguration 自动配置类上,所以 logging.file.namelogging.file.pathmanagement.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-boot-admin-log-1

实际上 Spring 在解析 Properties 时会在 Spring 缓存的 Map 中,把 management.endpoint.logfile.external-file 的 Key 转换成 management.endpoint.logfile.externalFile

spring-boot-admin-log-2

spring-boot-admin-log-3

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 #Eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #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 #将IP注册到Eureka Server上,若不配置默认使用机器的主机名

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 #将IP注册到Eureka Server上,若不配置默认使用机器的主机名

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、测试结果

  • 1)依次启动 eureka-serveradmin-serveradmin-client 应用程序

  • 2)浏览器访问 http://127.0.0.1:9001/,打开 Admin Server 的 Web 界面,可以看到有两个服务(如下图所示):

spring-boot-admin-ui-4

  • 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 #将IP注册到Eureka Server上,若不配置默认使用机器的主机名
metadata-map: #指定Spring Security的用户名和密码
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/ 后,页面会被重定向到登录界面,登录的用户名和密码分别为上面配置的 adminadmin,界面显示如下:

spring-boot-admin-ui-5

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 配置文件中,添加邮件相关的配置内容,其中 usernamenotify.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、参考资料