Spring Boot Actuator詳解與深入應用(二):Actuator 2.x
《Spring Boot Actuator詳解與深入應用》預計包括三篇,第一篇重點講Spring Boot Actuator 1.x的應用與定製端點;第二篇將會對比Spring Boot Actuator 2.x 與1.x的區別,以及應用和定製2.x的端點;第三篇將會介紹Actuator metric指標與Prometheus和Grafana的使用結合。這部分內容很常用,且較為入門,歡迎大家的關注。
前文回顧
本文系《Spring Boot Actuator詳解與深入應用》中的第二篇。在上一篇文章:Spring Boot Actuator詳解與深入應用(一):Actuator 1.x
2.1.0.RELEASE
。關於Spring Boot2.x的特性,在此不詳細敘述了,但是其流行的趨勢是顯而易見的。
本文將會對比Spring Boot Actuator 2.x 與1.x的區別,以及應用和定製2.x的端點。重點介紹最新的2.x版本的Actuator。
Actuator 2.x
Actuator 2.x繼續保持其基本功能,但簡化其模型,擴充套件其功能幷包含合適的預設值。首先,這個版本變得與特定框架解耦;此外,它通過將其與應用程式合併來簡化其安全模型;最後,在各種變化中,有些變化是巨大的,這包括HTTP請求/響應以及提供的Java API。此外,最新版本支援CRUD模型,而不是舊的RW(讀/寫)模型。
在Actuator 1.x中,它與Spring MVC繫結,因此與Servlet API相關聯。而在2.x中,Actuator定義了它的模型可插拔且可擴充套件,而不依賴於MVC。因此,通過這個新模型,我們可以像MVC一樣使用WebFlux作為底層Web技術。此外,以後的框架可以通過實現特定的介面卡來增加到這個模型中。在沒有任何額外的程式碼的情況下,JMX仍然支援暴露端點。
快速開始
引入如下的依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
複製程式碼
和使用的Spring Boot Actuator 1.x並沒有太大的區別。
Actuator 2.x的變化
不同於之前的Actuator 1.x,Actuator 2.x 的大多數端點預設被禁掉。 Actuator 2.x 中的預設端點增加了/actuator字首。
預設暴露的兩個端點為/actuator/health 和 /actuator/info。我們可以通過設定如下的屬性:
management.endpoints.web.exposure.include=*
複製程式碼
可以使得所有的端點暴露出來。此外,我們也可以列出需要暴露的端點或者排除某些端點。如:
management.endpoints.web.exposure.exclude=env,beans
複製程式碼
預設端點
下面我們看一下可用的端點,他們大部分在1.x中已經存在。儘管如此,有些端點新增,有些被刪除,有些被重構。
- /auditevents:同Actuator 1.x,還可以通過關鍵字進行過濾
- /beans:同Actuator 1.x,不可以過濾
- /conditions:返回服務中的自動配置項
- /configprops:允許我們獲取
@ConfigurationProperties
的bean物件 - /env:返回當前的環境變數,我們也可以檢索某個值
- /flyway:提供Flyway資料庫遷移的詳細情況
- /health:同Actuator 1.x
- /heapdump:返回應用服務使用地jvm堆dump資訊
- /info:同Actuator 1.x
- /liquibase:類似於 /flyway,但是元件工具為Liquibase
- /logfile:返回應用的普通日誌檔案
- /loggers:允許我們查詢和修改應用的日誌等級
- /metrics:同Actuator 1.x
- /prometheus:返回與/metrics類似,與Prometheus server一起使用
- /scheduledtasks:返回應用的週期性任務
- /sessions:同Actuator 1.x
- /shutdown:同Actuator 1.x
- /threaddump:dump所依賴的jvm執行緒資訊
Actuator的端點安全
Actuator端點是敏感的,必須防止未經授權的訪問。 如果應用程式中存在Spring Security,則預設情況下使用基於表單的HTTP基本身份驗證來保護端點。使用Spring Security保護Actuator的端點訪問。
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
複製程式碼
安全配置
為了應用Actuator的安全規則,我們增加如下的配置:
@Bean
public SecurityWebFilterChain securityWebFilterChain(
ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated()
.and().build();
}
複製程式碼
如上的配置使得所有訪問/actuator開頭的URL都必須是登入的狀態。我們還可以使用更加細化的配置:
@Configuration
public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers(EndpointRequest.to(ShutdownEndpoint.class))
.hasRole("ACTUATOR_ADMIN")
.requestMatchers(EndpointRequest.toAnyEndpoint())
.permitAll()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.permitAll()
.antMatchers("/")
.permitAll()
.antMatchers("/**")
.authenticated()
.and()
.httpBasic();
}
}
複製程式碼
如上的配置主要實現了:
- 限定訪問Shutdown端點的角色只能是ACTUATOR_ADMIN
- 允許訪問其他所有的端點
- 允許訪問靜態資源
- 允許訪問根目錄'/'
- 所有的請求都要經過認證
- 允許http靜態認證(可以使用任何形式的認證)
測試
為了能夠使用HTTP基本身份驗證測試上述配置,可以新增預設的spring安全性使用者:
spring:
security:
user:
name: actuator
password: actuator
roles: ACTUATOR_ADMIN
複製程式碼
/health端點
與以前的版本一樣,我們可以輕鬆新增自定義指標。建立自定義健康端點的抽象保持不變。與Spring Boot 1.x不同,endpoints.<id> .sensitive
屬性已被刪除。/health端點公開的執行狀況資訊取決於:
management.endpoint.health.show-details
複製程式碼
該屬性可以使用以下值之一進行配置:
- never:不展示詳細資訊,up或者down的狀態,預設配置
- when-authorized:詳細資訊將會展示給通過認證的使用者。授權的角色可以通過
management.endpoint.health.roles
配置。 - always:暴露詳細資訊
/health端點有很多自動配置的健康指示器:如redis、rabbitmq等元件。
當如上的元件有一個狀態異常,應用服務的整體狀態即為down。我們也可以通過配置禁用某個元件的健康監測management.health.mongo.enabled: false
複製程式碼
或者禁用所有自動配置的健康指示器:
management.health.defaults.enabled: false
複製程式碼
除此之外,還添加了新的介面ReactiveHealthIndicator
以實現響應式執行狀況檢查。
引入依賴
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
複製程式碼
自定義reactive健康檢查
@Component
public class DownstreamServiceHealthIndicator implements ReactiveHealthIndicator {
@Override
public Mono<Health> health() {
return checkDownstreamServiceHealth().onErrorResume(
ex -> Mono.just(new Health.Builder().down(ex).build())
);
}
private Mono<Health> checkDownstreamServiceHealth() {
// we could use WebClient to check health reactively
return Mono.just(new Health.Builder().up().build());
}
}
複製程式碼
健康指標的一個便利功能是我們可以將它們聚合為層次結構的一部分。 因此,按照上面的示例,我們可以將所有下游服務分組到下游服務類別下。只要每個巢狀服務都可以訪問,此次訪問就是健康的。
/metrics端點
在Spring Boot 2.0中,有一個bean型別為MeterRegistry
將會被自動配置,並且MeterRegistry
已經包含在Actuator的依賴中。如下為我們獲得的/metrics端點資訊。
{
"names": [
"jvm.gc.pause",
"jvm.buffer.memory.used",
"jvm.memory.used",
"jvm.buffer.count",
// ...
]
}
複製程式碼
可以看到,不同於1.x,我們已經看不到具體的指標資訊,只是展示了一個指標列表。為了獲取到某個指標的詳細資訊,我們可以請求具體的指標資訊,如/actuator/metrics/jvm.gc.pause
{
"name": "jvm.gc.pause",
"description": "Time spent in GC pause",
"baseUnit": "seconds",
"measurements": [{
"statistic": "COUNT",
"value": 2.0
}, {
"statistic": "TOTAL_TIME",
"value": 0.07300000000000001
}, {
"statistic": "MAX",
"value": 0.0
}],
"availableTags": [{
"tag": "cause",
"values": ["Metadata GC Threshold"]
}, {
"tag": "action",
"values": ["end of minor GC", "end of major GC"]
}]
}
複製程式碼
我們可以看到,現在的指標要詳細得多。不僅包括不同的值,還包括一些相關的元資料。
/info端點
/info端點沒有什麼變化,我們可以通過maven或者gradle引入依賴,增加git的詳細資訊。
<dependency>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
</dependency>
複製程式碼
同樣的,我們可以使用maven和gradle的外掛,獲取到構建的name,group和version(需要類路徑下存在META-INF/build-info.properties檔案)。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
複製程式碼
自定義端點
我們可以自定義端點,Spring Boot 2已經更新了自定義端點的方法,下面我們定義一個可以查詢、開啟或者關閉features標誌位的端點。
@Component
@Endpoint(id = "features")
public class FeaturesEndpoint {
private Map<String, Feature> features = new ConcurrentHashMap<>();
@ReadOperation
public Map<String, Feature> features() {
return features;
}
@ReadOperation
public Feature feature(@Selector String name) {
return features.get(name);
}
@WriteOperation
public void configureFeature(@Selector String name, Feature feature) {
features.put(name, feature);
}
@DeleteOperation
public void deleteFeature(@Selector String name) {
features.remove(name);
}
public static class Feature {
private Boolean enabled;
//...
}
}
複製程式碼
定義的端點路徑由@Endpoint
中的id屬性決定,在如上的例子中,請求的端點地址為/actuator/features
。並用如下的方法註解來定義操作:
- @ReadOperation:HTTP GET
- @WriteOperation:HTTP POST
- @DeleteOperation:HTTP DELETE
啟動應用,可以看到控制檯多瞭如下的日誌輸出:
[...].WebFluxEndpointHandlerMapping: Mapped "{[/actuator/features/{name}],
methods=[GET],
produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features],
methods=[GET],
produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features/{name}],
methods=[POST],
consumes=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features/{name}],
methods=[DELETE]}"[...]
複製程式碼
如上的日誌展示了Webflux如何暴露我們的端點,至於切換到Spring MVC,我們只需要引入依賴即可,並不需要更改任何程式碼。
之前方法上的元資料資訊(sensitive, enabled)都不在使用了,開啟或禁用端點,使用@Endpoint(id = “features”, enableByDefault = false)
。相比於舊的讀寫模型,我們可以使用@DeleteOperation
定義DELETE操作。
擴充套件端點
我們還可以通過註解@EndpointExtension
擴充套件事先定義好的端點,更精確的註解為:@EndpointWebExtension
,@EndpointJmxExtension
。
@Component
@EndpointWebExtension(endpoint = InfoEndpoint.class)
public class InfoWebEndpointExtension {
private InfoEndpoint delegate;
@Autowired
public InfoWebEndpointExtension(InfoEndpoint delegate) {
this.delegate = delegate;
}
@ReadOperation
public WebEndpointResponse<Map> info() {
Map<String, Object> info = this.delegate.info();
Integer status = getStatus(info);
return new WebEndpointResponse<>(info, status);
}
private Integer getStatus(Map<String, Object> info) {
// return 5xx if this is a snapshot
return 200;
}
}
複製程式碼
總結
本文主要講了Actuator 2.x相關特性和使用,對比了與Actuator 1.x 在使用上的區別。Actuator 2.x不依賴於某個框架元件(如Spring MVC),做到了易於插拔和擴充套件。當我們想要切換到Webflux時,通過Actuator 2.x中的介面卡,不需要更改任何程式碼即可實現。