第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
检查 Cassandra database is up.
检查 low disk space.
检查数据库连接是否正常
检查Elasticsearch cluster is up.
检查JMS broker is up.
检查mail server is up.
检查Mongo database is up.
检查Rabbit server is up.
检查Redis server is up.
检查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]。
小结
本章示例工程源代码:
https://github.com/EasySpringBoot/lightsword/tree/spring_boot_actuator_and_remote_shell_2017.5.4
参考资料:
1.http://javabeat.net/spring-boot-actuator/
2.http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-remote-shell.html
3.https://my.oschina.net/zj0303/blog/708366
4.http://www.crashub.org/