第11章 Spring Boot應用監控
在實際的生産系統中,我們怎樣知道我們的應用運作良好呢?我們往往需要對系統實際運作的情況(各種cpu,io,disk,db,業務功能等名額)進行監控運維。這需要耗費我們不少精力來搞這些工作。在SpringBoot中,我們完全不需要面對這樣的難題。
本章主要介紹使用Actuator對Spring Boot應用名額進行監控,以及通過遠端shell監控與管理我們的應用。
11.0 Actuator簡介
Actuator是spring boot提供的對應用系統的自省和監控功能,Actuator對應用系統本身的自省功能,可以讓我們友善快捷的實作線上運維監控的工作。這個有點DevOps的味道。
通過Actuator,我們可以使用資料化的名額去度量我們的應用的運作情況。比如檢視系統運作了多少線程,gc的情況,運作的基本參數等等
spring-boot-actuator子產品提供了一個監控和管理生産環境的子產品,可以使用http、jmx、ssh、telnet等拉管理和監控應用。
随着devops的興起,以及docker技術的普及,微服務在一定場合會越來越受歡迎。即使不說微服務,springboot這種可以直接内嵌web伺服器打成一個jar包的方式,也更符合devops的趨勢:打成個jar包,往伺服器上一扔,十分友善,自帶Actuator,把監控也給省了一大半,真正做到了可以把精力花在刀刃上。
11.1 使用Spring Boot Actuator監控應用
1.使用actuator
添加starter依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
重新開機應用。Spring Boot Actuator 的自動配置功能,會把Actuator的審計(Auditing)資訊、健康(health)資訊、各種度量資料采集(metrics gathering)會自動加到應用裡面。
Actuator主要暴露的功能如下:
HTTP方法 | 路徑 | 描述 | 是否敏感資訊 |
GET | /autoconfig | 檢視自動配置的使用情況, 顯示一個auto-configuration的報告,該報告展示所有auto-configuration候選者及它們被應用或未被應用的原因 | true |
GET | /configprops | 檢視配置屬性,包括預設配置, 顯示一個所有@ConfigurationProperties的整理清單 | true |
GET | /beans | bean及其關系清單, 顯示一個應用中所有Spring Beans的完整清單 | true |
GET | /dump | 列印線程棧 | true |
GET | /env | 檢視所有環境變量 | true |
GET | /env/{name} | 檢視具體變量值 | true |
GET | /health | 檢視應用健康名額, 當使用一個未認證連接配接通路時顯示一個簡單的’status’,使用認證連接配接通路則顯示全部資訊詳情 | false |
GET | /info | 檢視應用資訊 | false |
GET | /mappings | 檢視所有url映射, 即所有@RequestMapping路徑的整理清單 | true |
GET | /metrics | 檢視應用基本名額 | true |
GET | /metrics/{name} | 檢視具體名額 | true |
POST | /shutdown | 關閉應用,允許應用以優雅的方式關閉(預設情況下不啟用) | true |
GET | /trace | 檢視基本追蹤資訊,預設為最新的一些HTTP請求 | true |
這些HTTP端點(Endpoint),預設是系統根路徑通路的。如果我們想自定義context-path, 按照如下配置即可:
server.port=8888
#actuator
management.port=58888
management.context-path=/actuator
重新開機應用,我們将會看到如下日志:
01:23:38.033 [localhost-startStop-1] INFO o.s.b.w.s.FilterRegistrationBean - Mapping filter: 'springSecurityFilterChain' to: [/*]
01:23:38.283 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/info || /actuator/info.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.284 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/trace || /actuator/trace.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.285 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/beans || /actuator/beans.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.286 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/configprops || /actuator/configprops.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.343 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/mappings || /actuator/mappings.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.348 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/health || /actuator/health.json],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(java.security.Principal)
01:23:38.349 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/autoconfig || /actuator/autoconfig.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.352 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/heapdump || /actuator/heapdump.json],methods=[GET],produces=[application/octet-stream]}" onto public void org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint.invoke(boolean,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException,javax.servlet.ServletException
01:23:38.353 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/dump || /actuator/dump.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.362 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/metrics/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
01:23:38.362 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/metrics || /actuator/metrics.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.363 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/env/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
01:23:38.363 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/env || /actuator/env.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
01:23:38.364 [main] INFO o.s.b.a.e.m.EndpointHandlerMapping - Mapped "{[/actuator/server || /actuator/server.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
我們可以清楚的看到Actuator提供應用運作資訊監控等相關的HTTP Rest API(Endpoint)。
例如,我們通路 http://localhost:58888/actuator/env , 傳回了系統的環境參數資訊
// 20170504001108
// http://localhost:58888/actuator/env
{
"profiles": [
],
"server.ports": {
"local.management.port": 58888,
"local.server.port": 8888
},
"servletContextInitParams": {
},
"systemProperties": {
"java.runtime.name": "Java(TM) SE Runtime Environment",
"sun.boot.library.path": "/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib",
"java.vm.version": "25.40-b25",
"user.country.format": "CN",
......
"server.port": "8888",
"spring.jpa.hibernate.naming-strategy": "org.hibernate.cfg.ImprovedNamingStrategy",
"spring.jpa.hibernate.ddl-auto": "update",
"info.build.version": "1.0-SNAPSHOT",
"management.context-path": "/actuator",
"spring.datasource.url": "jdbc:mysql://localhost:3306/lightsword?useUnicode=true&characterEncoding=UTF8",
"endpoints.metrics.enabled": "true",
"endpoints.info.id": "info",
"management.port": "58888",
"spring.velocity.resourceLoaderPath": "classpath:/templates/",
"spring.mvc.static-path-pattern": "/**",
"spring.jpa.database": "MYSQL",
"spring.datasource.min-idle": "0",
"info.app.name": "ecs",
"spring.datasource.max-active": "0",
"spring.datasource.max-wait": "10000",
"management.security.sessions": "stateless",
"endpoints.metrics.id": "metrics",
"management.security.roles": "ADMIN",
"spring.application.name": "lightsword",
"spring.datasource.max-idle": "0",
"spring.datasource.password": "******",
"endpoints.actuator.enabled": "true",
"spring.datasource.username": "root",
"spring.velocity.properties.input.encoding": "UTF-8",
"logging.config": "classpath:logback-dev.groovy",
"endpoints.info.enabled": "true",
"spring.jpa.show-sql": "true",
"spring.velocity.toolbox-config-location": "/WEB-INF/toolbox.xml",
"info.build.artifactId": "lightsword",
"spring.velocity.properties.output.encoding": "UTF-8",
"spring.velocity.suffix": ".html",
"spring.datasource.driverClassName": "com.mysql.jdbc.Driver",
"spring.resources.static-locations": "classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/",
"spring.velocity.charset": "UTF-8",
"info.app.version": "1.0.0"
}
}
其他的一些端點,都帶有豐富的系統運作資料,感興趣的可以運作本章節的工程源碼,檢視端點的資料結構資訊。
同時,Actuator支援與security內建,按照如下配置即可啟用:
# Enable security.
management.security.enabled=true
# Comma-separated list of roles that can access the management endpoint.
management.security.roles=ADMIN
如果我們使用基于資料庫的user,role內建Security的權限認證,經過測試,發現這裡的management.security.roles=ADMIN将會不起作用。
如果我們想配置去掉某項的檢查,比如不監控health.mail:
server:
port: 8888
management:
port: 58888
health:
mail:
enabled: false
我們可以看出這個58888端口映射的路徑也是目前應用的伺服器資源。例如http://localhost:58888/js/jsoneditor.js 也能通路到。跟通路 http://localhost:8888/js/jsoneditor.js 是一樣的結果。
關于Actuator自動配置的源碼在org.springframework.boot.actuate.autoconfigure中。想要詳細深入的了解其中的原理,可以閱讀這裡的源碼。
在application.properties中關于 ACTUATOR的配置如下
# ----------------------------------------
# ACTUATOR PROPERTIES
# ----------------------------------------
# ENDPOINTS (AbstractEndpoint subclasses)
endpoints.enabled=true # Enable endpoints.
endpoints.sensitive= # Default endpoint sensitive setting.
endpoints.actuator.enabled=true # Enable the endpoint.
endpoints.actuator.path= # Endpoint URL path.
endpoints.actuator.sensitive=false # Enable security on the endpoint.
endpoints.auditevents.enabled= # Enable the endpoint.
endpoints.auditevents.path= # Endpoint path.
endpoints.auditevents.sensitive=false # Enable security on the endpoint.
endpoints.autoconfig.enabled= # Enable the endpoint.
endpoints.autoconfig.id= # Endpoint identifier.
endpoints.autoconfig.path= # Endpoint path.
endpoints.autoconfig.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.beans.enabled= # Enable the endpoint.
endpoints.beans.id= # Endpoint identifier.
endpoints.beans.path= # Endpoint path.
endpoints.beans.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.configprops.enabled= # Enable the endpoint.
endpoints.configprops.id= # Endpoint identifier.
endpoints.configprops.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regex expressions.
endpoints.configprops.path= # Endpoint path.
endpoints.configprops.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.docs.curies.enabled=false # Enable the curie generation.
endpoints.docs.enabled=true # Enable actuator docs endpoint.
endpoints.docs.path=/docs #
endpoints.docs.sensitive=false #
endpoints.dump.enabled= # Enable the endpoint.
endpoints.dump.id= # Endpoint identifier.
endpoints.dump.path= # Endpoint path.
endpoints.dump.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.env.enabled= # Enable the endpoint.
endpoints.env.id= # Endpoint identifier.
endpoints.env.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regex expressions.
endpoints.env.path= # Endpoint path.
endpoints.env.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.flyway.enabled= # Enable the endpoint.
endpoints.flyway.id= # Endpoint identifier.
endpoints.flyway.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.health.enabled= # Enable the endpoint.
endpoints.health.id= # Endpoint identifier.
endpoints.health.mapping.*= # Mapping of health statuses to HttpStatus codes. By default, registered health statuses map to sensible defaults (i.e. UP maps to 200).
endpoints.health.path= # Endpoint path.
endpoints.health.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.health.time-to-live=1000 # Time to live for cached result, in milliseconds.
endpoints.heapdump.enabled= # Enable the endpoint.
endpoints.heapdump.path= # Endpoint path.
endpoints.heapdump.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.hypermedia.enabled=false # Enable hypermedia support for endpoints.
endpoints.info.enabled= # Enable the endpoint.
endpoints.info.id= # Endpoint identifier.
endpoints.info.path= # Endpoint path.
endpoints.info.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.jolokia.enabled=true # Enable Jolokia endpoint.
endpoints.jolokia.path=/jolokia # Endpoint URL path.
endpoints.jolokia.sensitive=true # Enable security on the endpoint.
endpoints.liquibase.enabled= # Enable the endpoint.
endpoints.liquibase.id= # Endpoint identifier.
endpoints.liquibase.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.logfile.enabled=true # Enable the endpoint.
endpoints.logfile.external-file= # External Logfile to be accessed.
endpoints.logfile.path=/logfile # Endpoint URL path.
endpoints.logfile.sensitive=true # Enable security on the endpoint.
endpoints.loggers.enabled=true # Enable the endpoint.
endpoints.loggers.id= # Endpoint identifier.
endpoints.loggers.path=/logfile # Endpoint path.
endpoints.loggers.sensitive=true # Mark if the endpoint exposes sensitive information.
endpoints.mappings.enabled= # Enable the endpoint.
endpoints.mappings.id= # Endpoint identifier.
endpoints.mappings.path= # Endpoint path.
endpoints.mappings.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.metrics.enabled= # Enable the endpoint.
endpoints.metrics.filter.enabled=true # Enable the metrics servlet filter.
endpoints.metrics.filter.gauge-submissions=merged # Http filter gauge submissions (merged, per-http-method)
endpoints.metrics.filter.counter-submissions=merged # Http filter counter submissions (merged, per-http-method)
endpoints.metrics.id= # Endpoint identifier.
endpoints.metrics.path= # Endpoint path.
endpoints.metrics.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.shutdown.enabled= # Enable the endpoint.
endpoints.shutdown.id= # Endpoint identifier.
endpoints.shutdown.path= # Endpoint path.
endpoints.shutdown.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.trace.enabled= # Enable the endpoint.
endpoints.trace.filter.enabled=true # Enable the trace servlet filter.
endpoints.trace.id= # Endpoint identifier.
endpoints.trace.path= # Endpoint path.
endpoints.trace.sensitive= # Mark if the endpoint exposes sensitive information.
# ENDPOINTS CORS CONFIGURATION (EndpointCorsProperties)
endpoints.cors.allow-credentials= # Set whether credentials are supported. When not set, credentials are not supported.
endpoints.cors.allowed-headers= # Comma-separated list of headers to allow in a request. '*' allows all headers.
endpoints.cors.allowed-methods=GET # Comma-separated list of methods to allow. '*' allows all methods.
endpoints.cors.allowed-origins= # Comma-separated list of origins to allow. '*' allows all origins. When not set, CORS support is disabled.
endpoints.cors.exposed-headers= # Comma-separated list of headers to include in a response.
endpoints.cors.max-age=1800 # How long, in seconds, the response from a pre-flight request can be cached by clients.
# JMX ENDPOINT (EndpointMBeanExportProperties)
endpoints.jmx.domain= # JMX domain name. Initialized with the value of 'spring.jmx.default-domain' if set.
endpoints.jmx.enabled=true # Enable JMX export of all endpoints.
endpoints.jmx.static-names= # Additional static properties to append to all ObjectNames of MBeans representing Endpoints.
endpoints.jmx.unique-names=false # Ensure that ObjectNames are modified in case of conflict.
# JOLOKIA (JolokiaProperties)
jolokia.config.*= # See Jolokia manual
# MANAGEMENT HTTP SERVER (ManagementServerProperties)
management.add-application-context-header=false # Add the "X-Application-Context" HTTP header in each response.
management.address= # Network address that the management endpoints should bind to.
management.context-path= # Management endpoint context-path. For instance `/actuator`
management.cloudfoundry.enabled= # Enable extended Cloud Foundry actuator endpoints
management.cloudfoundry.skip-ssl-validation= # Skip SSL verification for Cloud Foundry actuator endpoint security calls
management.port= # Management endpoint HTTP port. Uses the same port as the application by default. Configure a different port to use management-specific SSL.
management.security.enabled=true # Enable security.
management.security.roles=ACTUATOR # Comma-separated list of roles that can access the management endpoint.
management.security.sessions=stateless # Session creating policy to use (always, never, if_required, stateless).
management.ssl.ciphers= # Supported SSL ciphers. Requires a custom management.port.
management.ssl.client-auth= # Whether client authentication is wanted ("want") or needed ("need"). Requires a trust store. Requires a custom management.port.
management.ssl.enabled= # Enable SSL support. Requires a custom management.port.
management.ssl.enabled-protocols= # Enabled SSL protocols. Requires a custom management.port.
management.ssl.key-alias= # Alias that identifies the key in the key store. Requires a custom management.port.
management.ssl.key-password= # Password used to access the key in the key store. Requires a custom management.port.
management.ssl.key-store= # Path to the key store that holds the SSL certificate (typically a jks file). Requires a custom management.port.
management.ssl.key-store-password= # Password used to access the key store. Requires a custom management.port.
management.ssl.key-store-provider= # Provider for the key store. Requires a custom management.port.
management.ssl.key-store-type= # Type of the key store. Requires a custom management.port.
management.ssl.protocol=TLS # SSL protocol to use. Requires a custom management.port.
management.ssl.trust-store= # Trust store that holds SSL certificates. Requires a custom management.port.
management.ssl.trust-store-password= # Password used to access the trust store. Requires a custom management.port.
management.ssl.trust-store-provider= # Provider for the trust store. Requires a custom management.port.
management.ssl.trust-store-type= # Type of the trust store. Requires a custom management.port.
# HEALTH INDICATORS
management.health.db.enabled=true # Enable database health check.
management.health.cassandra.enabled=true # Enable cassandra health check.
management.health.couchbase.enabled=true # Enable couchbase health check.
management.health.defaults.enabled=true # Enable default health indicators.
management.health.diskspace.enabled=true # Enable disk space health check.
management.health.diskspace.path= # Path used to compute the available disk space.
management.health.diskspace.threshold=0 # Minimum disk space that should be available, in bytes.
management.health.elasticsearch.enabled=true # Enable elasticsearch health check.
management.health.elasticsearch.indices= # Comma-separated index names.
management.health.elasticsearch.response-timeout=100 # The time, in milliseconds, to wait for a response from the cluster.
management.health.jms.enabled=true # Enable JMS health check.
management.health.ldap.enabled=true # Enable LDAP health check.
management.health.mail.enabled=true # Enable Mail health check.
management.health.mongo.enabled=true # Enable MongoDB health check.
management.health.rabbit.enabled=true # Enable RabbitMQ health check.
management.health.redis.enabled=true # Enable Redis health check.
management.health.solr.enabled=true # Enable Solr health check.
management.health.status.order=DOWN, OUT_OF_SERVICE, UP, UNKNOWN # Comma-separated list of health statuses in order of severity.
# INFO CONTRIBUTORS (InfoContributorProperties)
management.info.build.enabled=true # Enable build info.
management.info.defaults.enabled=true # Enable default info contributors.
management.info.env.enabled=true # Enable environment info.
management.info.git.enabled=true # Enable git info.
management.info.git.mode=simple # Mode to use to expose git information.
# TRACING (TraceProperties)
management.trace.include=request-headers,response-headers,cookies,errors # Items to be included in the trace.
# METRICS EXPORT (MetricExportProperties)
spring.metrics.export.aggregate.key-pattern= # Pattern that tells the aggregator what to do with the keys from the source repository.
spring.metrics.export.aggregate.prefix= # Prefix for global repository if active.
spring.metrics.export.delay-millis=5000 # Delay in milliseconds between export ticks. Metrics are exported to external sources on a schedule with this delay.
spring.metrics.export.enabled=true # Flag to enable metric export (assuming a MetricWriter is available).
spring.metrics.export.excludes= # List of patterns for metric names to exclude. Applied after the includes.
spring.metrics.export.includes= # List of patterns for metric names to include.
spring.metrics.export.redis.key=keys.spring.metrics # Key for redis repository export (if active).
spring.metrics.export.redis.prefix=spring.metrics # Prefix for redis repository if active.
spring.metrics.export.send-latest= # Flag to switch off any available optimizations based on not exporting unchanged metric values.
spring.metrics.export.statsd.host= # Host of a statsd server to receive exported metrics.
spring.metrics.export.statsd.port=8125 # Port of a statsd server to receive exported metrics.
spring.metrics.export.statsd.prefix= # Prefix for statsd exported metrics.
spring.metrics.export.triggers.*= # Specific trigger properties per MetricWriter bean name.
自動配置的 HealthIndicators
在目錄org.springframework.boot.actuate.health下面,Spring Boot 預設自動配了如下的HealthIndicators
- CassandraHealthIndicator
檢查 Cassandra database is up.
- DiskSpaceHealthIndicator
檢查 low disk space.
- DataSourceHealthIndicator
檢查資料庫連接配接是否正常
- ElasticsearchHealthIndicator
檢查Elasticsearch cluster is up.
- JmsHealthIndicator
檢查JMS broker is up.
- MailHealthIndicator
檢查mail server is up.
- MongoHealthIndicator
檢查Mongo database is up.
- RabbitHealthIndicator
檢查Rabbit server is up.
- RedisHealthIndicator
檢查Redis server is up.
- SolrHealthIndicator
檢查Solr server is up.
舉例說明,比如DataSourceHealthIndicator,其健康檢查代碼如下
private void doDataSourceHealthCheck(Health.Builder builder) throws Exception {
String product = getProduct();
builder.up().withDetail("database", product);
String validationQuery = getValidationQuery(product);
if (StringUtils.hasText(validationQuery)) {
try {
// Avoid calling getObject as it breaks MySQL on Java 7
List<Object> results = this.jdbcTemplate.query(validationQuery,
new SingleColumnRowMapper());
Object result = DataAccessUtils.requiredSingleResult(results);
builder.withDetail("hello", result);
}
catch (Exception ex) {
builder.down(ex);
}
}
}
實作邏輯很簡單,就是執行一次query,看傳回是否正常。
自定義 HealthIndicators
我們要自定義一個HealthIndicators,隻需要注冊一個Spring bean,實作HealthIndicator接口,實作其health()方法,傳回一個 Health 對象即可。
我們可以直接參考Spring Boot 預設自動配置的那些HealthIndicators,仿照着寫即可。
一個簡單的示例代碼如下:
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
不過有一點需要說明的是,Actuator 的這些HTTP Endpoints是基于 Spring MVC的應用的。
寫一個自己的Endpoint
隻需要實作Endpoint<T> 接口即可。這個接口的定義如下:
package org.springframework.boot.actuate.endpoint;
public interface Endpoint<T> {
String getId();
boolean isEnabled();
boolean isSensitive();
T invoke();
}
其中,getId()方法傳回的就是url的path。
我們來實作一個擷取目前運作伺服器的基本資訊的Endpoint,代碼如下:
package com.springboot.in.action.actuator
import java.net.InetAddress
import java.util
import org.springframework.boot.actuate.endpoint.Endpoint
import org.springframework.stereotype.Component
/**
* Created by jack on 2017/5/3.
*/
@Component
class ServerEndpoint extends Endpoint[java.util.List[String]] {
override def invoke(): java.util.List[String] = {
val serverDetails = new util.ArrayList[String]
try {
serverDetails.add("Server IP Address : " + InetAddress.getLocalHost.getHostAddress)
serverDetails.add("Server OS : " + System.getProperty("os.name").toLowerCase)
} catch {
case e: Exception =>
e.printStackTrace()
}
serverDetails
}
override def getId: String = "server"
override def isSensitive: Boolean = false
override def isEnabled: Boolean = true
}
重新部署運作,通路 http://localhost:58888/actuator/server ,我們看到輸出如下:
// 20170503235224
// http://localhost:58888/actuator/server
[
"Server IP Address : 192.168.1.104",
"Server OS : mac os x"
]
11.2 Spring Boot遠端Shell
Spring Boot通過內建Java shell架構CRaSH,讓我們可以使用ssh或telnet指令連接配接到運作的應用。
添加以下依賴即可啟用遠端shell支援:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-remote-shell</artifactId>
</dependency>
如果想使用telnet通路,需要另外添加org.crsh:crsh.shell.telnet的依賴。
配置完畢,重新開機應用。我們會看到啟動日志
01:00:42.611 [main] INFO o.s.b.a.a.CrshAutoConfiguration$CrshBootstrapBean - Configuring property auth.spring.roles=ADMIN from properties
01:00:42.619 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=SSHPlugin,interface=SSHPlugin]
01:00:42.621 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=SSHInlinePlugin,interface=CommandPlugin]
01:00:42.623 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=KeyAuthenticationPlugin,interface=KeyAuthenticationPlugin]
01:00:42.648 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=CronPlugin,interface=CronPlugin]
01:00:42.657 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=MailPlugin,interface=MailPlugin]
01:00:42.661 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=CRaSHShellFactory,interface=ShellFactory]
01:00:42.663 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=GroovyLanguageProxy,interface=Language]
01:00:42.680 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=JavaLanguage,interface=Language]
01:00:42.687 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=ScriptLanguage,interface=Language]
01:00:42.700 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=JaasAuthenticationPlugin,interface=AuthenticationPlugin]
01:00:42.730 [main] INFO org.crsh.plugin.PluginManager - Loaded plugin Plugin[type=SimpleAuthenticationPlugin,interface=AuthenticationPlugin]
01:00:42.739 [main] INFO o.s.b.a.a.CrshAutoConfiguration$CrshBootstrapBean - Configuring property ssh.port=2000 from properties
01:00:42.739 [main] INFO o.s.b.a.a.CrshAutoConfiguration$CrshBootstrapBean - Configuring property ssh.auth_timeout=600000 from properties
01:00:42.740 [main] INFO o.s.b.a.a.CrshAutoConfiguration$CrshBootstrapBean - Configuring property ssh.idle_timeout=600000 from properties
01:00:42.741 [main] INFO o.s.b.a.a.CrshAutoConfiguration$CrshBootstrapBean - Configuring property ssh.default_encoding=UTF-8 from properties
01:00:42.742 [main] INFO o.s.b.a.a.CrshAutoConfiguration$CrshBootstrapBean - Configuring property auth=spring from properties
01:00:42.766 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=KeyAuthenticationPlugin,interface=KeyAuthenticationPlugin]
01:00:42.766 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=JaasAuthenticationPlugin,interface=AuthenticationPlugin]
01:00:42.766 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=SimpleAuthenticationPlugin,interface=AuthenticationPlugin]
01:00:42.766 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=AuthenticationManagerAdapter,interface=AuthenticationPlugin]
01:00:42.767 [main] INFO org.crsh.ssh.SSHPlugin - Booting SSHD
01:00:42.793 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=GroovyLanguageProxy,interface=Language]
01:00:42.861 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=JavaLanguage,interface=Language]
01:00:42.861 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=ScriptLanguage,interface=Language]
01:00:42.871 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=CRaSHShellFactory,interface=ShellFactory]
01:00:44.185 [main] INFO o.a.sshd.common.util.SecurityUtils - Trying to register BouncyCastle as a JCE provider
01:00:45.739 [main] INFO o.a.sshd.common.util.SecurityUtils - Registration succeeded
01:00:45.978 [main] INFO org.crsh.ssh.term.SSHLifeCycle - About to start CRaSSHD
01:00:46.070 [main] INFO org.crsh.ssh.term.SSHLifeCycle - CRaSSHD started on port 2000
01:00:46.070 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=SSHPlugin,interface=SSHPlugin]
01:00:46.070 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=SSHInlinePlugin,interface=CommandPlugin]
01:00:46.071 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=CronPlugin,interface=CronPlugin]
01:00:46.071 [main] INFO org.crsh.plugin.PluginManager - Initialized plugin Plugin[type=MailPlugin,interface=MailPlugin]
從CRaSSHD started on port 2000這句看出,ssh預設監聽的端口号為2000。SpringBoot的關于remote shell的屬性配置,在org.springframework.boot.actuate.autoconfigure.ShellProperties類裡面。
我們也可以在application.properties裡面自行配置端口号:
management.shell.ssh.port=2001
執行如下指令
$ ssh -p 2000 user@localhost
我們就可以在shell終端連接配接我們的應用了。
如果我們什麼都不配置,使用預設的配置,遠端shell預設監聽端口為2000,預設使用者名為user,密碼為随機生成的,并且在輸出日志中會顯示。
如果我們應用內建了Spring Security,remote shell使用預設系統的配置。
management.shell.auth.spring.roles=ADMIN,USER
#spring, Integrated with spring security
management.shell.auth.type=spring
這樣配置将使用Spring Security的AuthenticationManager處理登入職責,相關代碼如下:
private static class AuthenticationManagerAdapter extends CRaSHPlugin<AuthenticationPlugin> implements AuthenticationPlugin<String> {
private static final PropertyDescriptor<String> ROLES = PropertyDescriptor.create("auth.spring.roles", "ADMIN", "Comma separated list of roles required to access the shell");
@Autowired
private AuthenticationManager authenticationManager;
@Autowired(
required = false
)
@Qualifier("shellAccessDecisionManager")
private AccessDecisionManager accessDecisionManager;
private String[] roles;
private AuthenticationManagerAdapter() {
this.roles = new String[]{"ADMIN"};
}
public boolean authenticate(String username, String password) throws Exception {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
Authentication token;
try {
token = this.authenticationManager.authenticate(token);
} catch (AuthenticationException var6) {
return false;
}
if(this.accessDecisionManager != null && token.isAuthenticated() && this.roles != null) {
try {
this.accessDecisionManager.decide(token, this, SecurityConfig.createList(this.roles));
} catch (AccessDeniedException var5) {
return false;
}
}
return token.isAuthenticated();
}
具體參考CrshAutoConfiguration和ShellProperties的Javadoc。
如果不是,将使用一個簡單的認證政策,你可能會看到類似這樣的資訊:
Using default password for shell access: ec03e16c-4cf4-49ee-b745-7c8255c1dd7e
如果想在簡單的認證政策裡指定使用者名密碼,按照如下配置即可
management.shell.auth.type=simple
management.shell.auth.simple.user.name=user
management.shell.auth.simple.user.password=123456
這個安全機制就是用的Spring security裡面的功能。
Linux和OSX使用者可以直接使用ssh連接配接遠端shell,Windows使用者可以下載下傳并安裝PuTTY,SecureCRT等用戶端來實作ssh連接配接。
輸入help可以擷取指令清單,Spring Boot提供metrics,beans,autoconfig和endpoint指令。如下所示
jack@jacks-MacBook-Air:~$ ssh -p 2000 user@localhost
Password authentication
Password:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.4.6.RELEASE)
> help
Try one of these commands with the -h or --help switch:
NAME DESCRIPTION
autoconfig Display auto configuration report from ApplicationContext
beans Display beans in ApplicationContext
cron manages the cron plugin
dashboard a monitoring dashboard
egrep search file(s) for lines that match a pattern
endpoint Invoke actuator endpoints
env display the term env
filter a filter for a stream of map
java various java language commands
jmx Java Management Extensions
jul java.util.logging commands
jvm JVM informations
less opposite of more
mail interact with emails
man format and display the on-line manual pages
metrics Display metrics provided by Spring Boot
shell shell related command
sleep sleep for some time
sort sort a map
system vm system properties commands
thread JVM thread commands
help provides basic help
repl list the repl or change the current repl
比如,我們想看一下系統有那些Endpoint,直接指令行操作如下
> endpoint
usage: endpoint [-h | --help] COMMAND [ARGS]
The most commonly used endpoint commands are:
invoke Invoke provided actuator endpoint
list List all available and enabled actuator endpoints
> endpoint list
serverEndpoint
requestMappingEndpoint
environmentEndpoint
healthEndpoint
beansEndpoint
infoEndpoint
metricsEndpoint
traceEndpoint
dumpEndpoint
autoConfigurationReportEndpoint
configurationPropertiesReportEndpoint
連上ssh終端,我們看應用端的背景列印的日志
02:06:04.657 [main] INFO o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8888 (http)
02:06:04.673 [main] INFO scala.App - Started App in 52.422 seconds (JVM running for 56.978)
02:06:13.105 [pool-6-thread-1] INFO o.a.s.server.session.ServerSession - Server session created from /127.0.0.1:50538
02:06:13.260 [pool-6-thread-1] INFO o.a.s.server.session.ServerSession - Kex: server->client aes128-ctr hmac-sha2-256 none
02:06:13.260 [pool-6-thread-1] INFO o.a.s.server.session.ServerSession - Kex: client->server aes128-ctr hmac-sha2-256 none
02:06:16.370 [pool-6-thread-1] INFO o.a.s.s.s.ServerUserAuthService - Session user@/127.0.0.1:50538 authenticated
可以看出,ServerUserAuthService負責認證目前登入使用者,ServerSession負責維護目前登入session。
你可以使用Groovy或Java編寫其他的shell指令(具體參考CRaSH文檔),Spring Boot預設會搜尋以下路徑的指令:
classpath*:/commands/**
classpath*:/crash/commands/**
如果我們想改變搜尋路徑,設定shell.command-path-patterns屬性即可。
下面是一個從src/main/resources/commands/hello.java加載的'hello'指令:
/**
* Created by jack on 2017/5/4.
*/
package commands;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.crsh.cli.Command;
import org.crsh.cli.Usage;
import org.crsh.command.BaseCommand;
import org.crsh.command.InvocationContext;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@Usage("Test Command : hello say")
public class hello extends BaseCommand{
@Usage("hello say")
@Command
public String say(InvocationContext context){
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) context.getAttributes().get("spring.beanfactory");
for (String name : defaultListableBeanFactory.getBeanDefinitionNames()) {
System.out.println(name);
context.getWriter().write(name);
context.getWriter().write("\n");
}
return "Hello, Now Is " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}
在遠端ssh中執行指令,結果如下
jack@jacks-MacBook-Air:~$ ssh -p 2000 user@localhost
Password authentication
Password:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.4.6.RELEASE)
> help
Try one of these commands with the -h or --help switch:
NAME DESCRIPTION
autoconfig Display auto configuration report from ApplicationContext
beans Display beans in ApplicationContext
cron manages the cron plugin
dashboard a monitoring dashboard
egrep search file(s) for lines that match a pattern
endpoint Invoke actuator endpoints
env display the term env
filter a filter for a stream of map
hello Test Command : hello say
java various java language commands
jmx Java Management Extensions
jul java.util.logging commands
jvm JVM informations
less opposite of more
mail interact with emails
man format and display the on-line manual pages
metrics Display metrics provided by Spring Boot
shell shell related command
sleep sleep for some time
sort sort a map
system vm system properties commands
thread JVM thread commands
help provides basic help
repl list the repl or change the current repl
> hello
usage: hello [-h | --help] COMMAND [ARGS]
The most commonly used hello commands are:
say hello say
> hello say
......
lightSwordHealthIndicator
serverEndpoint
globalExceptionHandlerAdvice
angularController
helloController
httpApiController
httpReportController
httpSuiteController
rootController
webMvcConfig
webSecurityConfig
dataInit
lightSwordUserDetailService
org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
authenticationManagerBuilder
enableGlobalAuthenticationAutowiredConfigurer
initializeUserDetailsBeanManagerConfigurer
initializeAuthenticationProviderBeanManagerConfigurer
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration
delegatingApplicationListener
webSecurityExpressionHandler
springSecurityFilterChain
........
org.springframework.boot.autoconfigure.web.MultipartProperties
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration$RestTemplateConfiguration
restTemplateBuilder
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration
org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration$DatabaseShutdownExecutorJpaDependencyConfiguration
org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration
inMemoryDatabaseShutdownExecutor
loginFilter
org.springframework.orm.jpa.SharedEntityManagerCreator#0
Hello, Now Is 2017-05-04 02:29:38
除了建立新指令,你也可以擴充CRaSH shell的其他特性,所有繼承org.crsh.plugin.CRaSHPlugin的Spring Beans将自動注冊到shell,具體可以檢視CRaSH參考文檔[4]。