Spring Boot Actuatorによる便利なAPIエンドポイント自動設定

【求人のご案内】Javaアプリ開発エンジニア募集

よく訓練されたアップル信者、都元です。先日Spring Bootご紹介しましたが、今回はSpring BootのサブモジュールであるActuatorという機能を紹介します。

Production-ready features

本番環境で動かすWebアプリケーションは、アプリケーションが本来果たすべき機能だけでなく、意外と雑多な機能が求められます。

例えば代表的なものは「ヘルスチェック」です。/healthに対して、ヘルシー(DB接続に問題なく、ディスクスペースも充分)であれば200、そうでなければ理由と共に500を返す、等です。

$ curl -s http://localhost:8080/health | jq .
{
  "status": "UP",
  "diskSpace": {
    "status": "UP",
    "free": 268629237760,
    "threshold": 10485760
  },
  "db": {
    "status": "UP",
    "database": "MySQL",
    "hello": 1
  }
}

また、現在のJavaスレッドダンプを返すAPIなんかもデバッグには非常に役に立つと思います。

$ curl http://localhost:8080/dump
[
  {
    "threadName": "http-nio-8080-exec-10",
    "threadId": 30,
    ...
    "inNative": false,
    "suspended": false,
    "threadState": "WAITING",
    "stackTrace": [
      {
        "methodName": "park",
        "fileName": "Unsafe.java",
        "lineNumber": -2,
        "className": "sun.misc.Unsafe",
        "nativeMethod": true
      },
      ...

直近のHTTPリクエストの概略一覧とか。

$ curl http://localhost:8080/trace | jq .
[
  {
    "timestamp": 1440586482297,
    "info": {
      "method": "GET",
      "path": "/",
      "headers": {
        "request": {
          "host": "localhost:8080",
          "user-agent": "curl/7.43.0",
          "accept": "*/*"
        },
        "response": {
          "X-Application-Context": "application:development,local",
          "Accept-Charset": "略",
          "Content-Type": "text/plain;charset=UTF-8",
          "Content-Length": "45",
          "Date": "Wed, 26 Aug 2015 10:54:42 GMT",
          "status": "200"
        }
      }
    }
  },
  ...
]

Springが管理しているBeanの詳細情報とか。

$ curl -s http://localhost:8080/beans | jq .
[
  {
    "context": "application:development,local",
    "parent": null,
    "beans": [
      {
        "bean": "berserkerApplication",
        "scope": "singleton",
        "type": "jp.classmethod.example.berserker.BerserkerApplication$$EnhancerBySpringCGLIB$$145ba40",
        "resource": "null",
        "dependencies": [
          "userRepository"
        ]
      },
      ...

アプリケーションのシャットダウン(POST)のための操作エンドポイント。

$ curl -s -X POST http://localhost:8080/shutdown | jq .
{
  "message": "Shutting down, bye..."
}

その他諸々…。こんなエンドポイントあったら便利ですよねー!

How to?

というエンドポイントを、あなたのSpring Bootアプリケーションに導入することができます。どうやって?

dependencies {
    compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}

依存ライブラリを1つ追加するだけです。クラスパスにこのライブラリが存在するだけで、上記でご紹介したエンドポイントを含む、その他便利なエンドポイントたちが自動的に有効になります。証拠物件はこちら。ああ、いつもの黒魔術ですよ。

もちろん、productionにおいてはセンシティブ(誰でも見れてはいけない)情報も含まれるため、認証機構が備わっていないアプリケーションでは自動的に有効にならないものもあります。認証機構があるアプリケーションでは、認証を経たリクエストのみでレスポンスが返るようになっています。

が、逆に言えばその判断さえも自動で行ってくれるという至れり尽くせり状態。恐れいります。

上記にご紹介した以外に、どんなエンドポイントがあるのか。一つ一つは説明しきれませんので、それはドキュメントを参照してください。

動かしてみる

$ git clone https://github.com/classmethod-aws/berserker.git
$ cd berserker
$ git checkout 11.0
$ ./gradlew bootRun
Starting a new Gradle Daemon for this build (subsequent builds will be faster).
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:findMainClass
:bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.3.RELEASE)

(略)

2016/04/08 11:46:14.194 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.196 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/info || /info.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.197 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2016/04/08 11:46:14.198 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/env || /env.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.200 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.202 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/flyway || /flyway.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.203 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.205 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2016/04/08 11:46:14.205 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.206 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/health || /health.json],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(java.security.Principal)
2016/04/08 11:46:14.207 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.209 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.210 [main] INFO  o.s.b.a.e.mvc.EndpointHandlerMapping:534 - Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016/04/08 11:46:14.363 [main] INFO  o.s.j.e.a.AnnotationMBeanExporter:431 - Registering beans for JMX exposure on startup
2016/04/08 11:46:14.370 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:431 - Registering beans for JMX exposure on startup
2016/04/08 11:46:14.376 [main] INFO  o.s.c.s.DefaultLifecycleProcessor:341 - Starting beans in phase 0
2016/04/08 11:46:14.379 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'requestMappingEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=requestMappingEndpoint]
2016/04/08 11:46:14.412 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'flywayEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=flywayEndpoint]
2016/04/08 11:46:14.422 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'environmentEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=environmentEndpoint]
2016/04/08 11:46:14.433 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'healthEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=healthEndpoint]
2016/04/08 11:46:14.437 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'beansEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=beansEndpoint]
2016/04/08 11:46:14.442 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'infoEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=infoEndpoint]
2016/04/08 11:46:14.447 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'metricsEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=metricsEndpoint]
2016/04/08 11:46:14.454 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'traceEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=traceEndpoint]
2016/04/08 11:46:14.471 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'dumpEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=dumpEndpoint]
2016/04/08 11:46:14.477 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'autoConfigurationReportEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=autoConfigurationReportEndpoint]
2016/04/08 11:46:14.484 [main] INFO  o.s.b.a.e.jmx.EndpointMBeanExporter:674 - Located managed bean 'configurationPropertiesReportEndpoint': registering with JMX server as MBean [org.springframework.boot:type=Endpoint,name=configurationPropertiesReportEndpoint]
2016/04/08 11:46:14.539 [main] INFO  o.a.coyote.http11.Http11NioProtocol:180 - Initializing ProtocolHandler ["http-nio-8080"]
2016/04/08 11:46:14.550 [main] INFO  o.a.coyote.http11.Http11NioProtocol:180 - Starting ProtocolHandler ["http-nio-8080"]
2016/04/08 11:46:14.567 [main] INFO  o.a.tomcat.util.net.NioSelectorPool:180 - Using a shared selector for servlet write/read
2016/04/08 11:46:14.593 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer:162 - Tomcat started on port(s): 8080 (http)
2016/04/08 11:46:14.605 [main] INFO  j.c.e.berserker.BerserkerApplication:57 - Started BerserkerApplication in 5.708 seconds (JVM running for 6.158)

色々エンドポイントが登録されているのがわかりますね。

$ curl -s http://localhost:8080/health | jq .
{
  "status": "UP",
  "diskSpace": {
    "status": "UP",
    "total": 499054952448,
    "free": 113897480192,
    "threshold": 10485760
  },
  "db": {
    "status": "UP",
    "database": "MySQL",
    "hello": 1
  }
}

まとめ

まぁ、障害調査でもない限り、使うことは少ないかもしれませんが、いざというときに「導入しておいてよかった」と思えます。

また、AWSでELBの配下に配置するWebアプリケーションとしてはヘルスチェックエンドポイントがあると便利ですので、筆者はひとまずこの機能は何も考えずに有効にして使っています。