17.3 保护Admin服务器

正如我们在第16章所讨论的那样,Actuator端点对外暴露的信息并不能随便消费。它们包含的信息暴露了应用的详情,这些信息只有应用程序的管理员才能查看。另外,还有一些端点允许对应用进行变更,它们就更不应该对所有人开放了。

正如安全性对于Actuator来说非常重要一样,它对Admin服务器同样重要。除此之外,如果Actuator端点需要认证,那么Admin需要知道凭证信息才能访问这些端点。接下来,我们看一下如何为Admin服务器添加一些安全性。首先,从认证开始。

17.3.1 为Admin服务器启用登录功能

默认情况下,Admin服务器是不安全的,所以为其添加安全性功能是一种好的做法。因为Admin服务器就是一个Spring Boot应用,所以我们可以使用SpringSecurity来保护它。这一点与其他的Spring Boot应用完全类似。就像使用SpringSecurity保护其他的应用一样,我们可以自由选择最适合需求的安全模式。

按照最小的要求,我们需要添加Spring Boot security starter到Admin服务器的构建文件中,既可以在Initializr中选中Security复选框,也可以添加如下的<dependency>到项目的pom.xml文件中:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

然后,为了避免观察Admin服务器的日志才能获取随机生成的密码,我们可以将简单的管理员用户名和密码配置在application.yml中:

1
2
3
4
5
spring:
security:
user:
name: admin
password: 53cr3t

现在,当在浏览器中加载Admin服务器的时候,我们会看到Spring Security默认的登录表单,提示我们输入用户名和密码。按照这里的配置片段,输入“admin”和“53cr3t”就可以登录了。当然,这是一个极其基本的安全配置。我推荐你参考第4章了解配置Spring Security的各种方式,为Admin服务器提供更丰富的安全模式。

17.3.2 为Actuator启用认证

在16.4节中,我们讨论了如何使用HTTP Basic认证保护Actuator端点。按照这种方式,我们会将不知道Actuator端点用户名和密码的用户拒之门外。也就意味着Admin服务器不能消费Actuator端点了,除非提供用户名和密码。但是,Admin如何得到凭证信息呢?

Admin服务器的客户端应用可以通过直接向Admin服务器注册自身或被Eureka发现的方式提供凭证信息给Admin服务器。如果应用是直接向Admin服务器注册自身,那么可以在注册时发送凭证信息。我们需要配置几个属性启用该功能。

spring.boot.admin.client.instance.metadata.user.name和spring.boot.admin.client.instance. metadata.user.password属性指定了Admin服务器访问应用的Actuator端点时可以使用的凭证信息。application.yml中如下的代码片段展示了如何设置这些属性:

1
2
3
4
5
6
7
8
9
spring:
boot:
admin:
client:
url: http://localhost:9090
instance:
metadata:
user.name: ${spring.security.user.name}
user.password: ${spring.security.user.password}

用户名和密码必须要设置在所有向Admin服务器注册的应用中。这里给定的值必须要匹配Actuator端点HTTP Basic认证头信息所需的用户名和密码。在本例中,它们设置成了admin和password,也就是访问Actuator端点所配置的凭证信息。

如果应用是由Admin服务器通过Eureka发现的,那么我们需要设置eureka.instance.metadata-map.user.name和eureka.instance.metadata-map.user.password:

1
2
3
4
5
eureka:
instance:
metadata-map:
user.name: admin
user.password: password

当应用使用Eureka注册的时候,凭证信息将会包含到Eureka注册记录的元数据中。当Admin服务器发现应用时,会和应用的其他详情一起从Eureka获取它的凭证信息。

17.2 探索Admin服务器

在将所有的Spring Boot应用注册为Admin服务器的客户端之后,我们就可以使用Admin服务器得到运行中应用的大量信息,包括:

  • 通用的健康信息;
  • 通过Micrometer和“/metrics”端点发布的所有指标;
  • 环境属性;
  • 包和类的日志级别;
  • 线程跟踪细节;
  • HTTP请求的跟踪情况;
  • 审计日志。

实际上,几乎Actuator暴露的所有内容我们都可以通过Admin服务器来查看,只不过它的展现形式更加人性化。它包括了图表和钻取信息的过滤器。Admin服务所展现的信息要比本章中看到的多得多,限于篇幅,我们使用本节剩余的内容着重介绍一些Admin服务器的亮点功能。

17.2.1 查看应用基本的健康状况和信息

正如我们在16.2.1小节所提到的那样,Actuator会通过“/health”和“/info”端点提供应用的健康状况和基本信息。Admin服务器在Details选项卡下展现了这些信息,如图17.6所示。

image-20211023203206091

图17.6 Spring Boot Admin UI的Details选项卡展现了应用的健康状况和基本信息

滑过Details选项卡的Health和Info部分,我们会在下方看到一些来自应用JVM的统计信息,包括展现处理器、线程和内存使用的图表,如图17.7所示。

image-20211023203218420

图17.7 在Details选项卡中下方将会看到额外的JVM内部信息(包括处理器、线程和内存统计数据)

图表中所展现的信息再加上Processes、Garbage Collection Pauses下面的指标,可以提供关于应用如何使用JVM资源的有用信息。

17.2.2 观察核心指标

在Actuator的所有端点中,“/metrics”端点所提供的信息可能最不易于人类阅读了。借助Admin服务器Metrics选项卡下的UI界面,我们可以很容易地消费应用所生成的指标数据。

在开始的时候,Metrics选项卡并不会展示任何指标。借助页面顶部的表单,我们能够设置想要查看的一个或多个指标。

在图17.8中,我们监视了http.server.requests分类的两个指标:第一个报告展现了发往“/ingredients”端点的HTTP GET请求,并且要求返回状态为200 (OK);第二个报告展现了所有产生HTTP 404 (NOT FOUND)响应的请求。

image-20211023203230801

图17.8 在Metrics选项卡下,我们可以监视应用的“/metrics”端点发布的所有指标

关于这些指标,非常棒的一点在于(其实几乎适用于Admin服务器展现的所有内容),这里所展示的是实时数据,会自动更新,无须刷新页面。

17.2.3 探查环境属性

Actuator的“/env”端点能够返回Spring Boot应用所有可用的环境变量,这些环境变量来源于各种属性源。尽管API端点的JSON格式响应并不难读,但是Admin服务器在Environment选项卡下以更美观的形式进行了展现(见图17.9)。

image-20211023203245751

图17.9 Environment选项卡展现了环境属性,并且包含了重写和过滤值的选项

因为这里可能会有数百个属性,所以可以使用属性名或值对可用属性进行过滤。图17.9展现了根据属性名和/或值包含“spring.”进行过滤后的属性列表。通过页面顶部的Environment Manager表单,Admin服务器还允许我们设置或重写环境属性。

17.2.4 查看和设置日志级别

Actuator的“/loggers”端点对于理解或重写运行中应用的日志级别非常有用。Admin服务器的Loggers选项卡基于“/loggers”端点提供了一个非常易于使用的UI页面,进一步简化了应用中的日志管理。图17.10展现了根据org.springframework.boot名称过滤后的loggers。

image-20211023203259747

图17.10 Loggers选项卡会展示应用中包和类的日志级别,并且允许我们重写它们的级别

默认情况下,Admin服务器会展现所有包和类的日志级别。它们可以通过名称(仅限于类)和/或显式配置的日志级别来进行过滤,这里不支持对由根logger继承来的级别进行过滤。

17.2.5 监控线程

在应用中,多个线程可以并行运行。尽管“/threaddump”端点(在16.2.3小节进行过描述)提供了应用运行中线程状态的快照,但是Spring Boot Admin UI中的Threads选项卡能够实时查看应用中所有的线程(见图17.11)。

image-20211023203346646

图17.11 我们可以使用Admin UI的Threads选项卡实时监控应用的线程

“/threaddump”端点只是捕获某个时刻的快照,Threads选项卡中的条形图与之不同,它是持续更新的,展示每个线程的状态:线程可运行的话,是绿色的;等待的话,是黄色的;阻塞的话,是红色的。

要查看某个线程的细节信息,可以点击列表中的线程行。这样会显示该线程的历史数据,包括线程当前的堆栈。

17.2.6 跟踪HTTP请求

Spring Boot Admin UI的Http Traces选项卡(见图17.12)展现了Actuator“/httptrace”端点的数据。与“/httptrace”端点返回请求时最近的100个请求不同,Http Traces选项卡列出了完整的HTTP请求历史。而且,在我们打开这个选项卡的时候,数据会一直刷新。如果你离开这个选项卡再回来,那么它初始会显示100条最近的请求,但是会从当前时刻开始进行跟踪。

我们可以看到,Http Traces选项卡包含了一个随时间变化的HTTP流量的堆积图(stacked graph)。这个图使用不同的颜色来表示成功和失败的请求:绿色代表成功,黄色代表客户端错误(例如404级别的HTTP响应),红色代表服务器错误(如500级别的HTTP响应)。如果将鼠标指针移动到图上,就会弹出一个悬停框(如图17.12最右侧所示),显示给定时间分解的请求计数。

image-20211023203400650

图17.12 Http Traces选项卡会跟踪该应用最近的HTTP流量,包括产生错误的请求信息

在堆积图的下方展现了跟踪历史,应用接收到的每个请求都会对应一行。点击其中一行,就会展开显示该请求的额外数据,包括请求和响应的头信息(见图17.13)。

image-20211023203412801

图17.13 点击Http Traces选项卡中的请求条目,将会展现该请求额外的详情

17.1 使用Spring Boot Admin

我曾经被问到很多次,开发一个消费Actuator端点的Web应用并为其提供一个易于查看的UI到底有多么难,这样是否有意义。我的答复是它只是一个REST API,因此所有的事情都是有可能的。不过,当位于德国的软件和咨询公司codecentricAG的优秀工程师已经完成了这项工作时,我们为什么还要为Actuator创建自己的UI呢?

Spring Boot Admin是一个管理类的Web前端应用,使得Actuator的端点更易于被人类所使用。它分为两个主要的组件:Spring Boot Admin服务器和它的客户端。Admin服务器负责收集并展现Actuator数据,而展现的数据则是由一个或多个Spring Boot应用提供的,这些应用就是Spring Boot Admin的客户端,如图17.1所示。

image-20211023202613037

图17.1 Spring Boot Admin的服务器消费来自一个或多个Spring Boot应用的Actuator端点,并将数据展现在一个基于Web的UI中

我们需要将组成Taco Cloud的每个应用(微服务)注册为Spring Boot Admin的客户端。首先,我们需要搭建Spring Boot Admin服务器,以便于接收每个客户端的Actuator信息。

17.1.1 创建Admin服务器

为了启用Admin服务器,我们首先需要创建一个新的Spring Boot应用并将Admin服务器依赖添加到项目的构建文件中。Admin服务器通常会作为一个单独的应用,与其他的应用区分开来。因此,最简单的方式就是使用Spring Boot Initializr创建一个新的Spring Boot项目并选择标签为Spring Boot Admin (Server)的复选框。这样会将如下的依赖添加到<dependencies>代码块中:

1
2
3
4
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>

现在,我们需要启用Admin服务器,只需要在主配置类上添加@EnableAdminServer注解就可以了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
package tacos.bootadmin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
@SpringBootApplication
@EnableAdminServer
public class BootAdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(BootAdminServerApplication.class, args);
}
}

最后,因为在开发阶段Admin服务器并不是唯一一个在本地运行的应用,所以我们需要将其设置为监听一个唯一的端口,而且这个端口要易于访问(比如,不能是0)。在这里,我选择9090作为Spring Boot Admin服务器的端口:

1
2
server:
port: 9090
注意:与其他微服务架构的Spring Boot应用类似,server.port可以在生产环境的profile使用不同的端口,在那时端口可能会由底层平台来决定。

现在,我们的Admin服务器已经准备就绪。如果此时启动应用并在浏览器中访问http://localhost:9090,那么我们将会看到如图17.2所示的效果。

image-20211023202805870

图17.2 没有任何实例在运行

我们可以看到,Spring Boot Admin显示有零个应用的零个实例正在运行。数字下面有“No applications registered.”这样的提示信息,说明此时这些数字没有任何意义。要让Admin服务器真正发挥作用,我们需要为其注册应用。

17.1.2 注册Admin客户端

因为Admin服务器独立于要展现Actuator数据的其他Spring Boot应用,所以必须让Admin服务器能够以某种方式感知这些应用。Admin服务器注册Spring BootAdmin客户端有两种方式:

  • 每个应用显式向Admin服务器注册自身;
  • Admin通过Eureka服务注册中心发现服务。

接下来,我们分别看一下这两种方案,首先是如何将单个Spring Boot应用配置为Spring Boot Admin客户端,这样它们就能向Admin服务器注册自身了。

显式配置Admin客户端应用

为了让Spring Boot应用注册为Admin服务器的客户端,我们必须将Spring BootAdmin client starter添加到项目的构建文件中。在Initializr中,我们可以选中标签为Spring Boot Admin (Client)的复选框,这样很容易就能将这个依赖添加到构建文件中。对于Maven构建的Spring Boot应用,我们也可以设置如下的依赖:

1
2
3
4
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>

客户端库准备就绪之后,我们需要配置Admin服务器的位置。这样的话,客户端就可以将自身注册进去。为了实现这一点,我们可以将spring.boot.admin.client.url属性设置为Admin服务器的根路径:

1
2
3
4
5
6
7
spring:
application:
name: ingredient-service
boot:
admin:
client:
url: http://localhost:9090

注意,在这里,我们还设置了spring.application.name属性(在本例中,也就是配料服务)。我们之前已经使用这个属性在Spring Cloud Config Server和Eureka中识别微服务。这里,它的目的很类似:识别Admin服务器中的应用。我们重启应用之后,将会看到它出现在Admin服务器中,如图17.3所示。

image-20211023202908369

图17.3 Spring Boot Admin UI展现了一个已注册的应用

尽管在图17.3中并没有太多关于配料服务的信息,但是我们可以看到应用的启动时间。如果Spring Boot Maven插件配置了build-info goal(如16.3.1小节所讨论的那样),这里还会显示构建版本。请放心,在Admin服务器中点击应用之后,我们会看到很多其他运行时的细节。我们将会在17.2节深入了解Admin服务器都提供了哪些功能。

我们需要在所有的应用间重复设置这些在Admin服务器中注册配料服务的配置。一种比较简单的方式是我们只配置spring.application.name属性,Spring CloudConfig Server会将spring.boot.admin.client.url发送给它的所有客户端。如果你已经使用Eureka作为服务注册中心,那么更好的方式是让Admin自己去发现服务。接下来,我们看一下如何将Admin配置为Eureka客户端。

发现Admin客户端

如果想让Admin服务器来发现服务,唯一需要做的事情就是添加Spring CloudNetflix Eureka Client starter到Admin服务器的构建文件中。如下是我们需要的Maven <dependency>

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
注意:我们也可以通过在Spring Initializr中选择Eureka Discovery复选框来添加该依赖。

Admin服务器启用了Eureka客户端功能之后,什么事情都不需要做了。我们可以直接略过上文所述的客户端配置,因为Admin会自动发现注册在Eureka中的服务并展现它们的Actuator数据。如果Eureka中注册了多个Taco Cloud服务,那么它们将会展现在Admin服务器中(参见图17.4)。

image-20211023202942990

图17.4 Spring Boot Admin UI能够展现在Eureka中发现的所有服务

在图17.4中列出了4个不同的应用,对应了6个服务:订单服务有两个实例,taco服务有两个实例,其他的两个应用各有一个实例。这里显示的所有应用均处于UP状态。如果有应用掉线(比如用户服务),就将会在Admin服务器中单独显示(见图17.5)。

image-20211023202956020

图17.5 Spring Boot Admin UI单独展现掉线的服务,与在线的服务隔离开

作为Eureka的客户端,Admin服务器也会将自身注册为Eureka中的服务。如果要避免这种现象,我们可以将eureka.client.register-with-eureka属性设置为false:

1
2
3
eureka:
client:
register-with-eureka: false

与其他的Eureka客户端类似,如果不是监听默认主机和端口,我们就可以配置Eureka服务器的位置。如下的YAML文件将Eureka位置配置成eureka1.tacocloud.com主机:

1
2
3
4
eureka:
client:
service-url:
defaultZone: http://eureka1.tacocloud.com:8761/eureka/

现在,我们已经将多个Taco Cloud服务注册到了Admin服务器中。接下来,我们看一下Admin服务器都提供了哪些功能。

第17章 管理Spring

本章内容:
  • 搭建Spring Boot Admin
  • 注册客户端应用
  • 使用Actuator端点
  • 保护Admin服务器

“一图胜千言”,对于很多应用程序的用户来说,一个用户友好的Web应用要胜过上千个API调用。不要误会我的意思,我是一个命令行爱好者,非常喜欢使用curl和HTTPie消费REST API。但是有时候先手动输入命令行来调用REST端点再查看结果要比在浏览器中点击链接并阅读结果低效得多。

在前面的章节中,我们探索了Spring Boot Actuator暴露的所有HTTP端点。端点返回的是JSON响应,所以对于如何使用它们并没有任何限制。在本章中,我们将会看到基于Actuator端点构建的前端用户界面(UI),从而使这些端点更易于使用,而且有些实时数据是很难直接通过调用Actuator使用的。

16.5 小结

  • Spring Boot Actuator以HTTP和JMX MBean的形式提供了多个端点,它们能够让我们探查Spring Boot应用内部的运行状况。
  • 大多数的Actuator端点默认是禁用的,我们可以通过设置management.endpoints.web.exposure.include和management.endpoints.web.exposure.exclude属性有选择地对外暴露。
  • 有些端点,比如“/loggers”和“/env”,允许写入操作,这样能够在运行时改变应用的配置。
  • 借助“/info”端点可以暴露应用的构建和Git提交的详情。
  • 自定义的健康指示器可以反映应用的健康状况,以便于跟踪外部集成系统的健康状态。
  • 自定义的应用指标可以通过Micrometer进行注册,让Spring Boot应用与多种流行的指标引擎进行集成,包括Datadog、New Relic和Prometheus。
  • Actuator的Web端点可以通过Spring Security进行保护,与Spring Web应用的其他端点非常相似。

16.4 保护Actuator

我们可能不想让别人窥探Actuator暴露的信息。另外,因为Actuator提供了一些操作来修改环境变量和日志级别,所以最好对Actuator进行保护,只有具有对应权限的客户端才能消费这些端点。

虽然保护Actuator端点非常重要,但是安全性本身并不是Actuator的职责,我们需要使用Spring Security来保护Actuator。因为Actuator端点的路径和应用本身的路径非常相似,所以保护Actuator与保护其他的应用路径并没有什么区别。我们在第4章讨论的内容依然适用于保护Actuator端点。

因为所有的端点都集中在“/actuator”基础路径(如果设置了management.endpoints.web.base-path属性,那么可能会是其他的路径)下,所以很容易将授权规则应用到所有的Actuator端点上。例如,只有具有ROLE_ADMIN权限的用户才能调用Actuator端点,那么我们可以重写WebSecurityConfigurerAdapter的configure()方法:

1
2
3
4
5
6
7
8
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/actuator/**").hasRole("ADMIN")
.and()
.httpBasic();
}

这需要所有的请求均由具备ROLE_ADMIN权限的授权用户发起,才能进行访问。它还配置了HTTP basic认证,这样客户端应用可以在请求的Authorization头信息中提交编码后的认证信息。

保护Actuator的唯一问题在于,端点的路径硬编码为“/actuator/**”,如果因为修改了management.endpoints.web.base-path属性发生变化的话,那么这种方式就无法正常运行了。为了帮助解决这个问题,Spring Boot提供了EndpointRequest(一个请求匹配类,更简单,而且不依赖于给定的String路径)。借助EndpointRequest,我们可以将相同的安全要求用到Actuator上,而且不需要硬编码路径:

1
2
3
4
5
6
7
8
9
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(EndpointRequest.toAnyEndpoint())
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}

EndpointRequest.toAnyEndpoint()方法会返回一个请求匹配器,它会匹配所有的Actuator端点。如果你想要将某些端点从请求匹配器中移除,那么我们可以调用excluding()方法,通过名称进行声明:

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(
EndpointRequest.toAnyEndpoint()
.excluding("health", "info"))
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}

另外,如果我们只是想将安全性用到其中一部分Actuator端点中,那么可以调用to()来替换toAnyEndpoint(),并使用名称指明这些端点:

1
2
3
4
5
6
7
8
9
10
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(EndpointRequest.to(
"beans", "threaddump", "loggers"))
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}

这样会限制只将安全性功能用到“/beans”“/threaddump”和“/loggers”端点上,其他的Actuator端点会全部对外开放。

16.3 自定义Actuator

Actuator最棒的特性之一就是它能够进行自定义,以满足应用的特定需求。有一些端点本身支持自定义扩展,同时Actuator也允许我们创建完全自定义的端点。

接下来,我们看一下Actuator能够进行自定义的几种方式。下面先从为“/info”端点添加信息开始。

16.3.1 为“/info”端点提供信息

正如我们在16.2.1小节所看到的那样,“/info”最初是空的,没有提供任何信息。我们可以通过创建前缀为“info.”的属性很容易地为它添加数据。

尽管创建前缀为“info.”的属性是一个很简单的为“/info”端点添加自定义数据的方式,但是这并不是唯一的方式。Spring Boot提供了名为InfoContributor的接口,允许我们以编程的方式为“/info”端点添加任何想要的信息。Spring Boot甚至提供了InfoContributor接口的几个实现,你肯定会发现它们非常有用。

接下来,我们看一下如何编写自定义的InfoContributor,以便于向“/info”端点添加自定义的信息。

创建自定义的Info贡献者

假设我们想要为“/info”端点添加关于Taco Cloud的统计信息,比如想要包含已经创建多少taco的信息。为了实现这一点,我们需要编写一个实现InfoContributor接口的类,并将TacoRepository注入进来,然后发布TacoRepository提供的信息到“/info”端点中。程序清单16.3展示了如何实现这样一个贡献者。

程序清单16.3 InfoContributor的自定义实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package tacos.tacos;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.actuate.info.Info.Builder;
@Component
public class TacoCountInfoContributor implements InfoContributor {
private TacoRepository tacoRepo;
public TacoCountInfoContributor(TacoRepository tacoRepo) {
this.tacoRepo = tacoRepo;
}
@Override
public void contribute(Builder builder) {
long tacoCount = tacoRepo.count();
Map<String, Object> tacoMap = new HashMap<String, Object>();
tacoMap.put("count", tacoCount);
builder.withDetail("taco-stats", tacoMap);
}
}

要实现InfoContributor接口,TacoCountInfoContributor就需要实现contribute()方法。这个方法能够得到一个Builder对象,基于这个对象,contribute()调用withDetail()方法来添加详情信息。在上述的实现中,我们通过TacoRepository的count()来获取已经创建了多少个taco。然后,我们将这个数量放到一个Map中,以值为taco-stats的label将它传递到builder中。这样形成的“/info”端点将会包含这个数量,如下所示:

1
2
3
4
5
{
"taco-stats": {
"count": 44
}
}

我们可以看到,InfoContributor的实现可以以任何方式贡献信息。为属性添加“info.”前缀虽然简单,但是它们却只能是静态值。

注入构建信息到“/info”端点中

Spring Boot提供了一些内置的InfoContributor实现,它们能够自动添加信息到“/info”端点的结果中。其中有一个实现是BuildInfoContributor,它能够将项目构建文件中的信息添加到“/info”端点的结果中。这包括了一些基本信息,比如项目版本、构建的时间戳以及执行构建的主机和用户。

为了将构建信息添加到“/info”端点的结果中,我们需要添加build-info goal到Spring Boot Maven Plugin executions中,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

如果使用Gradle构建项目,我们只需要添加如下几行代码到build.gradle文件中:

1
2
3
springBoot {
buildInfo()
}

不管是哪种方式,构建过程都会在可分发的JAR或WAR文件中生成一个名为build-info.properties的文件,BuildInfoContributor会使用这个文件并为“/info”端点贡献信息。如下的“/info”端点响应片段展现了所贡献的构建信息:

1
2
3
4
5
6
7
8
9
{
"build": {
"version": "0.0.16-SNAPSHOT",
"artifact": "ingredient-service",
"name": "ingredient-service",
"group": "sia5",
"time": "2018-06-04T00:24:04.373Z"
}
}

这个信息对于我们理解正在运行的应用的确切版本和构建时间是非常有用的。通过向“/info”端点发送GET请求,我们就能知道正在运行的是不是项目的最新构建版本。

暴露Git提交信息

假设我们的项目使用Git进行源码控制,那么我们可以在“/info”端点中包含Git提交信息。为了实现这一点,我们需要添加如下的插件到Maven项目的pom.xml文件中:

1
2
3
4
5
6
7
8
9
<build>
<plugins>
...
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
</plugin>
</plugins>
</build>

如果你是Gradle用户,也不用担心,我们可以将一个功能相同的插件放到build.gradle文件中:

1
2
3
plugins {
id "com.gorylenko.gradle-git-properties" version "1.4.17"
}

这两个插件完成的是相同的事情:它们会生成一个名为git.properties的构建期制件,这个文件包含了项目的所有Git元数据。在运行时,有个特殊的InfoContributor实现能够发现这个文件并将它的内容贡献给“/info”端点。

按照最简单的形式,“/info”端点展现的Git信息包括应用构建所使用的Git分支、提交的哈希值以及时间戳:

1
2
3
4
5
6
7
8
9
10
{
"git": {
"commit": {
"time": "2018-06-02T18:10:58Z",
"id": "b5c104d"
},
"branch": "master"
},
...
}

这些信息非常确定地描述了项目构建时代码的状态。但是,我们还可以将management.info.git.mode属性设置为full:

1
2
3
4
management:
info:
git:
mode: full

这样我们就能得到项目构建时非常详尽的Git提交信息。程序清单16.4展现了完整Git信息的一个样例。

程序清单16.4 通过“/info”端点展现完整的Git信息
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
46
47
{
"git": {
"build": {
"host": "DarkSide.local",
"version": "0.0.16-SNAPSHOT",
"time": "2018-06-02T18:11:23Z",
"user": {
"name": "Craig Walls",
"email": "craig@habuma.com"
}
},
"branch": "master",
"commit": {
"message": {
"short": "Add Spring Boot Admin and Actuator",
"full": "Add Spring Boot Admin and Actuator"
},
"id": {
"describe": "b5c104d-dirty",
"abbrev": "b5c104d",
"describe-short": "b5c104d-dirty",
"full": "b5c104d1fcbe6c2b84965ea08a330595100fd44e"
},
"time": "2018-06-02T18:10:58Z",
"user": {
"email": "craig@habuma.com",
"name": "Craig Walls"
}
},
"closest": {
"tag": {
"name": "",
"commit": {
"count": ""
}
}
},
"dirty": "true",
"remote": {
"origin": {
"url": "Unknown"
}
},
"tags": ""
},
...
}

除了时间戳和Git提交哈希值的缩略值,完整版本的信息还包含了代码提交者的名字和邮箱、完整的提交信息和其他内容,这样我们就能精确定位构建项目所使用的代码。实际上,我们可以看到程序清单16.4中dirty属性的值为true,表明在项目构建时构建目录中存在未提交的变更。没有什么信息比这更有说服力了!

16.3.2 实现自定义的健康指示器

Spring Boot提供了多个内置的健康指示器,它们能够提供与Spring应用进行交互的通用外部系统的健康信息。有时候你可能会发现,你所使用的外部系统在SpringBoot的预料之外,Spring Boot也没有为它提供健康指示器。例如,你的应用可能与一个遗留的大型机应用进行交互,应用的健康状况可能会受到遗留系统健康状况的影响。为了创建自定义的健康指示器,我们需要做的就是创建一个实现了HealthIndicator接口的bean。

实际上,Taco Cloud服务没有必要创建自定义的健康指示器,Spring Boot所提供的指示器就足够用了。为了阐述如何开发自定义的健康指示器,我们看一下程序清单16.5。它展现了一个简单的HealthIndicator实现,健康状况由每天中的时间所决定。

程序清单16.5 HealthIndicator的一个特殊实现
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
package tacos.tacos;
import java.util.Calendar;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class WackoHealthIndicator
implements HealthIndicator {
@Override
public Health health() {
int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
if (hour > 12) {
return Health
.outOfService()
.withDetail("reason",
"I'm out of service after lunchtime")
.withDetail("hour", hour)
.build();
}
if (Math.random() < 0.1) {
return Health
.down()
.withDetail("reason", "I break 10% of the time")
.build();
}
return Health
.up()
.withDetail("reason", "All is good!")
.build();
}
}

这个疯狂的健康指示器首先会判断当前是什么时间。如果是下午,那么所返回的健康状态是OUT_OF_SERVICE,其中还包含导致该状态的原因详情。即便是在午饭前,这个健康指示器也有10%的概率报告DOWN状态,因为它使用随机数来决定应用是否正常启动。如果随机数的值小于0.1,那么状态将是DOWN,否则状态将是UP。

显然,在真正的应用中,程序清单16.5的健康指示器不会有什么用处。但是,可以假设一下,我们不是根据当前时间或随机数,而是对外部系统发起一个远程调用,并基于接收到的响应状态来进行判定,这样的话它就是一个非常有用的健康指示器了。

16.3.3 注册自定义的指标

在16.2.4小节中,我们看到了如何访问“/metrics”端点来消费Actuator发布的各种指标,当时我们主要关注了HTTP请求的信息。Actuator提供的指标非常有用,但是“/metrics”端点的结果并不局限于内置的指标。

实际上,Actuator的指标是由Micrometer实现的。这是一个供应商中立的指标门面,借助它,我们能够发送任意想要的指标,并在所选的第三方监控系统中对其进行展现。它提供了对Prometheus、Datadog和New Relic等系统的支持。

使用Micrometer发布指标的最基本方式是借助Micrometer的MeterRegistry。在Spring Boot应用中,要发布指标的话,我们唯一需要做的就是将MeterRegistry注入到想要发布计数器、计时器和计量器(gauges)的地方,这些地方能够捕获应用的指标信息。

作为发布自定义指标的样例,假设我们想要统计不同配料所创建的taco的数量。也就是说,我们想要知道,使用生菜、碎牛肉、墨西哥薄饼以及其他配料分别制作了多少个taco。程序清单16.6中的TacoMetrics bean展示了如何使用MeterRegistry来收集信息。

程序清单16.6 TacoMetrics注册了关于taco配料的指标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package tacos.tacos;
import java.util.List;
import
org.springframework.data.rest.core.event.AbstractRepositoryEventListener
;
import org.springframework.stereotype.Component;
import io.micrometer.core.instrument.MeterRegistry;
@Component
public class TacoMetrics extends AbstractRepositoryEventListener<Taco> {
private MeterRegistry meterRegistry;
public TacoMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
protected void onAfterCreate(Taco taco) {
List<Ingredient> ingredients = taco.getIngredients();
for (Ingredient ingredient : ingredients) {
meterRegistry.counter("tacocloud",
"ingredient", ingredient.getId()).increment();
}
}
}

我们可以看到,TacoMetrics通过其构造器注入了MeterRegistry。它还扩展了AbstractRepositoryEventListener,这是Spring Data中的一个类,能够拦截repository事件。我们重写了onAfterCreate()方法,这样每当保存新的Taco对象时都会得到通知。

在onAfterCreate()中,我们为每种配料声明了一个计数器,其中标签名为ingredient,标签值为配料ID。如果给定标签的计数器已经存在,就会重用已有的计数器。计数器会不断递增,表明使用该配料又创建了一个taco。

在创建完几个taco之后,我们就可以查询“/metrics”端点来获取配料的计数信息了。对“/metrics/tacocloud”发送GET请求将会生成如下未经过滤的指标数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ curl localhost:8087/actuator/metrics/tacocloud
{
"name": "tacocloud",
"measurements": [ { "statistic": "COUNT", "value": 84 }
],
"availableTags": [
{
"tag": "ingredient",
"values": [ "FLTO", "CHED", "LETC", "GRBF",
"COTO", "JACK", "TMTO", "SLSA"]
}
]
}

measurements下的数值并没有太大的用处,它代表了所有配料的总数。但是,如果你想要知道使用墨西哥薄饼(FLTO)创建了多少个taco,那么我们可以将ingredient标签的值设置为FLTO:

1
2
3
4
5
6
7
8
$ curl localhost:8087/actuator/metrics/tacocloud?tag=ingredient:FLTO
{
"name": "tacocloud",
"measurements": [
{ "statistic": "COUNT", "value": 39 }
],
"availableTags": []
}

现在,我们可以清楚地看到,有39个taco是使用墨西哥薄饼作为其中的一道配料创建的。

16.3.4 创建自定义的端点

乍看上去,你可能会认为Actuator端点不过是使用Spring MVC的控制器实现的,但是在第18章中你将会发现,这些端点除了通过HTTP请求暴露之外,还暴露成JMX MBean。因此,它们肯定不仅仅是控制器类的端点。

实际上,Actuator端点的定义与控制器有很大的差异。Actuator端点并不是使用@Controller或@RestController注解来标注类,而是通过为类添加@Endpoint注解来实现的。

另外,它们不是使用HTTP方法命名的注解,如@GetMapping@PostMapping@DeleteMapping,Actuator端点的操作是通过为方法添加@ReadOperation@WriteOperation@DeleteOperation注解实现的。这些注解并没有指明任何的通信机制,实际上,这允许Actuator与各种各样的通信机制协作,内置了对HTTP和JMX的支持。

为了阐述如何编写自定义的Actuator,参见程序清单16.7中的NotesEndpoint。

程序清单16.7 用来记笔记的自定义端点
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
package tacos.ingredients;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.stereotype.Component;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Component
@Endpoint(id="notes", enableByDefault=true)
public class NotesEndpoint {
private List<Note> notes = new ArrayList<>();
@ReadOperation
public List<Note> notes() {
return notes;
}
@WriteOperation
public List<Note> addNote(String text) {
notes.add(new Note(text));
return notes;
}
@DeleteOperation
public List<Note> deleteNote(int index) {
if (index < notes.size()) {
notes.remove(index);
}
return notes;
}
@RequiredArgsConstructor
private class Note {
@Getter
private Date time = new Date();
@Getter
private final String text;
}
}

这是一个非常简单的记笔记的端点,我们可以通过写入操作提交笔记,通过读取操作阅读笔记列表并且通过删除操作移除某个笔记。不得不承认,这个端点并不像Actuator的端点那样有用。但是考虑到开箱即用的Actuator端点提供了如此众多的功能,所以很难设想一个自定义Actuator端点的实际样例。

不管怎么说,NotesEndpoint类使用了@Component注解,这样它会被Spring的组件扫描所发现,并将其初始化为Spring应用上下文中的bean。但是,与我们的讨论关联最大的事情是它还使用了@Endpoint注解,使其成为一个ID为notes的Actuator端点。它默认就是启用的,所以我们不需要在management.web.endpoints.web.exposure.include配置属性中显式启用它。

可以看到,NotesEndpoint提供了各种类型的操作。

  • notes()方法使用了@ReadOperation注解。当它被调用的时候,将会返回一个可用笔记的列表。按照HTTP的术语,这意味着它会处理针对“/actuator/notes”的HTTP GET请求并返回JSON格式的笔记列表。
  • addNote()方法使用了@WriteOperation注解。当它被调用的时候,将会根据给定的文本创建一个新的笔记并添加到列表中。按照HTTP的术语,它处理POST请求,请求体中是一个包含text属性的JSON对象。最后,它会在响应中返回当前笔记列表的状态。
  • deleteNote()方法使用了@DeleteOperation注解。当它被调用的时候,将会根据给定的索引删除一条笔记。按照HTTP的术语,这个端点会处理DELETE请求,其中索引是通过请求参数设置进来的。

为了看一下它的实际效果,我们可以使用curl测试新的端点。首先,使用两个单独的POST请求,添加两条笔记:

1
2
3
4
5
6
7
8
9
$ curl localhost:8080/actuator/notes \
-d'{"text":"Bring home milk"}' \
-H"Content-type: application/json"
[{"time":"2018-06-08T13:50:45.085+0000","text":"Bring home milk"}]
$ curl localhost:8080/actuator/notes \
-d'{"text":"Take dry cleaning"}' \
-H"Content-type: application/json"
[{"time":"2018-06-08T13:50:45.085+0000","text":"Bring home milk"},
{"time":"2018-06-08T13:50:48.021+0000","text":"Take dry cleaning"}]

我们可以看到,每当新增笔记的时候,端点都会返回增加新内容之后的笔记列表。如果想要查看笔记列表,我们可以发送一个简单的GET请求:

1
2
3
$ curl localhost:8080/actuator/notes
[{"time":"2018-06-08T13:50:45.085+0000","text":"Bring home milk"},
{"time":"2018-06-08T13:50:48.021+0000","text":"Take dry cleaning"}]

如果决定移除其中的某条笔记,那么我们可以发送一个DELETE请求,并将index作为请求参数:

1
2
$ curl localhost:8080/actuator/notes?index=1 -X DELETE
[{"time":"2018-06-08T13:50:45.085+0000","text":"Bring home milk"}]

很重要的一点就是,尽管我只展现了如何使用HTTP与端点交互,但是它们还会暴露为MBean,我们可以使用任意的JMX客户端来进行访问。如果你只想暴露HTTP端点,那么可以使用@WebEndpoint注解而不是@Endpoint来标注端点类:

1
2
3
4
5
@Component
@WebEndpoint(id="notes", enableByDefault=true)
public class NotesEndpoint {
...
}

类似的,如果你只想暴露MBean端点,那么可以使用@JmxEndpoint注解进行标注。

16.2 消费Actuator端点

Actuator是一个真正的宝藏,我们可以通过表16.1列出的HTTP端点获取正在运行中的应用的有用信息。作为HTTP端点,它们可以像任意REST API那样被消费,我们可以选择任意的HTTP客户端,包括Spring的RestTemplate和WebClient、基于浏览器的JavaScript应用以及简单的curl命令行客户端。

在探索Actuator端点的过程中,我们将会在本章中使用curl命令行客户端。在第17章中,我将会为你介绍Spring Boot Admin,这是一个构建在Actuator端点之上的用户友好的Web应用。

为了了解Actuator都提供了哪些端点,我们可以向Actuator的基础路径发送一个GET请求,这样能够得到每个端点的HATEOAS链接。如果使用curl向“/actuator”发送一个请求,那么我们将会看到如下所示的响应(为了节省空间,进行了删减):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ curl localhost:8081/actuator
{
"_links": {
"self": {
"href": "http://localhost:8081/actuator",
"templated": false
},
"auditevents": {
"href": "http://localhost:8081/actuator/auditevents",
"templated": false
},
"beans": {
"href": "http://localhost:8081/actuator/beans",
"templated": false
},
"health": {
"href": "http://localhost:8081/actuator/health",
"templated": false
},
...
}
}

因为不同的库可能会贡献自己的Actuator端点而且某些端点可能没有对外暴露,所以不同应用之间的实际结果也许会有所差异。

不管在什么情况下,Actuator基础路径提供的链接集合都可以作为Actuator所提供端点的一幅地图。我们首先从两个提供应用基本信息的端点开始探索Actuator,这两个端点就是“/health”和“/info”。

16.2.1 获取应用的基础信息

在去医院看病的时候,医生通常会首先问两个问题:你是谁?你感觉怎样?尽管医生或护士选择的说法可能会有所不同,但是他们的最终目的都是想要了解接诊的人以及为什么要去医院找医生看病。

Actuator的“/info”和“/health”端点为Spring Boot应用同等重要的问题提供了答案。“/info”端点告诉我们关于应用的信息,而“/health”端点则告诉我们应用健康状况的信息。

请求关于应用的信息

要了解正在运行中的应用的信息,我们可以请求“/info”端点。但是,默认情况下,“/info”并不会提供什么信息。如下是我们使用curl发送请求后可能会看到的效果:

1
2
$ curl localhost:8081/actuator/info
{}

虽然这样看起来,“/info”端点似乎没有太大的用处,但是我们最好将它视为一块干净的画布,我们可以在上面绘制任何想要展现的信息。

我们可以有多种为“/info”端点提供信息的方式,但是最简单直接的就是创建一个或多个属性名带有“info.”前缀的配置属性。例如,假设我们希望在“/info”的响应中包含售后支持的联系信息,包括Email地址和电话号码。为了实现这一点,我们可以在application.yml文件中配置如下的属性:

1
2
3
4
info:
contact:
email: support@tacocloud.com
phone: 822-625-6831

对于Spring Boot和应用上下文中的bean来说,info.contact.email property和info.contact.phone属性可能都没有什么特殊的意义。但是,因为它们的前缀是info,所以“/info”端点将会在响应中包含这两个属性的值:

1
2
3
4
5
6
{
"contact": {
"email": "support@tacocloud.com",
"phone": "822-625-6831"
}
}

在16.3.1小节,我们将会看到使用关于应用的有用信息来填充“/info”端点的其他几种方式。

探查应用的健康状况

发送HTTP GET请求到“/health”端点将会得到一个简单的JSON响应,其中包含了应用的健康状态。例如,如下是我们使用curl访问“/health”端点可能看到的响应:

1
2
$ curl localhost:8080/actuator/health
{"status":"UP"}

你可能会想,一个端点报告应用的状态是UP,这能有什么用处呢。如果应用停掉,那么它又该报告什么呢?

实际上,这里显示的是一个或多个健康指示器的聚合状态。健康指示器会报告应用要与之交互的外部系统的健康状态,比如数据库、消息代理甚至Spring Cloud组件,比如Eureka和Config Server。每个指示器的健康状态可能会是如下的可选值中的某一个。

  • UP:外部系统已经启动并且可以访问。
  • DOWN:外部系统已经停机或者不可访问。
  • UNKNOWN:外部系统的状态尚不清楚。
  • OUT_OF_SERVICE:外部系统可以访问得到,但是目前不可用。

所有健康指示器的状态会聚合成应用整体的健康状态,这个过程中会使用如下的规则。

  • 如果所有指示器都是UP,那么应用的健康状态是UP。
  • 如果一个或多个健康指示器是DOWN,那么应用的健康状态就是DOWN。
  • 如果一个或多个健康指示器是OUT_OF_SERVICE,那么应用的健康状态就是OUT_OF_SERVICE。
  • UNKNOWN的健康状态会被忽略,不会计入应用的聚合状态中。

默认情况下,请求“/health”端点的响应中只会包含聚合的状态。但是,我们可以配置management.endpoint.health.show-details属性,以便于展示所有健康指示器的完整细节:

1
2
3
4
management:
endpoint:
health:
show-details: always

management.endpoint.health.show-details属性的默认值是never。我们可以将它设置成always,这样就会始终显示健康指示器的完整细节;也可以将其设置成when-authorized,只有当客户端是完整认证的情况下才展示完整的细节信息。

现在,我们向“/health”端点发送GET请求的话,就会得到健康指示器的完整细节。如下是一个与Mongo文档数据库集成的服务样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"status": "UP",
"details": {
"mongo": {
"status": "UP",
"details": {
"version": "3.2.2"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 499963170816,
"free": 177284784128,
"threshold": 10485760
}
}
}
}

所有的应用,不管其外部依赖是什么,至少都会有一个针对文件系统的健康指示器,名为diskSpace。diskSpace健康指示器能够显示文件系统的健康状况(希望它是UP状态),这个状态的值是由还有多少剩余空间决定的。如果可用磁盘空间低于阈值,那么它将会报告DOWN的状态。

在前面的样例中,还有一个mongo健康指示器,它报告了Mongo数据库的状态。细节信息包括了Mongo数据库的版本。

自动配置功能能够确保只有与应用程序相关的健康指示器才会显示到“/health”端点中。除了mongo和diskSpace健康指示器,Spring Boot还为多个外部数据库和系统提供了健康指示器,包括:

  • Cassandra
  • Config Server
  • Couchbase
  • Eureka
  • Hystrix
  • JDBC数据源
  • Elasticsearch
  • InfluxDB
  • JMS消息代理
  • LDAP
  • Email服务器
  • Neo4j
  • Rabbit消息代理
  • Redis
  • Solr

另外,第三方库可以贡献自己的健康指示器。我们将会在16.3.2小节看一下如何编写自定义的健康指示器。

我们可以看到“/info”和“/health”端点提供了正在运行中的应用的基本信息。同时,还有一些其他的Actuator端点能够探查应用内部的配置信息。接下来,我们看一下Actuator是如何展现应用的配置的。

16.2.2 查看配置细节

除了接收应用的基本信息之外,了解应用是如何配置的也很有指导意义。例如,应用上下文中都有哪些bean?自动配置中哪些条件通过了,哪些条件失败了?应用中有哪些可用的环境变量?HTTP请求是如何映射控制器的?某些包或类所设置的日志级别是什么?

这些问题可以通过Actuator的“/beans”“/conditions”“/env”“/configprops”“/mappings”和“/loggers”端点来回答。在有些情况下,我们甚至还可以使用“/env”和“/loggers”端点,在应用运行的过程中对配置信息进行调整。我们将会逐个看一下这些端点,它们能够让我们洞察正在运行中的应用的配置情况。下面首先从“/beans”端点开始。

获取bean的装配报告

要研究Spring应用上下文,最基础的端点就是“/beans”。这个端点返回的JSON文档描述了应用上下文中的每个bean,其中包括它的Java类型以及它被注入的其他bean。

对“/beans”端点发送GET请求的完整响应可以轻松地填满这一整章。所以,我们不会列出“/beans”的完整响应,而是只考虑下面的片段,主要关注一个bean条目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"contexts": {
"application-1": {
"beans": {
...
"ingredientsController": {
"aliases": [],
"scope": "singleton",
"type": "tacos.ingredients.IngredientsController",
"resource": "file [/Users/habuma/Documents/Workspaces/
TacoCloud/ingredient-service/target/classes/tacos/
ingredients/IngredientsController.class]",
"dependencies": [
"ingredientRepository"
]
},
...
},
"parentId": null
}
}
}

响应的根元素是contexts,它包含了一个子元素,代表应用中的每个Spring应用上下文。在每个应用上下文中,都有一个beans元素,它包含了应用上下文所有bean的细节。

在上面的样例中,显示了名为ingredientsController的bean。我们可以看到,它没有别名,scope是singleton并且类型为tacos.ingredients.IngredientsController。另外,resource属性指向了定义这个bean的类文件路径。dependencies属性列出了注入到给定bean的所有其他bean。在本例中,ingredientsController被注入了一个名为ingredientRepository的bean。

阐述自动装配

我们可以看到,自动装配是Spring Boot提供的最强大的功能之一。但是,有时候你可能想要知道这些功能为什么会自动装配在一起。或者,你认为某些功能已经自动装配了,但是它们实际上却没有,你可能想要知道原因所在。在这种情况下,我们可以向“/conditions”发送GET请求,这样我们会知道自动装配过程中都发生了什么。

“/conditions”端点的自动装配报告可以分为3部分:匹配上的(positivematches,即已通过的条件化配置)、未匹配上的(negative matches,即失败的条件化配置)以及非条件化的类。如下的片段是对“/conditions”请求的响应,展现了每个组成部分的示例:

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
{
"contexts": {
"application-1": {
"positiveMatches": {
...
"MongoDataAutoConfiguration#mongoTemplate": [
{
"condition": "OnBeanCondition",
"message": "@ConditionalOnMissingBean (types:
org.springframework.data.mongodb.core.MongoTemplate;
SearchStrategy: all) did not find any beans"
}
],
...
},
"negativeMatches": {
...
"DispatcherServletAutoConfiguration": {
"notMatched": [
{
"condition": "OnClassCondition",
"message": "@ConditionalOnClass did not find required
class 'org.springframework.web.servlet.
DispatcherServlet'"
}
],
"matched": []
},
...
},
"unconditionalClasses": [
...
"org.springframework.boot.autoconfigure.context.
ConfigurationPropertiesAutoConfiguration",
...
]
}
}
}

在positiveMatches区域中,我们可以看到通过自动配置创建了一个MongoTemplate bean,这是因为目前上下文中还没有这样的bean。导致这种配置结果的原因是这里包含了@ConditionalOnMissingBean注解,如果没有明确配置这个bean,就会自动配置它。在本例中,并没有找到MongoTemplate类型的bean,因此自动配置功能介入并创建了一个该类型的bean。

在negativeMatches区域中,Spring Boot要尝试配置一个DispatcherServlet。但是,@ConditionalOnClass条件化注解失败了,这是因为没有找到DispatcherServlet类。

最后,在unconditionalClasses区域中是一个无条件配置的ConfigurationPropertiesAutoConfiguration。配置属性是Spring Boot操作的基础,所以任何与配置属性相关的配置都应该无条件自动装配。

探查环境和配置属性

除了知道应用的bean是如何装配在一起的,我们可能还对有哪些可用的环境属性以及bean中都注入了哪些配置属性感兴趣。

当我们向“/env”端点发送GET请求的时候,我们会得到一个非常长的响应,它包含了Spring应用中所有发挥作用的属性源。其中包括来自环境变量、JVM系统属性、application.properties和application.yml文件甚至来自Spring CloudConfig Server(该应用是Config Server客户端)的属性。

程序清单16.1列出了“/env”端点能够得到的响应示例,不过进行了删减,这样你会对它所提供的信息有一个大致了解:

程序清单16.1 “/env”端点的结果
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
$ curl localhost:8081/actuator/env
{
"activeProfiles": [
"development"
],
"propertySources": [
...
{
"name": "systemEnvironment",
"properties": {
"PATH": {
"value": "/usr/bin:/bin:/usr/sbin:/sbin",
"origin": "System Environment Property \"PATH\""
},
...
"HOME": {
"value": "/Users/habuma",
"origin": "System Environment Property \"HOME\""
}
}
},
{
"name": "applicationConfig: [classpath:/application.yml]",
"properties": {
"spring.application.name": {
"value": "ingredient-service",
"origin": "class path resource [application.yml]:3:11"
},
"server.port": {
"value": 8081,
"origin": "class path resource [application.yml]:9:9"
},
...
}
},
...
]
}

“/env”的完整响应会包含更多的信息,但是程序清单16.1只包含了几个值得注意的元素。首先,在响应的顶部是名为activeProfiles的字段。在本例中,它表明development profile处于激活状态。如果还有其他profile处于激活状态,那么也将会列到这里。

随后,propertySources字段是一个数组,Spring应用环境的每个属性源对应其中的一个条目。在程序清单16.1中,只显示了systemEnvironment以及引用application.yml文件的属性源。

在每个属性源中,是该属性源所提供的属性的列表以及它们的值。在application.yml属性源中,每个属性的origin字段指明了该属性是在哪里设置的,包括在application.yml文件中的行号和列号。

“/env”端点也可以用来获取特定的属性,只需要将属性名作为路径的第二个元素即可。例如,要检查server.port属性,我们可以提交GET请求到“/env/server.port”,如下所示:

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
$ curl localhost:8081/actuator/env/server.port
{
"property": {
"source": "systemEnvironment", "value": "8081"
},
"activeProfiles": [ "development" ],
"propertySources": [
{ "name": "server.ports" },
{ "name": "mongo.ports" },
{ "name": "systemProperties" },
{ "name": "systemEnvironment",
"property": {
"value": "8081",
"origin": "System Environment Property \"SERVER_PORT\""
}
},
{ "name": "random" },
{ "name": "applicationConfig: [classpath:/application.yml]",
"property": {
"value": 0,
"origin": "class path resource [application.yml]:9:9"
}
},
{ "name": "springCloudClientHostInfo" },
{ "name": "refresh" },
{ "name": "defaultProperties" },
{ "name": "Management Server" }
]
}

我们可以看到,这里依然会展现所有的属性源,但是只有包含特定属性的属性源才会显示额外的信息。在本例中,systemEnvironment属性源和application.yml属性源都包含了server.port属性的值。因为systemEnvironment属性源要优先于后面所列的属性源,所以它的值8081会胜出。胜出的值也会反映在顶部的property字段中。

不仅可以用“/env”端点来读取属性的值,还可以通过向“/env”端点发送POST请求,同时提交JSON文档格式的name和value字段,为正在运行的应用设置属性。例如,要将名为tacocloud.discount.code的属性设置为TACOS1234,我们可以在命令行使用curl提交POST请求,如下所示:

1
2
3
4
$ curl localhost:8081/actuator/env \
-d'{"name":"tacocloud.discount.code","value":"TACOS1234"}' \
-H "Content-type: application/json"
{"tacocloud.discount.code":"TACOS1234"}

在提交该属性之后,在返回的响应中将会包含新设置的属性和它的值。如果后续不需要这个属性,那么我们可以提交一个DELETE请求到“/env”端点,将通过该端点创建的所有属性删除:

1
2
$ curl localhost:8081/actuator/env -X DELETE
{"tacocloud.discount.code":"TACOS1234"}

通过Actuator API设置属性是非常有用的,但是需要记住所有通过向“/env”端点发送POST请求设置的属性只会用到接收到该请求的应用中,是临时的,应用重启的话就会丢失。

HTTP映射导览

尽管Spring MVC(以及Spring WebFlux)编程模型非常易于处理HTTP请求,我们只需要为方法添加请求映射注解即可,但是我们很难对应用整体能够处理哪些HTTP请求以及每种组件分别处理哪些请求有一个整体的了解。

Actuator的“/mappings”端点为应用中的所有HTTP请求处理器提供了一个一站式的视图,不管这些处理器是来自Spring MVC控制器还是Actuator端点,我们都能一目了然地看清。要获取Spring Boot应用中所有端点的完整列表,我们只需要向“/mappings”发送一个GET请求,就会看到大致如程序清单16.2所示的响应。

程序清单16.2 “/mappings”端点所展示的HTTP映射
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
46
47
48
$ curl localhost:8081/actuator/mappings | jq
{
"contexts": {
"application-1": {
"mappings": {
"dispatcherHandlers": {
"webHandler": [
...
{
"predicate": "{[/ingredients],methods=[GET]}",
"handler": "public
reactor.core.publisher.Flux<tacos.ingredients.Ingredient>
tacos.ingredients.IngredientsController.allIngredients()",
"details": {
"handlerMethod": {
"className": "tacos.ingredients.IngredientsController",
"name": "allIngredients",
"descriptor": "()Lreactor/core/publisher/Flux;"
},
"handlerFunction": null,
"requestMappingConditions": {
"consumes": [],
"headers": [],
"methods": [
"GET"
],
"params": [],
"patterns": [
"/ingredients"
],
"produces": []
}
}
},
...
]
}
},
"parentId": "application-1"
},
"bootstrap": {
"mappings": {
"dispatcherHandlers": {}
},
"parentId": null
}
}
}

为了简洁,这个响应进行了删减,只包含了一个请求处理器。具体来讲,它表明对“/ingredients”的GET请求将由IngredientsController的allIngredients()方法来处理。

管理日志级别

对于任何应用来说,日志都是很重要的特性。日志是一种审计方式,也是一种较为粗略的调试方法。

设置日志级别是一种需要很强平衡能力的事情。如果我们将日志级别设置得太低,那么日志中会有太多的噪声,查找有用的信息会变得很困难。另外,如果我们将日志级别设置地过于简洁,那么日志对于理解应用正在做什么可能没有太大的价值。

日志级别通常会基于Java包来进行设置。如果你想要知道正在运行的应用中使用了什么日志级别,那么可以向“/loggers”端点发送GET请求。如下的JSON展示了“/loggers”响应的一个片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"levels": [ "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" ],
"loggers": {
"ROOT": {
"configuredLevel": "INFO", "effectiveLevel": "INFO"
},
...
"org.springframework.web": {
"configuredLevel": null, "effectiveLevel": "INFO"
},
...
"tacos": {
"configuredLevel": null, "effectiveLevel": "INFO"
},
"tacos.ingredients": {
"configuredLevel": null, "effectiveLevel": "INFO"
},
"tacos.ingredients.IngredientServiceApplication": {
"configuredLevel": null, "effectiveLevel": "INFO"
}
}
}

在响应的顶部首先是所有合法日志级别的列表。在此之后,loggers元素列出了应用中每个包的日志级别详情。configuredLevel属性展示了明确配置的日志级别(如果没有明确配置的话,将会显示null)。effectiveLevel属性展示的是实际的日志级别,它可能是从父包或根logger继承下来的。

尽管这个片段只展现了根logger和4个包的日志级别,但是完整的响应会包含应用中每个包的日志级别,包括我们所使用的库对应的包。如果你只关心特定的包,那么可以在请求中以额外路径组件的方式指明包的名称。

例如,你只想知道taco.ingredients包的日志级别,那么可以发送请求到“/loggers/tacos/ ingredients”:

1
2
3
4
{
"configuredLevel": null,
"effectiveLevel": "INFO"
}

除了返回应用程序中包的日志级别之外,通过向“/loggers”端点发送POST请求,我们还能修改已配置的日志级别。例如,假设我们想要将taco.ingredients包的日志级别设置为DEBUG。如下的curl命令能够实现这一点:

1
2
3
$ curl localhost:8081/actuator/loggers/tacos/ingredients \
-d'{"configuredLevel":"DEBUG"}' \
-H"Content-type: application/json"

现在,日志级别已经发生了变化,我们可以向“/loggers/tacos/ingredients”发送GET请求,看一下它变成了什么样子:

1
2
3
4
{
"configuredLevel": "DEBUG",
"effectiveLevel": "DEBUG"
}

注意,在此之前,configuredLevel的值为null,现在它变成了DEBUG。这个变更也会影响到effectiveLevel。最重要的是,如果这个包中的代码以debug级别打印日志,那么日志文件中将会包含debug级别的信息。

16.2.3 查看应用的活动

如果我们能够时刻监视运行中应用的活动,那将会非常有用,我们所关注的信息可能包括应用正在处理什么类型的HTTP请求以及应用中所有线程的活动。为了实现这一点,Actuator提供了“/httptrace”“/threaddump”和“/heapdump”端点。

“/heapdump”端点可能是最难以详细阐述的Actuator端点。简而言之,它会下载一个gzip压缩的HPROF堆转储文件,该文件可以用来跟踪内存和线程问题。由于篇幅的原因,再加上堆转储文件的使用是一个非常高级的特性,所以对“/heapdump”端点的介绍就仅限于此。

跟踪HTTP活动

“/httptrace”端点能够报告应用所处理的最近100个请求的详情。详情内容包括请求的方法和路径、代表请求处理时刻的时间戳、请求和响应的头信息以及处理该请求的耗时。

如下的JSON片段展示了“/httptrace”端点响应的一个条目:

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
{
"traces": [
{
"timestamp": "2018-06-03T23:41:24.494Z",
"principal": null,
"session": null,
"request": {
"method": "GET",
"uri": "http://localhost:8081/ingredients",
"headers": {
"Host": ["localhost:8081"],
"User-Agent": ["curl/7.54.0"],
"Accept": ["*/*"]
},
"remoteAddress": null
},
"response": {
"status": 200,
"headers": {
"Content-Type": ["application/json;charset=UTF-8"]
}
},
"timeTaken": 4
},
...
]
}

尽管这些信息对调试很有价值,但是随着时间推移不断跟踪数据是更有意思的,基于响应的状态值,它能够让我们洞察应用程序在给定的时间内有多少请求是成功的、有多少请求是失败的。在第17章中,我们将会看到Spring Boot Admin是如何将这些信息捕获到一个运行图中的,借助这个图我们能够可视化一定的时间范围内的HTTP跟踪信息。

监控线程

除了HTTP请求的跟踪信息,在确定应用运行状况的时候,线程活动也是非常有用的。“/threaddump”端点能够生成一个当前线程活动的快照。通过如下的“/threaddump”端点响应片段,我们能够大致了解这个端点都提供了什么功能:

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
46
47
48
{
"threadName": "reactor-http-nio-8",
"threadId": 338,
"blockedTime": -1,
"blockedCount": 0,
"waitedTime": -1,
"waitedCount": 0,
"lockName": null,
"lockOwnerId": -1,
"lockOwnerName": null,
"inNative": true,
"suspended": false,
"threadState": "RUNNABLE",
"stackTrace": [
{
"methodName": "kevent0",
"fileName": "KQueueArrayWrapper.java",
"lineNumber": -2,
"className": "sun.nio.ch.KQueueArrayWrapper",
"nativeMethod": true
},
{
"methodName": "poll",
"fileName": "KQueueArrayWrapper.java",
"lineNumber": 198,
"className": "sun.nio.ch.KQueueArrayWrapper",
"nativeMethod": false
},
...
],
"lockedMonitors": [
{
"className": "io.netty.channel.nio.SelectedSelectionKeySet",
"identityHashCode": 1039768944,
"lockedStackDepth": 3,
"lockedStackFrame": {
"methodName": "lockAndDoSelect",
"fileName": "SelectorImpl.java",
"lineNumber": 86,
"className": "sun.nio.ch.SelectorImpl",
"nativeMethod": false
}
},
...
],
"lockedSynchronizers": [],
"lockInfo": null
}

完整的线程转储报告包含了运行中应用的每个线程。为了节省空间,这里的线程转储进行了删减,只包含了一个线程条目。我们可以看到,这里包含了线程的阻塞和锁定状态,以及其他的线程细节。这里还有一个堆栈,它能够展现线程都将时间花到了哪块代码中。

因为“/threaddump”只提供了请求时线程活动的快照,所以它很难完整了解随着时间的推移线程的行为都是什么样子的。在第17章中,我们将会看到SpringBoot Admin如何在一个实时视图中监视“/threaddump”端点。

16.2.4 获取应用的指标

“/metrics”端点能够报告运行中的应用程序所生成的各种度量指标,包括关于内存、处理器、垃圾收集以及HTTP请求的指标。Actuator提供了20多个开箱即用的指标分类,当我们向“/metrics”发送GET请求时所得到的指标分类证明了这一点:

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
$ curl localhost:8081/actuator/metrics | jq
{
"names": [
"jvm.memory.max",
"process.files.max",
"jvm.gc.memory.promoted",
"http.server.requests",
"system.load.average.1m",
"jvm.memory.used",
"jvm.gc.max.data.size",
"jvm.memory.committed",
"system.cpu.count",
"logback.events",
"jvm.buffer.memory.used",
"jvm.threads.daemon",
"system.cpu.usage",
"jvm.gc.memory.allocated",
"jvm.threads.live",
"jvm.threads.peak",
"process.uptime",
"process.cpu.usage",
"jvm.classes.loaded",
"jvm.gc.pause",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"process.files.open",
"jvm.buffer.count",
"jvm.buffer.total.capacity",
"process.start.time"
]
}

这里涉及太多的指标,所以本章不可能面面俱到地介绍。相反,我们可以只关注一个指标分类,即http.server.requests,以它作为样例介绍如何消费“/metrics”端点。

现在,我们不再简单地请求“/metrics”,而是发送GET请求到“/metrics/{METRICS CATEGORY}”,这样我们就会收到该分类的指标详情。就http.server.requests来说,我们发送GET请求到“/metrics/http.server.requests”所返回的数据如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ curl localhost:8081/actuator/metrics/http.server.requests
{
"name": "http.server.requests",
"measurements": [
{ "statistic": "COUNT", "value": 2103 },
{ "statistic": "TOTAL_TIME", "value": 18.086334315 },
{ "statistic": "MAX", "value": 0.028926313 }
],
"availableTags": [
{ "tag": "exception",
"values": [ "ResponseStatusException",
"IllegalArgumentException", "none" ] },
{ "tag": "method", "values": [ "GET" ] },
{ "tag": "uri",
"values": [
"/actuator/metrics/{requiredMetricName}",
"/actuator/health", "/actuator/info", "/ingredients",
"/actuator/metrics", "/**" ] },
{ "tag": "status", "values": [ "404", "500", "200" ] }
]
}

这个响应中最重要的组成部分是measurements区域,它包含了所请求分类的所有指标数据。在本例中,它表示一共有2103个HTTP请求,处理这些请求的总耗时是18.086334315秒,处理单个请求的最大耗时是0.028926313秒。

这些通用的指标非常有意思,但是我们可以使用availableTags中所列出的标签进一步细化结果。例如,我们知道一共有2103个请求,但是还不知道HTTP 200、HTTP 404或HTTP 500响应状态的请求分别有多少。借助status标签,我们可以得到所有状态为HTTP 404的请求指标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ curl localhost:8081/actuator/metrics/http.server.requests? \
tag=status:404
{
"name": "http.server.requests",
"measurements": [
{ "statistic": "COUNT", "value": 31 },
{ "statistic": "TOTAL_TIME", "value": 0.522061212 },
{ "statistic": "MAX", "value": 0 }
],
"availableTags": [
{ "tag": "exception",
"values": [ "ResponseStatusException", "none" ] },
{ "tag": "method", "values": [ "GET" ] },
{ "tag": "uri",
"values": [
"/actuator/metrics/{requiredMetricName}", "/**" ] }
]
}

通过使用tag请求属性指定标签名和值,我们可以看到所有响应为HTTP 404的请求的指标。这里显示有31个请求的结果是404,耗用了0.522061212秒。除此之外,我们可以看到有一些失败的请求是针对“/actuator/metrics/{requiredMetricsName}”的GET请求(尽管我们并不清楚{requiredMetricsName}路径变量解析成了什么)。另外,有些是发送其他路径的,是由“/**”通配符捕获到的。

如果我们想要知道有多少HTTP 404响应是发送到“/**”路径的,那么又该怎么办呢?我们所要做的就是进一步对其进行过滤,在请求中使用url标签,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
% curl "localhost:8081/actuator/metrics/http.server.requests? \
tag=status:404&tag=uri:/**"
{
"name": "http.server.requests",
"measurements": [
{ "statistic": "COUNT", "value": 30 },
{ "statistic": "TOTAL_TIME", "value": 0.519791548 },
{ "statistic": "MAX", "value": 0 }
],
"availableTags": [
{ "tag": "exception", "values": [ "ResponseStatusException" ] },
{ "tag": "method", "values": [ "GET" ] }
]
}

我们可以看到有30个路径匹配“/**”的请求得到了HTTP 404,并且处理这些请求耗费了0.519791548秒。

你可能也注意到了,随着我们不断细化请求的条件,可用的标签越来越有限。这里只列出了展现指标所对应的请求能够适用的标签。在本例中,exception和method标签只有一个值。显然,30个请求都是GET请求,并且都是因为抛出ResponseStatusException而产生的404状态。

导览整个“/metrics”可能是一件很麻烦的事情,但是稍加练习,我们一定能够找到自己想要的数据。在第17章中,我们将会看到借助Spring Boot Admin,我们能够更容易地消费“/metrics”端点的数据。

尽管Actuator端点所提供的信息有助于观察运行中Spring Boot应用的内部状况,但是它们并不适用于人类直接使用。作为REST端点,它们是供其他应用消费的,这里所说的其他应用也可能是UI。考虑到这一点,我们在第17章会看到如何在用户友好的Web应用中展现Actuator信息。现在,我们看一下如何自定义Actuator的端点。

16.1 Actuator概览

在机器领域中,执行机构(Actuator)指的是负责控制和移动装置的组件。在Spring Boot应用中,Spring Boot Actuator扮演了相同的角色,它能够让我们看到一个运行中的应用的内部状况,而且能够在一定程度上控制应用的行为。

通过Actuator暴露的端点,我们可以获取一个正在运行中的应用的内部状态。

  • 在应用环境中,都有哪些可用的配置属性?
  • 在应用中,各个源码包的日志级别是什么?
  • 应用消耗了多少内存?
  • 给定的HTTP端点被请求了多少次?
  • 应用本身以及与它协作的外部服务的健康状况如何?

为了在Spring Boot应用中启用Actuator,我们需要在构建文件中添加对Actuatorstarter的依赖。在Spring Boot应用的Maven pom.xml文件中,添加如下的<dependency>条目就能完成该任务:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

将Actuator starter添加到项目的构建文件中之后,应用就会具备一些开箱即用的Actuator端点,其中一部分如表16.1所示。

表16.1 探查运行中Spring Boot应用的状态并对其进行操作的Actuator端点

epub_29101559_120

按照上述属性,要获取应用的健康信息,我们需要向“/management/health”发送GET请求。

16.1.2 启用和禁用Actuator端点

你可能已经发现,默认情况下,只有“/health”和“/info”端点是启用的。大多数Actuator端点会携带敏感信息,所以应该保护起来。我们可以使用SpringSecurity来锁定Actuator,但是因为Actuator本身没有安全保护,所以大多数端点默认都是禁用的,需要我们来选择对外暴露哪些端点。

有两个配置属性能够控制对外暴露哪些端点,它们分别是management.endpoints.web.exposure.include和management.endpoints.web.exposure.exclude。通过management.endpoints.web. exposure.include属性,我们可以指定哪些端点想要暴露出来。例如,想要暴露“/health”“/info”“/beans”和“/conditions”的话,我们可以通过如下的配置来声明:

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: health,info,beans,conditions

management.endpoints.web.exposure.include属性也可以接受星号(*)作为通配符,表明所有的Actuator端点都会对外暴露:

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: '*'

如果除了个别端点之外,我们想暴露其他的所有端点,那么一般来讲更简单的方式是通过通配符将它们全部包含进来,然后明确排除一部分。例如,我们想要暴露除了“/threaddump”和“/heapdump”之外的端点,那么可以按照如下的形式同时设置management.endpoints.web.exposure.include和management.endpoints.web.exposure.exclude属性:

1
2
3
4
5
6
management:
endpoints:
web:
exposure:
include: '*'
exclude: threaddump,heapdump

如果你决定对外公开比“/health”和“/info”更多的信息,那么最好配置SpringSecurity来限制对其他端点的访问。我们将会在16.4节中了解如何保护Actuator端点。现在,我们看一下如何消费Actuator对外暴露的HTTP端点。

第16章 使用Spring Boot Actuator

本章内容:
  • 在Spring Boot项目中启用Actuator
  • 探索Actuator的端点
  • 自定义Actuator
  • 保护Actuator

你有没有试图猜测包装好的礼物盒中到底有什么东西的经历?你可能摇晃、掂量或者用尺子测量它。对于里面有什么东西,你可能会有一个确定的想法。但是,在真正将它打开之前,我们无法完全确定。

运行中的应用有点像包装好的礼物。你可以探测一下它,然后对里面的运行状况做出一个合理的猜测。但是,我们该如何确定呢?如果能有一种方式让我们窥探运行中的应用,假设我们能够查看它的行为、检查它的健康状况,甚至触发影响它运行的各种操作,那就太好了!

在本章中,我们将会讨论Spring Boot的Actuator。Actuator提供了生产环境可用的特性,包括监控Spring Boot应用和获取它的各种指标。Actuator的特性是通过各种端点提供的,这些端点可以通过HTTP调用,也可以通过JMX MBean来使用。在本章中,我们主要关注HTTP端点,而对JMX端点的介绍留到第19章。