什么是 Spring Boot Admin?

Spring Boot Admin 是一个管理和监控 Spring Boot 应用的社区项目。

创建 Spring Boot Admin Server

要做到这一点,只需创建一个简单的 Spring Boot 项目。由于 Spring Boot Admin Server 能够作为 servlet 或 webflux 应用运行,因此您需要相应的 Spring Boot Starter。添加 Spring Boot Admin Server starter 到您的 pom.xml:

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.5.1</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

通过在启动类中添加 @EnableAdminServer 来启用 Spring Boot Admin Server 配置:

/**
 * @author ZnPi
 * @date 2022-11-21
 */
@SpringBootApplication
@EnableAdminServer
public class MonitorApp {
    public static void main(String[] args) {
        SpringApplication.run(MonitorApp.class, args);
    }
}

要查看完整示例,请查看 pi-cloud 的 pi-monitor 模块:

Gitee

GitHub

后端

https://gitee.com/linjiabin100/pi-cloud.git

https://github.com/zengpi/pi-cloud.git

注册客户端应用

Spring Boot Admin Client 在 Admin Server 上注册应用程序。这是通过定期向 SBA Server 发送 HTTP post 请求来实现的,该请求提供有关应用程序的信息。

要在 SBA Server 上注册客户端应用,可以使用 Spring Cloud Discovery。在本例中使用 Nacos 注册中心:

添加 spring-cloud-starter-eureka 到你的 pom.xml:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

告诉 Nacos 客户端在哪里可以找到服务注册表:

# Nacos 客户端的配置部分
spring:
  cloud:
    nacos:
      server-addr: pi-nacos:8848
     
# 默认情况下大多数端点不会通过 http 暴露,这里暴露所有端点
# 对于生产环境,应该仔细选择要暴露的端点
management:
  endpoints:
    web:
      exposure:
        include: "*"  
  endpoint:
    health:
      show-details: ALWAYS

启用生产就绪特性

另外,对于客户端应用,你需要启用生产就绪特性。spring-boot-actuator 模块提供了 Spring Boot 的所有生产就绪特性:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

查看日志文件

默认情况下,日志文件不能通过 Actuator 端点访问,因此在 Spring Boot Admin 中不可见。为了启用日志文件 Actuator 端点,你需要通过设置 logging.file.path 或 logging.file.name 来配置 Spring Boot 写入日志文件。Spring Boot Admin 将检测所有看起来像 URL 的内容,并将其呈现为超链接。

Spring Boot Admin 还支持 ANSI 颜色转义。您需要设置一个自定义文件日志模式,因为 Spring Boot 的默认模式不使用颜色:

logging:
  # 写入日志文件的目标。启用日志文件 Actuator 端点
  file:
    name: logs/${spring.application.name}.log
  # 使用 ANSI 颜色的文件日志模式。
  pattern:
    file: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx"

同时,需要在 Spring Boot Admin 客户端暴露的端点中添加 logfile 配置:

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS
    # 添加 logfile 配置
    logfile:
      enabled: true
      external-file: logs/${spring.application.name}.log

邮件通知

邮件通知将以使用 Thymeleaf 模板呈现的 HTML 电子邮件形式发送。要启用邮件通知,请使用 spring-boot-starter-mail 配置 JavaMailSender 并设置收件人。这里以 163 邮箱发送到 QQ 邮箱为例。

添加 spring-boot-start -mail 到你的 dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

配置 JavaMailSender

spring:
  boot:
    admin:
      ui:
        title: 服务监控中心
      notify:
        mail:
          to: to@qq.com # 收件人邮箱
          from: from@163.com # 发送人邮箱
  mail:
    host: smtp.163.com
    username: from@163.com # 发送人邮箱
    password: HEEUKFSUFNFAGVGE # 授权码

关于授权码的获取,请设置 SMTP 服务。在您的浏览器中打开 163 邮箱的网址并登录上去。然后点击“设置” -> "POP3/SMTP/IMAP" -> “开启 IMAP/SMTP 服务”,您需要根据提示发送短信开通。请保存好您的授权码,此授权码只会出现一次。

安全

默认情况下,spring-boot-admin-server-ui 提供了一个登录页面和一个注销按钮,您需要将 spring-boot-starter-security 添加到您的 dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

服务器的 Spring Security 配置如下所示:

@Configuration(proxyBeanMethods = false)
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {

  private final AdminServerProperties adminServer;

  private final SecurityProperties security;

  public SecuritySecureConfig(AdminServerProperties adminServer, SecurityProperties security) {
    this.adminServer = adminServer;
    this.security = security;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    successHandler.setTargetUrlParameter("redirectTo");
    successHandler.setDefaultTargetUrl(this.adminServer.path("/"));

    http.authorizeRequests(
        // 授予对所有静态资源和登录页面的公共访问权。
        (authorizeRequests) -> authorizeRequests.antMatchers(this.adminServer.path("/assets/**")).permitAll() 
            .antMatchers(this.adminServer.path("/actuator/info")).permitAll()
            .antMatchers(this.adminServer.path("/actuator/health")).permitAll()
            .antMatchers(this.adminServer.path("/login")).permitAll()
            // 其他请求都必须进行身份认证。
            .anyRequest().authenticated() 
    // 配置登录和注销。
    ).formLogin(
        (formLogin) -> formLogin.loginPage(this.adminServer.path("/login")).successHandler(successHandler).and() 
    ).logout((logout) -> logout.logoutUrl(this.adminServer.path("/logout")))
        // 使用 Cookies启用 CSRF-Protection
        .csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 
            .ignoringRequestMatchers(
                // (取消)注册的端点禁用 CSRF-Protection
                new AntPathRequestMatcher(this.adminServer.path("/instances"),
                    HttpMethod.POST.toString()), 
                new AntPathRequestMatcher(this.adminServer.path("/instances/*"),
                    HttpMethod.DELETE.toString()), 
                // 禁用 Actuator 端点的 CSRF-Protection 功能。
                new AntPathRequestMatcher(this.adminServer.path("/actuator/**")) 
            ))
        .rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600));
  }

  // Required to provide UserDetailsService for "remember functionality"
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser(security.getUser().getName())
        .password("{noop}" + security.getUser().getPassword()).roles("USER");
  }
}

然后在您的配置文件中配置用户名和密码:

spring:
  security:
    user:
      name: admin
      password: admin