かんがるーさんの日記

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

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その29 )( build.gradle の dependencies から不要な記述を削除する )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その28 )( Docker で SMTPサーバ+Webmailクライアント環境を構築する ) の続きです。

参照したサイト・書籍

目次

  1. build.gradle の dependencies block から thymeleaf-extras-java8time を削除する
  2. build.gradle の dependencies block から jackson-datatype-jsr310 を削除する
  3. build.gradle の dependencies block から記述不要のものを削除する

手順

build.gradle の dependencies block から thymeleaf-extras-java8time を削除する

Spring Boot 2.0 Release NotesThe Thymeleaf starter now includes thymeleaf-extras-java8time which provides support for javax.time types. の記述があります。

今は build.gradle の dependencies block に implementation("org.springframework.boot:spring-boot-starter-thymeleaf")implementation("org.thymeleaf.extras:thymeleaf-extras-java8time") を記述していますが、gradlew dependencies コマンドを実行してみると確かに org.thymeleaf.extras:thymeleaf-extras-java8time が2重に出力されていました。

+--- org.springframework.boot:spring-boot-starter-thymeleaf -> 2.0.6.RELEASE
|    +--- org.springframework.boot:spring-boot-starter:2.0.6.RELEASE (*)
|    +--- org.thymeleaf:thymeleaf-spring5:3.0.10.RELEASE
|    |    +--- org.thymeleaf:thymeleaf:3.0.10.RELEASE
|    |    |    +--- ognl:ognl:3.1.12
|    |    |    |    \--- org.javassist:javassist:3.20.0-GA -> 3.22.0-GA
|    |    |    +--- org.attoparser:attoparser:2.0.5.RELEASE
|    |    |    +--- org.unbescape:unbescape:1.1.6.RELEASE
|    |    |    \--- org.slf4j:slf4j-api:1.7.25
|    |    \--- org.slf4j:slf4j-api:1.7.25
|    \--- org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.1.RELEASE
|         +--- org.thymeleaf:thymeleaf:3.0.0.RELEASE -> 3.0.10.RELEASE (*)
|         \--- org.slf4j:slf4j-api:1.6.6 -> 1.7.25
+--- org.thymeleaf.extras:thymeleaf-extras-springsecurity5 -> 3.0.3.RELEASE
|    \--- org.slf4j:slf4j-api:1.7.25
+--- org.thymeleaf.extras:thymeleaf-extras-java8time -> 3.0.1.RELEASE (*)

build.gradle の dependencies block から implementation("org.thymeleaf.extras:thymeleaf-extras-java8time") を削除します。

build.gradle の dependencies block から jackson-datatype-jsr310 を削除する

Spring Boot 2.0 Migration Guide - Jackson / JSON Support の内容に従い spring-boot-starter-json を使用するよう変更しようと思いましたが、gradlew dependencies コマンドを実行すると spring-boot-starter-web の依存関係に入っていました。

+--- org.springframework.boot:spring-boot-starter-web -> 2.0.6.RELEASE
|    ..........
|    +--- org.springframework.boot:spring-boot-starter-json:2.0.6.RELEASE
|    |    +--- org.springframework.boot:spring-boot-starter:2.0.6.RELEASE (*)
|    |    +--- org.springframework:spring-web:5.0.10.RELEASE
|    |    |    +--- org.springframework:spring-beans:5.0.10.RELEASE (*)
|    |    |    \--- org.springframework:spring-core:5.0.10.RELEASE (*)
|    |    +--- com.fasterxml.jackson.core:jackson-databind:2.9.7
|    |    |    +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0
|    |    |    \--- com.fasterxml.jackson.core:jackson-core:2.9.7
|    |    +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.7
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.9.7
|    |    |    \--- com.fasterxml.jackson.core:jackson-databind:2.9.7 (*)
|    |    +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7
|    |    |    +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.9.7
|    |    |    \--- com.fasterxml.jackson.core:jackson-databind:2.9.7 (*)
|    |    \--- com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.7
|    |         +--- com.fasterxml.jackson.core:jackson-core:2.9.7
|    |         \--- com.fasterxml.jackson.core:jackson-databind:2.9.7 (*)
|    ..........

spring-boot-starter-json の依存関係に com.fasterxml.jackson.datatype:jackson-datatype-jsr310 が入っているので、build.gradle の dependencies block から implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") を削除します。

build.gradle の dependencies block から記述不要のものを削除する

他にも記述しなくてもよいものがありそうだったので、見直して以下のように変更しました。

  • spring-boot-starter-thymeleaf に exclude group: "org.codehaus.groovy", module: "groovy" を記述していましたが、今は spring-boot-starter-thymeleaf の依存関係に groovy が入っていなかったので削除します。
  • spring-session-data-redis の依存関係に spring-session-core が入っていたので、implementation("org.springframework.session:spring-session-core") を削除します。
  • spring-boot-starter-test の依存関係に mockito-core が入っていたので、testImplementation("org.mockito:mockito-core") を削除します。

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

履歴

2019/01/06
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その28 )( Docker で SMTPサーバ+Webmailクライアント環境を構築する )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その27 )( ProviderManager#getProviders が DaoAuthenticationProvider を3つ返す原因を調査する ) の続きです。

参照したサイト・書籍

目次

  1. SMTP+IMAP4サーバを構築する
  2. Webmail クライアント Rainloop の環境を構築する
  3. 動作確認

手順

SMTP+IMAP4サーバを構築する

まずは SMTPサーバから。Docker イメージは tvial/docker-mailserver を使用します。Webmail クライアント Rainloop からの接続に IMAP4 が使用されるので、IMAP4 も使用可能にします。

プロジェクトの直下に docker/mail-server/config ディレクトリを作成します。

.env に tvial/docker-mailserver のバージョン番号を追加します。

MAILSERVER_VERSION=release-v6.1.0

docker-compose.yml を以下のように変更します。

  # docker-mailserver
  # https://hub.docker.com/r/tvial/docker-mailserver/
  # https://github.com/tomav/docker-mailserver
  #
  # 起動した docker-mailserver のコンテナ(mail-server) にアクセスする場合には以下のコマンドを実行する
  # docker exec -it mail-server /bin/sh
  #
  # アカウントのパスワードを SHA512 で作成する場合には、コンテナにアクセスして以下のようにコマンドを実行して生成する
  # doveadm pw -s SHA512-CRYPT -u [メールアドレス(例:tanaka@mail.example.com)] -p [パスワード]
  #
  mail-server:
    image: tvial/docker-mailserver:${MAILSERVER_VERSION}
    container_name: mail-server
    hostname: mail
    domainname: example.com
    ports:
      - "25:25"
      - "143:143"
    volumes:
      - ./docker/mail-server/config/:/tmp/docker-mailserver/
    environment:
      # debug したい場合には以下の行のコメントアウトを解除する
      # - DMS_DEBUG=1
      - ENABLE_SPAMASSASSIN=0
      - ENABLE_CLAMAV=0
      - ENABLE_FETCHMAIL=0
      - ENABLE_FAIL2BAN=0
      - ENABLE_POSTGREY=0
      - ENABLE_POP3=0
    cap_add:
      - NET_ADMIN
      - SYS_PTRACE
    restart: always

docker/mail-server/config/postfix-main.cf を新規作成し、以下の内容を記述します。

mydestination =
smtpd_recipient_restrictions =

docker/mail-server/config/dovecot.cf を新規作成し、以下の内容を記述します。

disable_plaintext_auth = no
ssl = no
# debug したい場合には以下の行のコメントアウトを解除する
# auth_verbose = yes
# auth_debug = yes

docker/mail-server/config/postfix-accounts.cf を新規作成し、以下の内容を記述します。

kimura.masao@test.co.jp|{PLAIN}xxxxxxxx
endo.yoko@sample.com|{PLAIN}xxxxxxxx
sato.masahiko@sample.com|{PLAIN}xxxxxxxx
takahasi.naoko@test.co.jp|{PLAIN}xxxxxxxx
kato.hiroshi@sample.com|{PLAIN}xxxxxxxx
ito.aoi@test.co.jp|{PLAIN}xxxxxxxx
tanaka.taro@sample.com|{PLAIN}xxxxxxxx
suzuki.hanako@test.co.jp|{PLAIN}xxxxxxxx

※docker-compose.yml ではメールサーバの hostname+domainname を mail.example.com と設定しましたが、アカウントには別のドメインを設定可能です。

docker-compose up -d コマンドで起動した後、docker-compose down で終了させます。

Webmail クライアント Rainloop の環境を構築する

プロジェクトの直下に docker/rainloop/data ディレクトリを作成します。

docker-compose.yml を以下のように変更します。

  # Webmail クライアント
  rainloop:
    image: hardware/rainloop
    container_name: rainloop
    ports:
      - "8888:8888"
    volumes:
      - ./docker/rainloop/data/:/rainloop/data

docker-compose up -d コマンドで起動します。

http://localhost:8888/?admin にアクセスし、admin / 12345 でログインして設定します。

f:id:ksby:20190105224104p:plain f:id:ksby:20190105224158p:plain f:id:ksby:20190105224237p:plain

http://localhost:8888/ にアクセスし、tanaka.taro@sample.com / xxxxxxxx でログインできることを確認します。

動作確認

Tomcat を起動して、貸出希望書籍 CSV ファイルアップロード画面で CSV ファイルをアップロードしてからメールが届くことを確認します。

f:id:ksby:20190105225135p:plain

履歴

2019/01/05
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その27 )( ProviderManager#getProviders が DaoAuthenticationProvider を3つ返す原因を調査する )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その26 )( Gradle を 4.10.2 → 4.10.3 へ、Spring Boot を 2.0.6 → 2.0.7 へバージョンアップする。。。が Spring Security の bug のため Spring Boot は 2.0.6 へ戻す ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回の記事において、org.springframework.security.authentication.ProviderManager#authenticate の中で getProviders() が DaoAuthenticationProvider を3つ返しているのを見つけたのですが、userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされているものが2つありました(残りの1つは ImMemoryUserDetailsManager)。

      LendingUserDetailsService がセットされている DaoAuthenticationProvider が2つあると認証処理時に DB への検索処理が2回実行されるので、1つだけになるように変更します。

参照したサイト・書籍

目次

  1. WebSecurityConfig クラスから daoAuhthenticationProvider, configAuthentication メソッドをコメントアウトしてみる
  2. WebSecurityConfig クラスの configAuthentication メソッドで .userDetailsService(userDetailsService) をコメントアウトしてみる
  3. WebSecurityConfig クラスから daoAuhthenticationProvider メソッドだけコメントアウトしてみる
  4. まとめてみると。。。

手順

WebSecurityConfig クラスから daoAuhthenticationProvider, configAuthentication メソッドをコメントアウトしてみる

src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java から daoAuhthenticationProvider, configAuthentication メソッドをコメントアウトするとどうなるか確認してみます。

@Configuration
public class WebSecurityConfig {

    ..........

//    /**
//     * @return ???
//     */
//    @Bean
//    public AuthenticationProvider daoAuhthenticationProvider() {
//        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
//        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
//        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
//        daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
//        return daoAuthenticationProvider;
//    }
//
//    /**
//     * @param auth ???
//     * @throws Exception
//     */
//    @SuppressWarnings("PMD.SignatureDeclareThrowsException")
//    @Autowired
//    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
//        // AuthenticationManagerBuilder#userDetailsService の後に auth.inMemoryAuthentication() を呼び出すと
//        // AuthenticationManagerBuilder の defaultUserDetailsService に
//        // org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされて
//        // Remember Me 認証で InMemoryUserDetailsManager が使用されて DB のユーザが参照されなくなるので、
//        // Remember Me 認証で使用する UserDetailsService を一番最後に呼び出す
//        // ※今回の場合には auth.userDetailsService(userDetailsService) が一番最後に呼び出されるようにする
//        auth.inMemoryAuthentication()
//                .withUser("actuator")
//                .password("{noop}xxxxxxxx")
//                .roles("ENDPOINT_ADMIN");
//        auth.authenticationProvider(daoAuhthenticationProvider())
//                .userDetailsService(userDetailsService);
//    }

}

「ログインを5回失敗すればアカウントはロックされる」のテストを debug 実行すると、org.springframework.security.authentication.ProviderManager#authenticate で getProviders() から userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider が1つ返ってきていました。特に何もしなくても DaoAuthenticationProvider がセットされるようです。

f:id:ksby:20190105141217p:plain f:id:ksby:20190105141335p:plain

テストも成功します。

f:id:ksby:20190105141955p:plain

ただし全てのテストを実行してみると「次回から自動的にログインするをチェックすれば次はログインしていなくてもログイン後の画面にアクセスできる」のテスト(Remember Me 認証のテスト)だけ失敗します。

f:id:ksby:20190105143321p:plain f:id:ksby:20190105143546p:plain

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername の以下の場所でエラーになっており、delegate する先の UserDetailsService が見つからないことが原因のようです。

f:id:ksby:20190105143921p:plain

WebSecurityConfig クラスの configAuthentication メソッドで .userDetailsService(userDetailsService)コメントアウトしてみる

今度は src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java の configAuthentication メソッドで .userDetailsService(userDetailsService)コメントアウトしてみます。また passwordEncoder には BCryptPasswordEncoder のインスタンスをセットせず、デフォルトの DelegatingPasswordEncoder のインスタンスを使用します。

@Configuration
public class WebSecurityConfig {

    ..........

    /**
     * @return ???
     */
    @Bean
    public AuthenticationProvider daoAuhthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
//        daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
        return daoAuthenticationProvider;
    }

    /**
     * @param auth ???
     * @throws Exception
     */
    @SuppressWarnings("PMD.SignatureDeclareThrowsException")
    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        // AuthenticationManagerBuilder#userDetailsService の後に auth.inMemoryAuthentication() を呼び出すと
        // AuthenticationManagerBuilder の defaultUserDetailsService に
        // org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされて
        // Remember Me 認証で InMemoryUserDetailsManager が使用されて DB のユーザが参照されなくなるので、
        // Remember Me 認証で使用する UserDetailsService を一番最後に呼び出す
        // ※今回の場合には auth.userDetailsService(userDetailsService) が一番最後に呼び出されるようにする
        auth.inMemoryAuthentication()
                .withUser("actuator")
                .password("{noop}xxxxxxxx")
                .roles("ENDPOINT_ADMIN");
        auth.authenticationProvider(daoAuhthenticationProvider());
//                .userDetailsService(userDetailsService);
    }

}

「ログインを5回失敗すればアカウントはロックされる」のテストを debug 実行すると、org.springframework.security.authentication.ProviderManager#authenticate で getProviders() から userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider と ImMemoryUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider の計2つが返ってきていました。

f:id:ksby:20190105150501p:plain

テストは成功します。

f:id:ksby:20190105150247p:plain

全てのテストを実行してみると先程と同様に「次回から自動的にログインするをチェックすれば次はログインしていなくてもログイン後の画面にアクセスできる」のテスト(Remember Me 認証のテスト)だけ失敗します。ただし失敗の原因が異なり、今度は HTTPステータスコードが 200 ではなく 302 が返ってくるというものでした。

f:id:ksby:20190105151213p:plain f:id:ksby:20190105151333p:plain

これは Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その13 )( Remember Me 認証が使えなくなっていたので調査・修正する ) で調査済ですが、Remember Me 認証用の UserDetailsService として org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされた後に別の UserDetailsService をセットしていないので、InMemoryUserDetailsManager が認証処理に使用されて認証エラーになり、ログイン画面へリダイレクトさせられているのでしょう。

WebSecurityConfig クラスから daoAuhthenticationProvider メソッドだけコメントアウトしてみる

今度は src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java から daoAuhthenticationProvider メソッドだけコメントアウトしてみます。

//    /**
//     * @return ???
//     */
//    @Bean
//    public AuthenticationProvider daoAuhthenticationProvider() {
//        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
//        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
//        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
//        daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
//        return daoAuthenticationProvider;
//    }

    /**
     * @param auth ???
     * @throws Exception
     */
    @SuppressWarnings("PMD.SignatureDeclareThrowsException")
    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        // AuthenticationManagerBuilder#userDetailsService の後に auth.inMemoryAuthentication() を呼び出すと
        // AuthenticationManagerBuilder の defaultUserDetailsService に
        // org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされて
        // Remember Me 認証で InMemoryUserDetailsManager が使用されて DB のユーザが参照されなくなるので、
        // Remember Me 認証で使用する UserDetailsService を一番最後に呼び出す
        // ※今回の場合には auth.userDetailsService(userDetailsService) が一番最後に呼び出されるようにする
        auth.inMemoryAuthentication()
                .withUser("actuator")
                .password("{noop}xxxxxxxx")
                .roles("ENDPOINT_ADMIN");
//        auth.authenticationProvider(daoAuhthenticationProvider())
//                .userDetailsService(userDetailsService);
        auth.userDetailsService(userDetailsService);
    }

「ログインを5回失敗すればアカウントはロックされる」のテストを debug 実行すると、org.springframework.security.authentication.ProviderManager#authenticate で getProviders() から userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider と ImMemoryUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider の計2つが返ってきていました。

f:id:ksby:20190105152741p:plain

テストは成功します。

f:id:ksby:20190105152957p:plain

全てのテストを実行してみると、全て成功しました。

f:id:ksby:20190105153616p:plain

まとめてみると。。。

  • Remember Me 認証を使用しないのであれば、WebSecurityConfig クラスに daoAuhthenticationProvider, configAuthentication メソッドを定義する必要はない。
  • Remember Me 認証を使用するのであれば、WebSecurityConfig クラスに configAuthentication メソッドを定義して、その中で AuthenticationManagerBuilder#userDetailsService を呼び出して認証処理に使用する UserDetailsService クラスのインスタンスをセットする。
  • WebSecurityConfig クラスに configAuthentication メソッドを定義する場合、同じ UserDetailsService で AuthenticationManagerBuilder#authenticationProvider と AuthenticationManagerBuilder#userDetailsService を呼び出さないこと。認証処理時にセットした UserDetailsService で2回認証処理が行われることになる。

Remember Me 認証に対応しようとすると Spring Security は何気に難易度が上がる感じがします。

src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java は daoAuhthenticationProvider メソッドだけコメントアウトする案を採用し、コメントアウトではなく削除で反映します。

履歴

2019/01/05
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その26 )( Gradle を 4.10.2 → 4.10.3 へ、Spring Boot を 2.0.6 → 2.0.7 へバージョンアップする。。。が Spring Security の bug のため Spring Boot は 2.0.6 へ戻す )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その25 )( Docker で PostreSQL+pgAdmin4+ Flyway の環境を構築する2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Gradle を 4.10.2 → 4.10.3 へ、Spring Boot を 2.0.x 系の最新バージョンである 2.0.7 へバージョンアップします。。。が Spring Security 5.0.10.RELEASE の bug で build が通らなかったので Spring Boot は 2.0.6 のままにします。
    • ライブラリは出来るだけ最新バージョンにします。

参照したサイト・書籍

  1. Gradle Build Tool - Releases
    https://gradle.org/releases/

  2. Spring Security 5.0 解剖速報
    https://www.slideshare.net/TakuyaIwatsuka/spring-security5report

  3. AuthenticationFailureBadCredentialsEvent published twice
    https://github.com/spring-projects/spring-security/issues/6281

目次

  1. gradle を 4.10.2 → 4.10.3 へバージョンアップする
  2. build.gradle を変更する
  3. AuthenticationFailureBadCredentialsEvent が2回発生する原因を調査する
  4. build.gradle を変更する2
  5. 最後に

手順

gradle を 4.10.2 → 4.10.3 へバージョンアップする

build.gradle の wrapper タスクの記述を以下のように変更します。

wrapper {
    gradleVersion = "4.10.3"
    distributionType = Wrapper.DistributionType.ALL
}
  • gradleVersion = "4.10.2"gradleVersion = "4.10.3" に変更します。

コマンドプロンプトから gradlew wrapper --gradle-version=4.10.3gradlew --version コマンドを実行します。

f:id:ksby:20190103175532p:plain

gradle/wrapper/gradle-wrapper.properties は以下の内容になります。

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します(画面キャプチャはなし)。

build.gradle を変更する

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

buildscript {
    ext {
        group "ksbysample"
        version "2.0.7-RELEASE"
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/release/" }
        maven { url "https://plugins.gradle.org/m2/" }
    }
}

plugins {
    id "java"
    id "eclipse"
    id "idea"
    id "org.springframework.boot" version "2.0.7.RELEASE"
    id "io.spring.dependency-management" version "1.0.6.RELEASE"
    id "groovy"
    id "checkstyle"
    id "com.github.spotbugs" version "1.6.8"
    id "pmd"
    id "net.ltgt.errorprone" version "0.0.16"
    id "de.undercouch.download" version "3.4.3"
    id "com.gorylenko.gradle-git-properties" version "2.0.0-beta1"
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

wrapper {
    gradleVersion = "4.10.3"
    distributionType = Wrapper.DistributionType.ALL
}

[compileJava, compileTestGroovy, compileTestJava]*.options*.encoding = "UTF-8"
[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = [
        "-Xlint:all,-options,-processing,-path"
        , "-Xep:RemoveUnusedImports:WARN"
        , "-Xep:InsecureCryptoUsage:OFF"
        , "-Xep:ParameterName:OFF"
]

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

springBoot {
    buildInfo()
}

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

configurations {
    // for Doma 2
    domaGenRuntime
}

checkstyle {
    configFile = file("${rootProject.projectDir}/config/checkstyle/google_checks.xml")
    toolVersion = "8.16"
    sourceSets = [project.sourceSets.main]
}

spotbugs {
    toolVersion = "3.1.10"
    ignoreFailures = true
    effort = "max"
    spotbugsTest.enabled = false
}
tasks.withType(com.github.spotbugs.SpotBugsTask) {
    reports {
        xml.enabled = false
        html.enabled = true
    }
}

pmd {
    toolVersion = "6.10.0"
    sourceSets = [project.sourceSets.main]
    ignoreFailures = true
    consoleOutput = true
    ruleSetFiles = rootProject.files("/config/pmd/pmd-project-rulesets.xml")
    ruleSets = []
}

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        // mavenBom は以下の URL のものを使用する
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/2.0.7.RELEASE/
        // bomProperty に指定可能な property は以下の URL の BOM に記述がある
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/2.0.7.RELEASE/spring-boot-dependencies-2.0.7.RELEASE.pom
        mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) {
            // Spring Boot の BOM に定義されているバージョンから変更する場合には、ここに以下のように記述する
            // bomProperty "thymeleaf.version", "3.0.9.RELEASE"
            bomProperty "groovy.version", "2.5.4"
        }
    }
}

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:42.2.5"
    def spockVersion = "1.2-groovy-2.5"
    def domaVersion = "2.20.0"
    def lombokVersion = "1.18.4"
    def errorproneVersion = "2.3.1"
    def powermockVersion = "2.0.0-RC.4"
    def spotbugsVersion = "3.1.10"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix F. Dependency versions ( https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html ) 参照
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf") {
        exclude group: "org.codehaus.groovy", module: "groovy"
    }
    implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")
    implementation("org.thymeleaf.extras:thymeleaf-extras-java8time")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-freemarker")
    implementation("org.springframework.boot:spring-boot-starter-mail")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-data-redis")
    implementation("org.springframework.boot:spring-boot-starter-amqp")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    implementation("org.springframework.session:spring-session-core")
    implementation("org.springframework.session:spring-session-data-redis")
    implementation("org.springframework.retry:spring-retry")
    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
    implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
    implementation("org.apache.commons:commons-lang3")
    implementation("org.codehaus.janino:janino")
    implementation("io.micrometer:micrometer-registry-prometheus")
    implementation("redis.clients:jedis")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("org.yaml:snakeyaml")
    testImplementation("org.mockito:mockito-core")
    runtimeOnly("org.springframework.boot:spring-boot-properties-migrator")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    runtimeOnly("${jdbcDriver}")
    implementation("com.integralblue:log4jdbc-spring-boot-starter:1.0.2")
    implementation("org.simpleframework:simple-xml:2.7.1")
    implementation("com.univocity:univocity-parsers:2.7.6")
    implementation("com.google.guava:guava:27.0.1-jre")
    implementation("org.flywaydb:flyway-core:5.2.4")
    testImplementation("org.dbunit:dbunit:2.6.0")
    testImplementation("com.icegreen:greenmail:1.5.9")
    testImplementation("org.assertj:assertj-core:3.11.1")
    testImplementation("com.jayway.jsonpath:json-path:2.4.0")
    testImplementation("org.jsoup:jsoup:1.11.3")
    testImplementation("cglib:cglib-nodep:3.2.10")
    testImplementation("org.spockframework:spock-core:${spockVersion}")
    testImplementation("org.spockframework:spock-spring:${spockVersion}")
    
    // for lombok
    annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
    compileOnly("org.projectlombok:lombok:${lombokVersion}")
    testCompileOnly("org.projectlombok:lombok:${lombokVersion}")

    // for Doma
    annotationProcessor("org.seasar.doma:doma:${domaVersion}")
    implementation("org.seasar.doma:doma:${domaVersion}")
    domaGenRuntime("org.seasar.doma:doma-gen:${domaVersion}")
    domaGenRuntime("${jdbcDriver}")

    // for Error Prone ( http://errorprone.info/ )
    errorprone("com.google.errorprone:error_prone_core:${errorproneVersion}")
    compileOnly("com.google.errorprone:error_prone_annotations:${errorproneVersion}")

    // PowerMock
    testImplementation("org.powermock:powermock-module-junit4:${powermockVersion}")
    testImplementation("org.powermock:powermock-api-mockito2:${powermockVersion}")

    // for SpotBugs
    compileOnly("com.github.spotbugs:spotbugs:${spotbugsVersion}")
    compileOnly("net.jcip:jcip-annotations:1.0")
    compileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}")
    testImplementation("com.google.code.findbugs:jsr305:3.0.2")
}

..........
  • buildscript block 内で version "2.0.6-RELEASE"version "2.0.7-RELEASE" に変更します。
  • plugins block の以下の点を変更します。
    • id "org.springframework.boot" version "2.0.6.RELEASE"id "org.springframework.boot" version "2.0.7.RELEASE"
    • id "com.github.spotbugs" version "1.6.5"id "com.github.spotbugs" version "1.6.8"
  • checkstyle タスクで toolVersion = "8.14"toolVersion = "8.16" に変更します。
  • spotbugs タスクで toolVersion = "3.1.8"toolVersion = "3.1.10" に変更します。
  • pmd タスクで toolVersion = "6.9.0"toolVersion = "6.10.0" に変更します。
  • dependencies block の以下の点を変更します。
    • def domaVersion = "2.19.3"def domaVersion = "2.20.0"
    • def spotbugsVersion = "3.1.8"def spotbugsVersion = "3.1.10"
    • implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity4")implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")Tomcat 起動時に Auto-configuration for thymeleaf-extras-springsecurity4 is deprecated in favour of thymeleaf-extras-springsecurity5 という WARN ログが出力されていました。
    • implementation("com.google.guava:guava:27.0-jre")implementation("com.google.guava:guava:27.0.1-jre")
    • testImplementation("com.icegreen:greenmail:1.5.8")testImplementation("com.icegreen:greenmail:1.5.9")
    • testImplementation("cglib:cglib-nodep:3.2.9")testImplementation("cglib:cglib-nodep:3.2.10")

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行してみると、テストが1件失敗しました。

f:id:ksby:20190103204541p:plain

もう少し詳細な情報が欲しいので、Project Tool Window で src/test を選択した後コンテキストメニューを表示して「Run 'All Tests'」を選択します。

テストが失敗したのは「ログインを5回失敗すればアカウントはロックされる」のテストで、ログインの失敗回数が1回だけのはずが2回カウントされているためでした。

f:id:ksby:20190103210042p:plain f:id:ksby:20190103210236p:plain

debug 実行してみるとログイン前は失敗回数が 0 回ですが、

f:id:ksby:20190103211614p:plain f:id:ksby:20190103211811p:plain

1度ログインに失敗しただけなのに失敗回数が2回になっていました。

f:id:ksby:20190103211929p:plain f:id:ksby:20190103212043p:plain

もう少し調査して分かったことは ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent が2回呼び出されている(AuthenticationFailureBadCredentialsEvent が2回発生している)ということでした。build.gradle を変更前に戻して試してみると AuthenticationFailureBadCredentialsEventListener#onApplicationEvent は1回しか呼び出されません。

f:id:ksby:20190103232534p:plain

AuthenticationFailureBadCredentialsEvent が2回発生する原因を調査する

2回実行されるのが問題と分かっている ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent メソッドの最初の位置に breakpoint を設定してから、「ログインを5回失敗すればアカウントはロックされる」のテストを debug 実行して、breakpoint で止まった後に IntelliJ IDEA の Debug Window でスタックトレースをたどって通過している位置を確認しながら何が起きているのかを調べます。

f:id:ksby:20190105080448p:plain

そうして分かったことは、まず org.springframework.security.authentication.ProviderManager#authenticate が呼び出されますが、この時は for (AuthenticationProvider provider : getProviders()) { ... } では例外が発生せず lastException も null のままです。getProviders() で AnonymousAuthenticationProvider, RememberMeAuthenticationProvider が返ってきますが、この2つは if (!provider.supports(toTest)) { continue; } の処理で continue されます。

f:id:ksby:20190105021223p:plain f:id:ksby:20190105021345p:plain

その下に if (result == null && parent != null) { ... } という if 文がありますが、parent が null ではなく ProviderManager のインスタンスがセットされているので、その中の result = parentResult = parent.authenticate(authentication); が実行されます。

f:id:ksby:20190105022013p:plain f:id:ksby:20190105022133p:plain

org.springframework.security.authentication.ProviderManager#authenticate が呼び出されてますが、今度は for (AuthenticationProvider provider : getProviders()) { ... } で例外が発生して lastException に org.springframework.security.authentication.BadCredentialsExceptionインスタンスがセットされます。getProviders() で DaoAuthenticationProvider が3つ返ってきますが(3つは何かおかしいので後で見直します)、全て result = provider.authenticate(authentication); が実行されて AuthenticationException が発生します。

f:id:ksby:20190105023034p:plain f:id:ksby:20190105023408p:plain f:id:ksby:20190105023520p:plain

その下の if 文3つはどれも条件が一致せず、次の prepareException(lastException, authentication); が実行されます。

f:id:ksby:20190105024310p:plain

org.springframework.security.authentication.ProviderManager#prepareException の中の eventPublisher.publishAuthenticationFailure(ex, auth); が実行されると eventPublisher に DefaultAuthenticationEventPublisher のインスタンスがセットされているので AuthenticationFailureBadCredentialsEvent が発生して ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent が呼び出されてログインの失敗回数が +1 されます。

f:id:ksby:20190105024550p:plain f:id:ksby:20190105024701p:plain

その後で org.springframework.security.authentication.ProviderManager#authenticate の最後の throw lastException; が実行されると、最初の org.springframework.security.authentication.ProviderManager#authenticate に戻り、org.springframework.security.authentication.BadCredentialsException の例外が throw されているので catch (AuthenticationException e) { lastException = e; } の処理が実行されて lastException に org.springframework.security.authentication.BadCredentialsExceptionインスタンスがセットされます。

f:id:ksby:20190105030150p:plain

その下の if 文2つはどれも条件が一致せず、次の prepareException(lastException, authentication); が実行されます。

f:id:ksby:20190105030523p:plain

org.springframework.security.authentication.ProviderManager#prepareException の中の eventPublisher.publishAuthenticationFailure(ex, auth); が実行されると先程と同じく eventPublisher に DefaultAuthenticationEventPublisher のインスタンスがセットされているので AuthenticationFailureBadCredentialsEvent が発生して ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent が呼び出されてログインの失敗回数が +1 されます。

f:id:ksby:20190105030648p:plain f:id:ksby:20190105030751p:plain

Spring Boot を 2.0.6 → 2.0.7 へ上げる前に戻して何が違うのか確認してみたところ、2回目の org.springframework.security.authentication.ProviderManager#prepareException の中の eventPublisher.publishAuthenticationFailure(ex, auth); が実行された時に eventPublisher にセットされているのが ProviderManager$NullEventPublisher のインスタンスで、この場合 ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent が呼び出されていませんでした(ログインの失敗回数も +1 されません)。

f:id:ksby:20190105032050p:plain f:id:ksby:20190105032206p:plain

なぜ eventPublisher にセットされているインスタンスが変わるのか Spring Security の 5.0.9.RELEASE と 5.0.10.RELEASE の差異を追ってみると、org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#getHttp で authenticationBuilder.authenticationEventPublisher(eventPublisher); という行が追加されていたことが原因でした。

f:id:ksby:20190105033604p:plain

変更が入った commit は Set AuthenticationEventPublisher on each AuthenticationManagerBuilder で、Fixes gh-6009 のリンクをクリックすると AuthenticationSuccessEvent not published for oauth2Login() #6009 の Issue へ飛んで、更に別の AuthenticationFailureBadCredentialsEvent published twice #6281 の Issue が見つかりました。修正されていて次のバージョンに入るようです。

build が通らなくなるので Spring Boot は 2.0.6 に戻します。Gradle やライブラリ関連はバージョンアップしたままにします。

build.gradle を変更する2

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

buildscript {
    ext {
        group "ksbysample"
        version "2.0.6-RELEASE"
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/release/" }
        maven { url "https://plugins.gradle.org/m2/" }
    }
}

plugins {
    id "java"
    id "eclipse"
    id "idea"
    id "org.springframework.boot" version "2.0.6.RELEASE"
    id "io.spring.dependency-management" version "1.0.6.RELEASE"
    id "groovy"
    id "checkstyle"
    id "com.github.spotbugs" version "1.6.8"
    id "pmd"
    id "net.ltgt.errorprone" version "0.0.16"
    id "de.undercouch.download" version "3.4.3"
    id "com.gorylenko.gradle-git-properties" version "2.0.0-beta1"
}

..........
  • buildscript block 内で version "2.0.7-RELEASE"version "2.0.6-RELEASE" に戻します。
  • plugins block の以下の点を変更します。
    • id "org.springframework.boot" version "2.0.7.RELEASE"id "org.springframework.boot" version "2.0.6.RELEASE"

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されました。

f:id:ksby:20190105041926p:plain

最後に

2.0 系の 2.0.7 とかなり後のバージョンなのでバージョンアップも簡単にできるはずと考えていましたが、そんなことはありませんでした! そうか、こんなこともあるんですね。。。

履歴

2019/01/05
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その25 )( Docker で PostreSQL+pgAdmin4+ Flyway の環境を構築する2 )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その24 )( Docker で PostreSQL+pgAdmin4+ Flyway の環境を構築する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Spring Boot の Web アプリケーションにも Flyway を導入します。
    • PostgreSQL のメトリックスを Prometheus+Grafana で見られるようにします。

参照したサイト・書籍

  1. Spring Boot Reference Guide - 82.5.1 Execute Flyway Database Migrations on Startup
    https://docs.spring.io/spring-boot/docs/2.0.7.RELEASE/reference/htmlsingle/#howto-execute-flyway-database-migrations-on-startup

  2. PostgreSQL Statistics
    https://grafana.com/dashboards/6742

  3. wrouesnel/postgres_exporter
    https://github.com/wrouesnel/postgres_exporter

目次

  1. Spring Boot の Web アプリケーションに Flyway を導入する
  2. PostgreSQL のメトリックスを Prometheus+Grafana で見られるようにする

手順

Spring Boot の Web アプリケーションに Flyway を導入する

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

dependencies {
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    ..........
    implementation("com.google.guava:guava:27.0-jre")
    implementation("org.flywaydb:flyway-core:5.2.4")
    testImplementation("org.dbunit:dbunit:2.6.0")
    ..........
  • dependencies block に implementation("org.flywaydb:flyway-core:5.2.4") を追加します。

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

動作確認します。docker-compose.yml の flyway コンテナの設定をコメントアウトしてから、

#  flyway:
#    image: boxfuse/flyway:${FLYWAY_VERSION}-alpine
#    container_name: flyway
#    volumes:
#      - ./src/main/resources/db/migration:/flyway/sql
#    command: -url=${FLYWAY_URL} -user=${FLYWAY_USER} -password=${FLYWAY_PASSWORD} -connectRetries=60 migrate
#    depends_on:
#      - postgresql

docker-compose up -d コマンドを実行した後 Tomcat を起動すると flyway_schema_history テーブルの create table 文が実行されており、

f:id:ksby:20190103120942p:plain

pgAdmin4 で接続してみるとテーブルが作成されてデータも登録されていました。

f:id:ksby:20190103121856p:plain

tomcat を停止して docker-compose down コマンドを実行した後、docker-compose.yml を元に戻します。

今度は docker で flyway コンテナを起動した後、SQL ファイルを追加して Tomcat 起動時に実行されるか確認します。

docker-compose up -d コマンドを実行します。

src/main/resources/db/migration の下に V2__create_table.sql を新規作成した後、以下の内容を記述します。

create table test_table (
    test_id                 bigserial primary key
    , test_data             text
);

tomcat を起動すると V2__create_table.sql が実行されて test_table が作成されていました。

f:id:ksby:20190103123420p:plain

問題なさそうです。

また Tomcat を起動時に Direct configuration of the Flyway object has been deprecated and will be removed in Flyway 6.0. Use Flyway.configure() instead. 等の WARN ログが出力されていました。おそらく Flyway の AutoConfiguration だと思いますが、Spring Boot の対応バージョンが 6.0 になる前に独自に Flyway のバージョンを 6.0 系に上げようとしても上がらないかもしれません。

f:id:ksby:20190103114613p:plain

PostgreSQL のメトリックスを Prometheus+Grafana で見られるようにする

PostgreSQL のメトリックスを PostgreSQL Statistics で Grafana で表示させてみます。

PostgreSQL → Prometheus への exporter は wrouesnel/postgres_exporter を Docker で起動して利用します。

https://github.com/wrouesnel/postgres_exporter#running-as-non-superuser を参考にして src/main/resources/db/init/create_database.sql に以下の SQL 文を追加して postgres_exporter 用のユーザを作成します。

-- postgres_exporter user
CREATE USER postgres_exporter PASSWORD 'zzzzzzzz';
ALTER USER postgres_exporter SET SEARCH_PATH TO postgres_exporter,pg_catalog;
-- If deploying as non-superuser (for example in AWS RDS), uncomment the GRANT
-- line below and replace <MASTER_USER> with your root user.
-- GRANT postgres_exporter TO <MASTER_USER>
CREATE SCHEMA postgres_exporter AUTHORIZATION postgres_exporter;
CREATE VIEW postgres_exporter.pg_stat_activity
AS
SELECT * from pg_catalog.pg_stat_activity;
GRANT SELECT ON postgres_exporter.pg_stat_activity TO postgres_exporter;
CREATE VIEW postgres_exporter.pg_stat_replication AS
SELECT * from pg_catalog.pg_stat_replication;
GRANT SELECT ON postgres_exporter.pg_stat_replication TO postgres_exporter;

.env を以下のように変更します。

........
FLYWAY_PASSWORD=xxxxxxxx

POSTGRES_EXPORTER_USER=postgres_exporter
POSTGRES_EXPORTER_PASSWORD=zzzzzzzz
  • 以下の2行を追加します。
    • POSTGRES_EXPORTER_USER=postgres_exporter
    • POSTGRES_EXPORTER_PASSWORD=zzzzzzzz

docker-compose.yml に postgres_exporter の設定を追加します。

  flyway:
    ..........

  postgres_exporter:
    image: wrouesnel/postgres_exporter
    container_name: postgres_exporter
    environment:
      - DATA_SOURCE_NAME=postgresql://${POSTGRES_EXPORTER_USER}:${POSTGRES_EXPORTER_PASSWORD}@postgresql:5432/ksbylending?sslmode=disable
    depends_on:
      - postgresql

docker/prometheus/prometheus.yml を以下のように変更します。

- job_name: 'postgres_exporter'
  static_configs:
    - targets: ['postgres_exporter:9187']

docker-compose up -d コマンドを実行します。

f:id:ksby:20190103161540p:plain

Grafana に PostgreSQL StatisticsDashboard を追加します。http://localhost:3000/ にアクセスした後、画面左側のメニューから「Create」-「Import」を選択します。

「Import」画面が表示されますので 6742 の ID を入力します。

f:id:ksby:20190103161838p:plain

「Prometheus」で「spring-actuator」を選択した後、「Import」ボタンをクリックします。

f:id:ksby:20190103162012p:plain

PostgreSQL StatisticsDashboard が表示されます(画面右上の表示間隔の設定を Last 30 minutes → Last 15 minutes Refresh every 5s に変更しています)。

f:id:ksby:20190103162253p:plain f:id:ksby:20190103162405p:plain f:id:ksby:20190103162457p:plain f:id:ksby:20190103162626p:plain

履歴

2019/01/03
初版発行。

Spring Boot + Spring Integration でいろいろ試してみる ( 番外編 )( POP over SSL で Docker コンテナと通信できなくなる問題を調べてみる。。。が、解決はしませんでした )

概要

記事一覧はこちらです。

Spring Boot + Spring Integration でいろいろ試してみる ( その30 )( Docker Compose でサーバを構築する、SMTP over SSL+POP over SSLサーバ編 ) で POP over SSL を試すと全ての Docker コンテナとの通信が出来なくなると書きましたが、何か対応方法がないか調べてみましたので、そのメモ書きです。結局解決はできませんでしたが。。。

参照したサイト・書籍

  1. Windows Defenderのリアルタイム保護を無効化/有効化する
    https://news.mynavi.jp/article/win10tips-251/

  2. Configure and troubleshoot the Docker daemon
    https://docs.docker.com/config/daemon/

  3. moby/vpnkit
    https://github.com/moby/vpnkit

  4. Docker for WindowsのMobyLinuxVMに接続する方法
    https://qiita.com/gentaro/items/cf666259cb6baf2eb8db

  5. How do I request a file but not save it with Wget? [closed]
    https://stackoverflow.com/questions/9691367/how-do-i-request-a-file-but-not-save-it-with-wget

  6. 【Go】マルチプレクサってなんやねん
    https://qiita.com/huji0327/items/c85affaf5b9dbf84c11e

  7. docker stats
    https://docs.docker.com/engine/reference/commandline/stats/

  8. 「top」は時代遅れ!?これからは「htop」を使おう!
    https://linuxfan.info/htop

  9. htop - an interactive process viewer for Unix
    https://hisham.hm/htop/

目次

  1. Windows Defender のリアルタイム保護が原因ではないようだ
  2. Docker 関連のログは C:\Users\<ユーザ名>\AppData\Local\Docker\log.txt に出力される
  3. VpnKit とは?
  4. MobyLinuxVM に接続して通信状況を確認する
  5. docker stats コマンドでコンテナ毎のリソース使用量をリアルタイムで見られる
  6. htop をインストールしてコンテナ内のプロセスの状況を見てみる

手順

Windows Defender のリアルタイム保護が原因ではないようだ

Windows Defenderのリアルタイム保護を無効化/有効化する という記事を見つけて、全てのコンテナへの通信ができなくなるのは POP over SSL の時だけなので、もしかするとこれが原因か?。。。と試してみましたが、関係ありませんでした。PowerShell を管理者として実行した後、Set-MpPreference -DisableRealtimeMonitoring 1 で無効にしてから試してみましたが、再発します。

Docker 関連のログは C:\Users\<ユーザ名>\AppData\Local\Docker\log.txt に出力される

Configure and troubleshoot the Docker daemon の「Read the logs」に Windows の場合には AppData\Local にあると書かれていたので、調べてみたところ C:\Users\<ユーザ名>\AppData\Local\Docker\log.txt に出力されていました。

[03:43:24.374][Moby           ][Info   ] [   67.770300] IPv6: ADDRCONF(NETDEV_CHANGE): vethd4a2099: link becomes ready
[03:43:24.412][Moby           ][Info   ] [   67.804819] br-e83b99f00770: port 1(vethd4a2099) entered blocking state
[03:43:24.445][Moby           ][Info   ] [   67.843327] br-e83b99f00770: port 1(vethd4a2099) entered forwarding state
[03:43:24.517][Moby           ][Info   ] [   67.890008] IPv6: ADDRCONF(NETDEV_CHANGE): veth753c8c1: link becomes ready
[03:43:24.547][Moby           ][Info   ] [   67.948086] br-e83b99f00770: port 3(veth753c8c1) entered blocking state
[03:43:24.588][Moby           ][Info   ] [   67.979328] br-e83b99f00770: port 3(veth753c8c1) entered forwarding state
[03:43:24.753][ApiProxy       ][Info   ] time="2018-12-27T03:43:24+09:00" msg="proxy << POST /v1.25/containers/9a96b7e2b06c03c8153949f25f93a05cd3e2848241fede2e756d6ee077b5b887/start (3.3064587s)\n"
[03:43:24.773][ApiProxy       ][Info   ] time="2018-12-27T03:43:24+09:00" msg="proxy << POST /v1.25/containers/4133e614b7baa7404d7d2459443c5e83d200e47ac265c838720f1c2a64b6bcd2/start (2.709457s)\n"
[03:46:56.439][VpnKit         ][Info   ] vpnkit.exe: Connected Ethernet interface f6:16:36:bc:f9:c6

[03:46:56.439][VpnKit         ][Info   ] vpnkit.exe: UDP interface connected on 192.168.65.1

[03:47:07.337][VpnKit         ][Error  ] vpnkit.exe: Socket.Stream: caught 既存��E接続��EリモーチEホストに強制皁E��刁E��されました、E

[03:47:22.580][VpnKit         ][Error  ] vpnkit.exe: Socket.Stream: caught 既存��E接続��EリモーチEホストに強制皁E��刁E��されました、E

[03:48:08.141][VpnKit         ][Error  ] vpnkit.exe: Socket.Stream: caught 既存��E接続��EリモーチEホストに強制皁E��刁E��されました、E
  • VpnKit の Error が出力されている(UTF-8 で表示させたにも関わらず文字化けしていますが。。。)。そもそも VpnKit って何?
  • その前のログを見ていると IPv6: ADDRCONF(NETDEV_CHANGE): veth753c8c1: link becomes ready というログが出ている。コンテナの方でも IPv4 で通信していると思っていたが、IPv6 を使っている?

VpnKit とは?

https://twitter.com/Docker/status/800760722716299264 には VPNKit is a set of tools and services for helping HyperKit VMs interoperate w/ host VPN configurations. と書かれていました。Port-forwarding を読むと、ホストからコンテナへのポートフォワードを行ってくれるのも vpnkit のようです。

コンテナと通信できなくなる問題が発生した時も vpnkit は Error ログを出力していて動いているようなので、Plumbing inside Docker for Windows の図を見た感じだと Hyper-V か Docker の MobyLinuxVM の問題なのでしょうか?

MobyLinuxVM に接続して通信状況を確認する

MobyLinuxVM に入って状況を確認できないのか調べてみたところ、Docker for WindowsのMobyLinuxVMに接続する方法 という記事を見つけました。

この手順で MobyLinuxVM に接続して見てみると wget コマンドが使えることが分かったので、通信不可になる前の状態の時に以下2つの URL へアクセスしてみると、どちらも問題なくアクセスできました。

  • wget -O- ip6-localhost:8888/rainloop/v/1.12.1/static/apple-touch-icon.png(コンテナ内の URL)
  • wget -O- https://www.gstatic.com/images/branding/googlemic/2x/googlemic_color_24dp.png(インターネット側の URL)

f:id:ksby:20181228124844p:plain

通信不可になってから再度試してみても、どちらも問題なくアクセスできました。

f:id:ksby:20181228125846p:plain

vpnkit.exe: Socket.Stream: caught 既存��E接続��EリモーチEホストに強制皁E��刁E��されました、E というログと、今回確認した結果を合わせると、

  • vpnkit.exe は起動時に MobyLinuxVM と Socket.Stream を接続する(接続したままにする)。
  • POP over SSL で接続不可になった時には、この Socket.Stream の接続が強制切断されている。
  • vpnkit.exe - MobyLinuxVM 間の Socket.Stream が切れているため、PC からコンテナへの通信が全て失敗する。

という状況のような気がします。仮にこの推測が正しいとすると、接続・切断を繰り返す SSL 通信を PC - コンテナ間で実施すると POP over SSL の時と同様に、全てのコンテナにアクセスできなくなる状況が発生するということでしょうか?

後から気づきましたが、MobyLinuxVM に接続できると /var/log の下に出力されている Docker のログファイルが見れました。

f:id:ksby:20181229133458p:plain

vpnkit が port forward している時のログと思われるファイルがちょうど vpnkit-forward.log という名前であったので、tail -f vpnkit-forwarder.log で問題発生時のログを見てみると Multiplexer main loop failed with Unknown channel id: 10 というログが出力されていました。

f:id:ksby:20181229140614p:plain

Multiplexer って何?。。。と思って調べてみると、【Go】マルチプレクサってなんやねん という記事を見つけました。vpnkit は Go ではなく OCaml で実装されていますが、概念的には同じようなもののはず。中でエラーが出ていることは分かりましたが、これ以上はどうしようもないかな。。。

もう1点。vpnkit.exe は PC 上で実行されていますが(Windows のタスクマネージャで動作しているのが確認できます)、

f:id:ksby:20181229174934p:plain

なぜ MobyLinuxVM 上でログが出るのだろう?と思って MobyLinuxVM で ps -ef | grep "vpnkit" で見てみたらポートフォーワードの設定毎に /usr/bin/vpnkit-expose-port が実行されていました。そんな仕組みだったとは。。。

f:id:ksby:20181229175600p:plain

問題発生後に ps -ef | grep "vpnkit" を実行してみましたが、出力される結果は変わりませんでした。/usr/bin/vpnkit-expose-port が落ちている訳ではないようです。また docker-compose down コマンドを実行すると /usr/bin/vpnkit-expose-port のプロセスは全てなくなりました。

docker stats コマンドでコンテナ毎のリソース使用量をリアルタイムで見られる

POP over SSL の件とは全く関係ありませんが、調べている時に見つけて役に立ちそうだったので書いておきます。イメージとしては Docker コンテナ用の top コマンドみたいなものでしょうか。

docker stats --help でヘルプが見れます。

f:id:ksby:20181226232040p:plain

docker-compose up -d コマンドでコンテナを起動した後、

f:id:ksby:20181226232150p:plain

docker stats コマンドを実行すると実行中の全てのコンテナのリソース使用量が見られます(1秒未満の間隔で更新されています)。

f:id:ksby:20181226232314p:plain f:id:ksby:20181226232411p:plain

docker stats mail-server のようにコンテナ名を指定すると、指定されたコンテナのみのリソース使用量が表示されます。

f:id:ksby:20181226232722p:plain f:id:ksby:20181226233015p:plain

htop をインストールしてコンテナ内のプロセスの状況を見てみる

top よりも使い勝手がよい htop というプロセスビューアがあることを知ったので、そのメモ書きです。

debian で apt-get を使用する場合には、apt-get update && apt-get install -y htop && apt-get clean && rm -rf /var/lib/apt/lists/* を実行します。

mail-server コンテナで試すと以下のようになりました。

f:id:ksby:20181229171026p:plain

htop コマンドで起動します。

f:id:ksby:20181229165528p:plain

Alpine Linux の場合には apk add --no-cache htop を実行します。

f:id:ksby:20181229172140p:plain

履歴

2018/12/29
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その24 )( Docker で PostreSQL+pgAdmin4+ Flyway の環境を構築する )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( 番外編 )( docker volume メモ書き ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • PostgreSQL の環境を Windows バイナリ(postgresql-10.5-1-windows-x64.exe)をインストールして構築した環境から Docker の PostgreSQL 環境へ変更します。
    • pgAdmin4 も Docker 版を入れます。
    • Docker の Flyway 環境も構築し、docker-compose up -d コマンドでコンテナを作成した時にテーブルの作成、データの投入を自動で実行されるようにします。尚、Flyway の PostgreSQL のページ を見ると、まだ PostgreSQL の 11 はサポートされていませんでした。

参照したサイト・書籍

  1. dockerhub - postgres
    https://hub.docker.com/_/postgres/

  2. docker-library/postgres
    https://github.com/docker-library/postgres

  3. dpage/pgadmin4
    https://hub.docker.com/r/dpage/pgadmin4

  4. pgadmin4/pkg/docker/Dockerfile
    https://github.com/postgres/pgadmin4/blob/master/pkg/docker/Dockerfile

  5. Dockerfile reference
    https://docs.docker.com/engine/reference/builder/

    • VOLUME の説明を参照しました。
  6. Best practices for writing Dockerfiles
    https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

    • 今回の記事とは直接関係ありませんが、調べている時に見つけたのでメモっておきます。
  7. boxfuse/flyway
    https://hub.docker.com/r/boxfuse/flyway

  8. flyway/flyway-docker
    https://github.com/flyway/flyway-docker

目次

  1. IntelliJ IDEA で YAML ファイルをリフォーマットした時に配列が1つ右にインデントされるようになった
  2. 単体の PostgreSQL+pgAdmin4サーバの環境を構築する
  3. create database 文等の SQL を実行した後、動作確認する
  4. Docker で Flyway の環境を構築する
  5. 次回は。。。

手順

IntelliJ IDEA で YAML ファイルをリフォーマットした時に配列が1つ右にインデントされるようになった

おそらく 2018.3 にバージョンアップしたからだと思いますが、以前はリフォーマットしても配列の要素は1つ上の要素と左揃えになっていたのが(ports, environment の部分)、

  rabbitmq_exporter:
    image: kbudde/rabbitmq-exporter:latest
    container_name: rabbitmq_exporter
    ports:
    - "9419:9419"
    environment:
    - RABBIT_URL=http://haproxy:15672
    - RABBIT_USER=${RABBITMQ_DEFAULT_USER}
    - RABBIT_PASSWORD=${RABBITMQ_DEFAULT_PASS}
    - RABBIT_CAPABILITIES=bert,no_sort
    - PUBLISH_PORT=9419

Ctrl+Alt+L を押してリフォーマットすると1つ右にインデントするようになりました。個人的には嬉しい変更です。

  rabbitmq_exporter:
    image: kbudde/rabbitmq-exporter:latest
    container_name: rabbitmq_exporter
    ports:
      - "9419:9419"
    environment:
      - RABBIT_URL=http://haproxy:15672
      - RABBIT_USER=${RABBITMQ_DEFAULT_USER}
      - RABBIT_PASSWORD=${RABBITMQ_DEFAULT_PASS}
      - RABBIT_CAPABILITIES=bert,no_sort
      - PUBLISH_PORT=9419

単体の PostgreSQL+pgAdmin4サーバの環境を構築する

.env に PostgreSQL、pgAdmin4 のバージョン番号を記述します。

..........
RABBITMQ_DEFAULT_VHOST=/

POSTGRESQL_VERSION=11.1
PGADMIN4_VERSION=3.6
  • 以下の2行を追加します。
    • POSTGRESQL_VERSION=11.1
    • PGADMIN4_VERSION=3.6

pgadmin4 のデータを保存するディレクトリとして docker/pgadmin4/data を作成します。

docker-compose.yml に postgresql、pgadmin4 の設定を追加します。

  # 起動したコンテナに /bin/sh でアクセスする場合には以下のコマンドを実行する
  # docker exec -it postgresql /bin/sh
  #
  # psql を使用したい場合には以下のコマンドを実行する
  # docker exec -it postgresql psql -U postgres
  postgresql:
    image: postgres:${POSTGRESQL_VERSION}-alpine
    container_name: postgresql
    ports:
      - "5432:5432"
    volumes:
      - ./sql:/sql
    environment:
      - LANG=ja_JP.UTF-8
      - POSTGRES_PASSWORD=xxxxxxxx

  # URL
  # http://localhost:12000/
  pgadmin4:
    image: dpage/pgadmin4:${PGADMIN4_VERSION}
    container_name: pgadmin4
    ports:
      - "12000:80"
    volumes:
      - ./docker/pgadmin4/data:/var/lib/pgadmin
    environment:
      # PGADMIN_DEFAULT_EMAIL には接続する PostgreSQL の ユーザ名を設定する(サーバを追加する時楽なため)
      - PGADMIN_DEFAULT_EMAIL=postgres
      - PGADMIN_DEFAULT_PASSWORD=yyyyyyyy
  • PostgreSQL の Docker Image は -alpine 版を使用します。
  • 構築後に SQL ファイルを実行したいので volumes に ./sql:/sql を記述しています(Flyway の環境を導入したら削除します)。
  • PostgreSQL の Docker Image はデフォルトでは ENV LANG en_US.utf8 と設定されているので、LANG=ja_JP.UTF-8 を設定して日本語環境にします。また PostgreSQL を Docker で構築する記事を見ると RUN localedef -i ja_JP -c -f UTF-8 -A /usr/share/locale/locale.alias ja_JP.UTF-8 が記載されていることが多いのですが、postgres/Dockerfile-alpine.template の中にコメントで alpine doesn't require explicit locale-file generation と記載されており、-alpine 版では不要です。
  • pgAdmin4 の Docker Image は dpage/pgadmin4 を使用します。
  • pgAdmin4 のポート番号はホストの適当に空いているところに関連付けます(今回は 12000番ポートにしました)。
  • pgAdmin4 の設定情報をホストのディレクトリに保存したいので volumes に ./docker/pgadmin4/data:/var/lib/pgadmin を記述します。外部からマウントするディレクトリをどうやって探すのかよく分かっていなかったのですが、Dockerfile の中に VOLUME ... で記述されているディレクトリを見つければよいだけでした。
  • pgAdmin4 起動後に参照するサーバを追加するのですが、PGADMIN_DEFAULT_EMAIL にサーバに接続する時のユーザ名を設定しておくとサーバ追加時の設定が少し楽になるので postgres を記述しておきます(別に EMAIL である必要はないようです)。GADMIN_DEFAULT_EMAIL、PGADMIN_DEFAULT_PASSWORD は pgAdmin4 にログインする時のユーザID、パスワードになります。

docker-compose up -d コマンドを実行します。

f:id:ksby:20181224174918p:plain f:id:ksby:20181224175012p:plain

docker exec -it postgresql psql -U postgres コマンドを実行し psql で接続した後、日本語環境で PostgreSQL が構築されていることを確認します。

f:id:ksby:20181224175216p:plain

pgAdmin4 で接続してみます。http://localhost:12000/ にアクセスしてログイン画面が表示されたら ID、パスワード(docker-compose.yml に設定した PGADMIN_DEFAULT_EMAIL、PGADMIN_DEFAULT_PASSWORD の文字列)を入力し、「language」を "Japanese" に変更した後、「Login」ボタンをクリックします。

f:id:ksby:20181224175609p:plain

ログインして pgAdmin4 のメイン画面が表示されたらサーバを追加します。画面左側の「Servers」を選択してコンテンキストメニューを表示した後、「作成」-「サーバ...」を選択します。

f:id:ksby:20181224180106p:plain

「作成 - サーバ」ダイアログが表示されるので、「名称」に PostgreSQL のコンテナ名と同じ "postgresql" を入力します。

f:id:ksby:20181224180244p:plain

「接続」タブをクリックした後、「ホスト名/アドレス」に PostgreSQL のコンテナ名である "postgresql" を(docker-compose コマンドで起動しているのでコンテナ名で接続できます)、「パスワード」に PostgreSQL の postgres ユーザのパスワード(docker-compose.yml の postgresql コンテナに設定した POSTGRES_PASSWORD の文字列)を入力します。入力したら「保存」ボタンをクリックします。

※「ユーザ名」には PGADMIN_DEFAULT_EMAIL に設定した文字列がデフォルトでセットされます。今回は PostgreSQL に接続可能な "postgres" という文字列を PGADMIN_DEFAULT_EMAIL に設定していたので、改めて入力はしていません。

f:id:ksby:20181224180459p:plain

メイン画面上に追加した postgresql サーバが表示されます。

f:id:ksby:20181224180820p:plain

create database 文等の SQL を実行した後、動作確認する

プロジェクトの sql フォルダの下にある SQL ファイルを実行して database, table を作成した後、gradle の build タスクを実行してテストが正常に終了するか確認してみます。

まずは以下のコマンドを実行して SQL ファイルを実行します。

$ docker exec -it postgresql /bin/sh

# cd /sql
# psql -U postgres
postgres=# \i create_database.sql
postgres=# \q

# psql -U ksbylending_user ksbylending
ksbylending=> \i create_table.sql
ksbylending=> \i insert_data.sql
ksbylending=> \q

f:id:ksby:20181224181943p:plain f:id:ksby:20181224182031p:plain

gradle の build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されます。

f:id:ksby:20181224184630p:plain

IntelliJ IDEA から Tomcat を起動して Spring Actuator の health check を見ると db が PostgreSQL で認識されています。

f:id:ksby:20181224185844p:plain

以下の手順で画面から動作を確認してみましたが、こちらも問題ありませんでした。

  • ブラウザを起動して http://localhost:8080/ にアクセスしてログイン画面を表示します。tanaka.taro@sample.com / taro でログインします。
  • 検索対象図書館登録画面が表示されます。"東京都" で検索した後、一覧表示されている図書館から「国立国会図書館東京本館」を選択します。
  • ログアウトします。
  • ログイン画面に戻るので suzuki.hanako@test.co.jp / hanako でログインします。
  • 貸出希望書籍 CSV ファイルアップロード画面が表示されます。以下の内容が記述された CSV ファイルをアップロードします。

    "ISBN","書名"
    "978-4-7741-6366-6","GitHub実践入門"
    "978-4-7741-5377-3","JUnit実践入門"
    "978-4-7973-8014-9","Java最強リファレンス"
    "978-4-7973-4778-4","アジャイルソフトウェア開発の奥義"
    "978-4-87311-704-1","Javaによる関数型プログラミング"

  • 「貸出状況を確認しました」のメールが送信されるので、メールに記述されている URL にアクセスします。
  • 貸出申請画面が表示されます。3冊程「申請する」を選択して申請します。
  • ログアウトします。
  • 「貸出申請がありました」のメールが送信されるので、メールに記述されている URL にアクセスします。ログイン画面が表示されるので、tanaka.taro@sample.com / taro でログインします。
  • 貸出承認画面が表示されます。「承認」あるいは「却下」を選択して確定させます。
  • ログアウトします。
  • 「貸出申請が承認・却下されました」のメールが送信されるので、メールに記述されている URL にアクセスします。ログイン画面が表示されるので、suzuki.hanako@test.co.jp / hanako でログインします。
  • 貸出申請結果確認画面が表示されるので内容を確認します。

Docker で Flyway の環境を構築する

Docker で Flyway の環境を構築してみます。

src/main/resources の下に db/init、db/migration ディレクトリを作成します。

src/main/resources/db/init の下に sql/create_database.sql を移動します。

src/main/resources/db/migration の下に create_table.sql、insert_data.sql を移動し、以下のようにファイル名を変更します。

  • create_table.sql → V1__create_table.sql
  • insert_data.sql → V1.1__insert_data.sql

sql ディレクトリを削除します。ここまでの操作で以下のディレクトリ構成になります。

f:id:ksby:20181224230413p:plain

.env に Flyway のバージョン番号と設定を記述します。

..........
POSTGRESQL_VERSION=11.1
PGADMIN4_VERSION=3.6

FLYWAY_VERSION=5.2.4
FLYWAY_URL=jdbc:postgresql://postgresql/ksbylending
FLYWAY_USER=ksbylending_user
FLYWAY_PASSWORD=xxxxxxxx
  • 以下の4行を追加します。
    • FLYWAY_VERSION=5.2.4
    • FLYWAY_URL=jdbc:postgresql://postgresql/ksbylending
    • FLYWAY_USER=ksbylending_user
    • FLYWAY_PASSWORD=xxxxxxxx

docker-compose.yml を以下のように変更します。

  postgresql:
    image: postgres:${POSTGRESQL_VERSION}-alpine
    container_name: postgresql
    ports:
      - "5432:5432"
    volumes:
      - ./src/main/resources/db/init/create_database.sql:/docker-entrypoint-initdb.d/create_database.sql
    environment:
      - LANG=ja_JP.UTF-8
      - POSTGRES_PASSWORD=xxxxxxxx

  ..........

  flyway:
    image: boxfuse/flyway:${FLYWAY_VERSION}-alpine
    container_name: flyway
    volumes:
      - ./src/main/resources/db/migration:/flyway/sql
    command: -url=${FLYWAY_URL} -user=${FLYWAY_USER} -password=${FLYWAY_PASSWORD} -connectRetries=60 migrate
    depends_on:
      - postgresql
    # 下の3行は debug 用
    # うまく動かない時はコメントアウトを解除した後、
    # docker exec -it postgresql /bin/sh
    # で接続してから
    # flyway <command に記述した文字列>
    # を実行してみる
    #
    # entrypoint: /bin/sh
    # stdin_open: true
    # tty: true
  • postgresql コンテナの volumes の設定を以下のように変更します。
    • - ./sql:/sql を削除します。
    • - ./src/main/resources/db/init/create_database.sql:/docker-entrypoint-initdb.d/create_database.sql を追加します。create database 文は Flyway の実行前に実行しておく必要があるので postgresql コンテナの /docker-entrypoint-initdb.d ディレクトリの下に SQL ファイルを置いてコンテナ起動時に SQL ファイルを自動で実行させます(postgres/docker-entrypoint.sh 参照)。
  • flyway コンテナの設定を追加します。

docker-compose up -d コマンドを実行します。

f:id:ksby:20181224232024p:plain

pgAdmin4 でログインして ksbylending データベース及びテーブルが作成されていることを確認します。

f:id:ksby:20181224232230p:plain

build タスクを実行して "BUILD SUCCESSFUL" のメッセージが出力することも確認できました。

f:id:ksby:20181224233248p:plain

次回は。。。

Spring Boot アプリケーションにも Flyway を導入し、PostgreSQL のメトリックスを Prometheus+Grafana で見られるようにする予定です。

履歴

2018/12/24
初版発行。