かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その76 )( Spring Boot Actuator を導入する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その75 )( コネクションプーリング用ライブラリを Tomcat connection pool → HikariCP に切り替える ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Spring Boot Actuator を導入します。

参照したサイト・書籍

目次

  1. Actuator を導入せずに IntelliJ IDEA から Tomcat を起動するとどのような状態か?
  2. build.gradle を変更する
  3. IntelliJ IDEA から Tomcat を起動して Run Tool Window を確認する
  4. ログレベルを変更してみる
  5. /actuator/** を Spring Security の CSRF チェックの対象外にする
  6. ログレベルを変更してみる(続き)
  7. Info Endpoint から情報を取得してみる
  8. Shutdown Endpoint からシャットダウンしてみる
  9. Thread Dump Endpoint からスレッドダンプを出力してみる
  10. /actuator/** に IPアドレスでアクセス制限をかけるには?
  11. 最後に

手順

Actuator を導入せずに IntelliJ IDEA から Tomcat を起動するとどのような状態か?

IntelliJ IDEA から Tomcat を起動後、画面の下の Run Tool Window 内に表示される「EndPoints」タブの中の「Health」「Mappings」タブを見ると Spring Boot Actuator is not enabled. Add dependency to the spring-boot-actuator to enable it. のメッセージが表示されています。

f:id:ksby:20180805233846p:plain f:id:ksby:20180805233947p:plain

build.gradle を変更する

build.gradle の以下の点を変更します。

dependencies {
    ..........
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    ..........
  • implementation("org.springframework.boot:spring-boot-starter-actuator") を追加します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

IntelliJ IDEA から Tomcat を起動して Run Tool Window を確認する

今度は「Health」「Mappings」タブ内にデータが表示されるようになりました。

f:id:ksby:20180806000034p:plain f:id:ksby:20180806000243p:plain

  • デフォルトで health check してくれる対象は 50.9.1 Auto-configured HealthIndicator に一覧があります。Elasticsearch、Rabbit、Redis 等もチェックしてくれるようです。
  • 現在メールサーバ(smtp4dev)を起動していないので、mail に「Status: DOWN」のアイコンが表示されていました。
  • 「Mappings」に表示されている Path の内リンクになっているもの(おそらく GET でアクセス可能なもの?)をクリックすると、ブラウザが起動してドメイン名やポート番号を追加された URL でアクセスしてくれます。大量に表示されていても IntelliJ IDEA の Search 機能が効くのでパスをタイプすると該当するものにジャンプしてくれます(下の例だと input 03 と入力しています)。これは以外に便利な機能ではないでしょうか。 f:id:ksby:20180806003908p:plain
  • webjars は導入していないのですが、/webjars/** が表示されていました。Spring Boot Reference Guide を見たところ In addition to the “standard” static resource locations mentioned earlier, a special case is made for Webjars content. Any resources with a path in /webjars/** are served from jar files if they are packaged in the Webjars format. という記述があり、src/main/resources の下に static ディレクトリを作成すると自動で /webjars/** の mapping?を作成するようです。初めて知りました。。。

smtp4dev を起動すると mail のアイコンが「Status: UP」のものに変わりました。IntelliJ IDEA で起動している場合にはデフォルトだと 15秒間隔でチェックされます(「Endpoints」タブ内の左側にある歯車アイコンをクリックすると設定画面が表示されます。)。

f:id:ksby:20180806002601p:plain

ログレベルを変更してみる

application.properties に設定を追加して Loggers endpoint を有効にします。

doma.dialect=org.seasar.doma.jdbc.dialect.H2Dialect

management.endpoints.web.exposure.include=health,info,loggers

spring.datasource.hikari.jdbc-url=jdbc:h2:mem:bootnpmgebdb
..........
  • management.endpoints.web.exposure.include=health,info,loggers を追加します。management.endpoints.web.exposure.include の設定を追加する場合、デフォルトで有効になっている health, info も記述しないと無効になります。

IntelliJ IDEA から Tomcat を再起動した後、ブラウザから http://localhost:8080/inquiry/input/01 にアクセスすると以下のログが出力されます。application-develop.properties に logging.level.org.springframework.web=DEBUG の設定を入れていますので、DEBUG ログが出力されています。

f:id:ksby:20180806005716p:plain

Spring Boot Actuator Web API Documentation を参考に、コマンドプロンプトから curl 'http://localhost:8080/actuator/loggers/org.springframework.web' -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel":"info"}' コマンドを実行します。。。が 403 でアクセス拒否されました。Spring Security が入っているので POST メソッドだと CSRF のチェック対象から外さないとアクセスできませんでした。

f:id:ksby:20180806010334p:plain

/actuator/** を Spring Security の CSRF チェックの対象外にする

src/main/java/ksbysample/webapp/bootnpmgeb/config/WebSecurityConfig.java の以下の点を変更します。http.csrf().ignoringAntMatchers(...) というメソッドがあることに気づいたのでいろいろ書き直します。

package ksbysample.webapp.bootnpmgeb.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * ???
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String ACTUATOR_ANT_PATTERN = "/actuator/**";
    private static final String H2_CONSOLE_ANT_PATTERN = "/h2-console/**";

    @Value("${spring.h2.console.enabled:false}")
    private boolean springH2ConsoleEnabled;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 認証の対象外にしたいURLがある場合には、以下のような記述を追加します
                // 複数URLがある場合はantMatchersメソッドにカンマ区切りで対象URLを複数列挙します
                // .antMatchers("/country/**").permitAll()
                //
                // この Web アプリケーションでは Spring Security を CSRF対策で使用したいだけなので、
                // 全ての URL を認証の対象外にする
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated();

        // Spring Boot Actuator のパスは CSRF チェックの対象外にする
        http.csrf().ignoringAntMatchers(ACTUATOR_ANT_PATTERN);

        // spring.h2.console.enabled=true に設定されている場合には H2 Console を表示するために必要な設定を行う
        if (springH2ConsoleEnabled) {
            http.csrf().ignoringAntMatchers(H2_CONSOLE_ANT_PATTERN);
            http.headers().frameOptions().sameOrigin();
        }
    }

}
  • private static final Pattern DISABLE_CSRF_TOKEN_PATTERN = Pattern.compile("(?i)^(GET|HEAD|TRACE|OPTIONS)$"); を削除します。
  • private static final String ACTUATOR_ANT_PATTERN = "/actuator/**"; を追加します。
  • private static final Pattern H2_CONSOLE_URI_PATTERN = Pattern.compile("^/h2-console");private static final String H2_CONSOLE_ANT_PATTERN = "/h2-console/**"; に変更します。
  • http.csrf().ignoringAntMatchers(ACTUATOR_ANT_PATTERN); を追加します。
  • http.csrf() から .requireCsrfProtectionMatcher(...) を削除し、.ignoringAntMatchers(H2_CONSOLE_ANT_PATTERN) を追加します。

ログレベルを変更してみる(続き)

再度 curl コマンドを実行すると、今度は 204(No Content)が返ってきました。成功したようです。

f:id:ksby:20180806012314p:plain

ブラウザから http://localhost:8080/inquiry/input/01 にアクセスしてログを出力してみると DEBUG ログが出力されていません。これまで設定を変更して再起動しないと変更できないものと思っていたので、これはちょっと感激です。

f:id:ksby:20180806012555p:plain

curl 'http://localhost:8080/actuator/loggers/org.springframework.web' -i -X GET コマンドを実行すれば、現在設定されているログレベルが取得できます。

f:id:ksby:20180806013233p:plain

Info Endpoint から情報を取得してみる

Spring Boot Actuator Web API Documentation - 11. Info (info) を参考にコマンドプロンプトから curl 'http://localhost:8080/actuator/info' -i -X GET コマンドを実行してみます。。。が、何も返ってきませんでした。"git" と "build" の情報が返ってくるものと思ったのですが。

f:id:ksby:20180807010529p:plain

調べてみると Spring Boot Reference Guide に以下の記述を見つけました。build.gradle に設定を追加して情報を生成しないといけないようです。

build.gradle の以下の点を変更します。

plugins {
    id "java"
    id "eclipse"
    id "idea"
    id "org.springframework.boot" version "2.0.4.RELEASE"
    id "io.spring.dependency-management" version "1.0.6.RELEASE"
    id "groovy"
    id "net.ltgt.errorprone" version "0.0.14"
    id "checkstyle"
    id "com.github.spotbugs" version "1.6.2"
    id "pmd"
    id "com.moowork.node" version "1.2.0"
    id "com.gorylenko.gradle-git-properties" version "1.5.1"
}

..........

// for Doma 2
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

springBoot {
    buildInfo()
}
  • plugins block に id "com.gorylenko.gradle-git-properties" version "1.5.1" を追加します。
  • springBoot { buildInfo() } を追加します。

変更後、Tomcat を停止してから Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

clean タスク → Rebuild Project → build タスクを実行すると bootBuildInfogenerateGitProperties というタスクが追加されていました。

f:id:ksby:20180807012748p:plain

Tomcat を起動して、再び curl 'http://localhost:8080/actuator/info' -i -X GET コマンドを実行してみますがやっぱり何も出ません。どうも Executable Jar から起動しないと何も出ないようです。

f:id:ksby:20180807013038p:plain

Tomcat を停止後 Executable Jar から起動して再度コマンドを実行すると、今度は情報が返ってきました。

f:id:ksby:20180807013649p:plain

JSON がフォーマットされていなくて見にくいので、jq というツールを入れてフォーマットしてみます。Download jq から「Windows」の jq 1.5 executables for 64-bit or 32-bit.64-bit のリンクをクリックして jq-win64.exe をダウンロードした後、ファイル名を jq.exe に変更して C:\Git\mingw64\bin の下へ入れます。

HTTPヘッダは不要なので -i オプションを外し、他にも余計な情報を出さないように -s オプションを付けて curl 'http://localhost:8080/actuator/info' -X GET -s | jq コマンドを実行するときれいに整形されました。

f:id:ksby:20180807021022p:plain

Shutdown Endpoint からシャットダウンしてみる

application.properties に設定を追加して Shutdown endpoint を有効にします。Shutdown endpoint は management.endpoints.web.exposure.include に endpoint 名を追加するだけでは有効にならず、management.endpoint.shutdown.enabled=true も追加する必要がありますAppendix A. Common application properties に記述がありました)。

management.endpoints.web.exposure.include=health,info,loggers,shutdown
management.endpoint.shutdown.enabled=true

IntelliJ IDEA から Tomcat を起動後、curl 'http://localhost:8080/actuator/shutdown' -i -X POST コマンドを実行すると {"message":"Shutting down, bye..."} のレスポンスが返ってきました。

f:id:ksby:20180808015723p:plain

しかし、Tomcat の方は落ちませんでした。これも Executable Jar から起動しないとダメなのかな。。。

f:id:ksby:20180808015629p:plain

boot-npm-geb-sample-1.0.2-RELEASE.jar を作成し直してコピーしてから、コマンドプロンプトから起動してみます。

f:id:ksby:20180808020802p:plain f:id:ksby:20180808020942p:plain

curl 'http://localhost:8080/actuator/shutdown' -i -X POST コマンドを実行すると、

f:id:ksby:20180808021236p:plain

今度は Tomcat が停止しました。ログは IntelliJ IDEA から Tomcat を起動した時と同じく HikariPool-1 - Shutdown completed. で終わっていました。

f:id:ksby:20180808021403p:plain f:id:ksby:20180808021622p:plain

ちなみにサービスに登録して起動してから、

f:id:ksby:20180808022052p:plain

curl コマンドを実行して停止してもサービスの画面上は起動したままでした。ログは HikariPool-1 - Shutdown completed. までは出力されています。

f:id:ksby:20180808022303p:plain f:id:ksby:20180808022556p:plain

Executable Jar を直接起動している時はきちんと落ちるようですが、IntelliJ IDEA や Windows のサービスから起動している時は落ちたことが起動元のプロセスに伝わらない、ということでしょうか?

Thread Dump Endpoint からスレッドダンプを出力してみる

application.properties に設定を追加して Thread Dump endpoint を有効にします。

management.endpoints.web.exposure.include=health,info,loggers,shutdown,threaddump
management.endpoint.shutdown.enabled=true

IntelliJ IDEA から Tomcat を起動後、curl 'http://localhost:8080/actuator/threaddump' -i -X POST コマンドを実行します。

f:id:ksby:20180808221827p:plain

なんかこれじゃない感が。。。 スレッドダンプと言えばテキスト形式のものだと思っていたので、JSON ではなくテキストのものを返して欲しいのですが、テキストで返す方法は見当たりませんでした。

もしかして最近はスレッドダンプは JSON 形式が常識なのか?と思って調べてみましたが、それらしい記事も見当たらず。その代わり以下の記事を見つけました。IBM Thread and Monitor Dump Analyzer for Java というツールが便利そうです。

試しに Java VisualVM からスレッドダンプを出力してテキストファイルに保存した後、

f:id:ksby:20180808224409p:plain f:id:ksby:20180808224506p:plain

java -Xmx512m -jar jca457.jar コマンドで IBM Thread and Monitor Dump Analyzer for Java を起動して保存したスレッドダンプを読み込ませると以下のような感じで表示されます。

f:id:ksby:20180808224740p:plain f:id:ksby:20180808224910p:plain f:id:ksby:20180808225045p:plain

2つのタイミングの異なるスレッドダンプを読み込ませて比較表示することも出来るようです。

f:id:ksby:20180808225523p:plain f:id:ksby:20180808225731p:plain

このツール、全然噂を聞いたことがありませんでした。そんなに有名なツールではないのかな?

/actuator/**IPアドレスでアクセス制限をかけるには?

src/main/java/ksbysample/webapp/bootnpmgeb/config/WebSecurityConfig.java に以下のように定義します。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()

                // アクセス元のホストIPアドレスで指定する場合
                .antMatchers(ACTUATOR_ANT_PATTERN).hasIpAddress("127.0.0.1")
                .antMatchers(ACTUATOR_ANT_PATTERN).hasIpAddress("192.168.56.1")

                // アクセス元のネットワークIPアドレスで指定する場合
                .antMatchers(ACTUATOR_ANT_PATTERN).hasIpAddress("192.168.56.0/24")

                // アクセス元を複数指定する場合
                .antMatchers(ACTUATOR_ANT_PATTERN)
                    .access("hasIpAddress('127.0.0.1') or hasIpAddress('192.168.56.0/24')")

                ..........

最後に

Health , Loggers, Mappings 等の Endpoint も面白いのですが、Actuator で一番面白そうなのは Prometheus Endpoint を利用して Prometheus と連携してモニタリングすることですよね。他の人が書いたものを見る限りでは簡単に出来るようです。

Spring Boot ってどんどん便利になっていますが、覚えることが多くて大変です。。。 いや、本当に。

Spring Boot Actuator の導入まで終わりましたが、あと以下の点を記述して終わりにする予定です。

  • Spring Session が生成している SESSION Cookie の domain 属性や httponly 属性の値を変更する方法の調査
  • gradle-processes の Gradle 4.6 以降対応版が出ているようなので正式に導入する
  • PMD を 6.6.0 にバージョンアップすると build タスク実行時のメッセージが減るような記述を見かけたので、その検証

履歴

2018/08/09
初版発行。