第11章 Spring Boot应用监控

第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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容