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クライアント環境を構築する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- Spring Boot 2.0 Release Notes、Spring Boot 2.0 Migration Guide を見直していて、build.gradle の dependencies から削除してよい記述が見つかったので削除します。
参照したサイト・書籍
目次
- build.gradle の dependencies block から thymeleaf-extras-java8time を削除する
- build.gradle の dependencies block から jackson-datatype-jsr310 を削除する
- build.gradle の dependencies block から記述不要のものを削除する
手順
build.gradle の dependencies block から thymeleaf-extras-java8time を削除する
Spring Boot 2.0 Release Notes に The 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クライアント環境を構築する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- SMTPサーバは smtp4dev v2 version を使用していましたが、Docker の SMTPサーバ環境へ変更します。
- Webmail クライアントの Rainloop を Docker で起動して送信されたメールを確認できるようにします。
- SMTPサーバや Rainloop の環境構築に関する詳しい手順は以下の記事で書いています。今回は簡単に実施した作業を書くだけにします(キャプチャもほぼ貼りません)。
参照したサイト・書籍
目次
手順
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 でログインして設定します。
http://localhost:8888/ にアクセスし、tanaka.taro@sample.com / xxxxxxxx でログインできることを確認します。
動作確認
Tomcat を起動して、貸出希望書籍 CSV ファイルアップロード画面で CSV ファイルをアップロードしてからメールが届くことを確認します。
履歴
2019/01/05
初版発行。
Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その27 )( ProviderManager#getProviders が DaoAuthenticationProvider を3つ返す原因を調査する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
前回の記事において、org.springframework.security.authentication.ProviderManager#authenticate の中で
getProviders()
が DaoAuthenticationProvider を3つ返しているのを見つけたのですが、userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされているものが2つありました(残りの1つは ImMemoryUserDetailsManager)。LendingUserDetailsService がセットされている DaoAuthenticationProvider が2つあると認証処理時に DB への検索処理が2回実行されるので、1つだけになるように変更します。
参照したサイト・書籍
目次
- WebSecurityConfig クラスから daoAuhthenticationProvider, configAuthentication メソッドをコメントアウトしてみる
- WebSecurityConfig クラスの configAuthentication メソッドで
.userDetailsService(userDetailsService)
をコメントアウトしてみる - WebSecurityConfig クラスから daoAuhthenticationProvider メソッドだけコメントアウトしてみる
- まとめてみると。。。
手順
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 がセットされるようです。
テストも成功します。
ただし全てのテストを実行してみると「次回から自動的にログインするをチェックすれば次はログインしていなくてもログイン後の画面にアクセスできる」のテスト(Remember Me 認証のテスト)だけ失敗します。
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername の以下の場所でエラーになっており、delegate する先の UserDetailsService が見つからないことが原因のようです。
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つが返ってきていました。
テストは成功します。
全てのテストを実行してみると先程と同様に「次回から自動的にログインするをチェックすれば次はログインしていなくてもログイン後の画面にアクセスできる」のテスト(Remember Me 認証のテスト)だけ失敗します。ただし失敗の原因が異なり、今度は HTTPステータスコードが 200 ではなく 302 が返ってくるというものでした。
これは 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つが返ってきていました。
テストは成功します。
全てのテストを実行してみると、全て成功しました。
まとめてみると。。。
- 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 へ戻す )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- 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 のままにします。
- ライブラリは出来るだけ最新バージョンにします。
参照したサイト・書籍
Gradle Build Tool - Releases
https://gradle.org/releases/Spring Security 5.0 解剖速報
https://www.slideshare.net/TakuyaIwatsuka/spring-security5reportAuthenticationFailureBadCredentialsEvent published twice
https://github.com/spring-projects/spring-security/issues/6281
目次
- gradle を 4.10.2 → 4.10.3 へバージョンアップする
- build.gradle を変更する
- AuthenticationFailureBadCredentialsEvent が2回発生する原因を調査する
- build.gradle を変更する2
- 最後に
手順
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.3
、gradlew --version
コマンドを実行します。
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件失敗しました。
もう少し詳細な情報が欲しいので、Project Tool Window で src/test を選択した後コンテキストメニューを表示して「Run 'All Tests'」を選択します。
テストが失敗したのは「ログインを5回失敗すればアカウントはロックされる」のテストで、ログインの失敗回数が1回だけのはずが2回カウントされているためでした。
debug 実行してみるとログイン前は失敗回数が 0 回ですが、
1度ログインに失敗しただけなのに失敗回数が2回になっていました。
もう少し調査して分かったことは ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent が2回呼び出されている(AuthenticationFailureBadCredentialsEvent が2回発生している)ということでした。build.gradle を変更前に戻して試してみると AuthenticationFailureBadCredentialsEventListener#onApplicationEvent は1回しか呼び出されません。
AuthenticationFailureBadCredentialsEvent が2回発生する原因を調査する
2回実行されるのが問題と分かっている ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent メソッドの最初の位置に breakpoint を設定してから、「ログインを5回失敗すればアカウントはロックされる」のテストを debug 実行して、breakpoint で止まった後に IntelliJ IDEA の Debug Window でスタックトレースをたどって通過している位置を確認しながら何が起きているのかを調べます。
そうして分かったことは、まず org.springframework.security.authentication.ProviderManager#authenticate が呼び出されますが、この時は for (AuthenticationProvider provider : getProviders()) { ... }
では例外が発生せず lastException も null のままです。getProviders()
で AnonymousAuthenticationProvider, RememberMeAuthenticationProvider が返ってきますが、この2つは if (!provider.supports(toTest)) { continue; }
の処理で continue されます。
その下に if (result == null && parent != null) { ... }
という if 文がありますが、parent が null ではなく ProviderManager のインスタンスがセットされているので、その中の result = parentResult = parent.authenticate(authentication);
が実行されます。
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 が発生します。
その下の if 文3つはどれも条件が一致せず、次の prepareException(lastException, authentication);
が実行されます。
org.springframework.security.authentication.ProviderManager#prepareException の中の eventPublisher.publishAuthenticationFailure(ex, auth);
が実行されると eventPublisher
に DefaultAuthenticationEventPublisher のインスタンスがセットされているので AuthenticationFailureBadCredentialsEvent が発生して ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent が呼び出されてログインの失敗回数が +1 されます。
その後で 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
のインスタンスがセットされます。
その下の if 文2つはどれも条件が一致せず、次の prepareException(lastException, authentication);
が実行されます。
org.springframework.security.authentication.ProviderManager#prepareException の中の eventPublisher.publishAuthenticationFailure(ex, auth);
が実行されると先程と同じく eventPublisher
に DefaultAuthenticationEventPublisher のインスタンスがセットされているので AuthenticationFailureBadCredentialsEvent が発生して ksbysample.webapp.lending.security.AuthenticationFailureBadCredentialsEventListener#onApplicationEvent が呼び出されてログインの失敗回数が +1 されます。
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 されません)。
なぜ eventPublisher
にセットされているインスタンスが変わるのか Spring Security の 5.0.9.RELEASE と 5.0.10.RELEASE の差異を追ってみると、org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#getHttp で authenticationBuilder.authenticationEventPublisher(eventPublisher);
という行が追加されていたことが原因でした。
変更が入った 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" のメッセージが出力されました。
最後に
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 の Web アプリケーションにも Flyway を導入します。
- PostgreSQL のメトリックスを Prometheus+Grafana で見られるようにします。
参照したサイト・書籍
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-startupPostgreSQL Statistics
https://grafana.com/dashboards/6742wrouesnel/postgres_exporter
https://github.com/wrouesnel/postgres_exporter
目次
手順
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 文が実行されており、
pgAdmin4 で接続してみるとテーブルが作成されてデータも登録されていました。
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 が作成されていました。
問題なさそうです。
また 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 系に上げようとしても上がらないかもしれません。
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
コマンドを実行します。
Grafana に PostgreSQL Statistics の Dashboard を追加します。http://localhost:3000/ にアクセスした後、画面左側のメニューから「Create」-「Import」を選択します。
「Import」画面が表示されますので 6742
の ID を入力します。
「Prometheus」で「spring-actuator」を選択した後、「Import」ボタンをクリックします。
PostgreSQL Statistics の Dashboard が表示されます(画面右上の表示間隔の設定を Last 30 minutes → Last 15 minutes Refresh every 5s に変更しています)。
履歴
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 コンテナとの通信が出来なくなると書きましたが、何か対応方法がないか調べてみましたので、そのメモ書きです。結局解決はできませんでしたが。。。
参照したサイト・書籍
Windows Defenderのリアルタイム保護を無効化/有効化する
https://news.mynavi.jp/article/win10tips-251/Configure and troubleshoot the Docker daemon
https://docs.docker.com/config/daemon/Docker for WindowsのMobyLinuxVMに接続する方法
https://qiita.com/gentaro/items/cf666259cb6baf2eb8dbHow 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【Go】マルチプレクサってなんやねん
https://qiita.com/huji0327/items/c85affaf5b9dbf84c11edocker stats
https://docs.docker.com/engine/reference/commandline/stats/「top」は時代遅れ!?これからは「htop」を使おう!
https://linuxfan.info/htophtop - an interactive process viewer for Unix
https://hisham.hm/htop/
目次
- Windows Defender のリアルタイム保護が原因ではないようだ
- Docker 関連のログは
C:\Users\<ユーザ名>\AppData\Local\Docker\log.txt
に出力される - VpnKit とは?
- MobyLinuxVM に接続して通信状況を確認する
docker stats
コマンドでコンテナ毎のリソース使用量をリアルタイムで見られる- 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)
通信不可になってから再度試してみても、どちらも問題なくアクセスできました。
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 のログファイルが見れました。
vpnkit が port forward している時のログと思われるファイルがちょうど vpnkit-forward.log という名前であったので、tail -f vpnkit-forwarder.log
で問題発生時のログを見てみると Multiplexer main loop failed with Unknown channel id: 10
というログが出力されていました。
Multiplexer って何?。。。と思って調べてみると、【Go】マルチプレクサってなんやねん という記事を見つけました。vpnkit は Go ではなく OCaml で実装されていますが、概念的には同じようなもののはず。中でエラーが出ていることは分かりましたが、これ以上はどうしようもないかな。。。
もう1点。vpnkit.exe は PC 上で実行されていますが(Windows のタスクマネージャで動作しているのが確認できます)、
なぜ MobyLinuxVM 上でログが出るのだろう?と思って MobyLinuxVM で ps -ef | grep "vpnkit"
で見てみたらポートフォーワードの設定毎に /usr/bin/vpnkit-expose-port
が実行されていました。そんな仕組みだったとは。。。
問題発生後に 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
でヘルプが見れます。
docker-compose up -d
コマンドでコンテナを起動した後、
docker stats
コマンドを実行すると実行中の全てのコンテナのリソース使用量が見られます(1秒未満の間隔で更新されています)。
docker stats mail-server
のようにコンテナ名を指定すると、指定されたコンテナのみのリソース使用量が表示されます。
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 コンテナで試すと以下のようになりました。
htop
コマンドで起動します。
Alpine Linux の場合には apk add --no-cache htop
を実行します。
履歴
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 はサポートされていませんでした。
参照したサイト・書籍
dockerhub - postgres
https://hub.docker.com/_/postgres/docker-library/postgres
https://github.com/docker-library/postgresdpage/pgadmin4
https://hub.docker.com/r/dpage/pgadmin4pgadmin4/pkg/docker/Dockerfile
https://github.com/postgres/pgadmin4/blob/master/pkg/docker/DockerfileDockerfile reference
https://docs.docker.com/engine/reference/builder/- VOLUME の説明を参照しました。
Best practices for writing Dockerfiles
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/- 今回の記事とは直接関係ありませんが、調べている時に見つけたのでメモっておきます。
boxfuse/flyway
https://hub.docker.com/r/boxfuse/flywayflyway/flyway-docker
https://github.com/flyway/flyway-docker
目次
- IntelliJ IDEA で YAML ファイルをリフォーマットした時に配列が1つ右にインデントされるようになった
- 単体の PostgreSQL+pgAdmin4サーバの環境を構築する
- create database 文等の SQL を実行した後、動作確認する
- Docker で Flyway の環境を構築する
- 次回は。。。
手順
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
コマンドを実行します。
docker exec -it postgresql psql -U postgres
コマンドを実行し psql で接続した後、日本語環境で PostgreSQL が構築されていることを確認します。
pgAdmin4 で接続してみます。http://localhost:12000/ にアクセスしてログイン画面が表示されたら ID、パスワード(docker-compose.yml に設定した PGADMIN_DEFAULT_EMAIL、PGADMIN_DEFAULT_PASSWORD の文字列)を入力し、「language」を "Japanese" に変更した後、「Login」ボタンをクリックします。
ログインして pgAdmin4 のメイン画面が表示されたらサーバを追加します。画面左側の「Servers」を選択してコンテンキストメニューを表示した後、「作成」-「サーバ...」を選択します。
「作成 - サーバ」ダイアログが表示されるので、「名称」に PostgreSQL のコンテナ名と同じ "postgresql" を入力します。
「接続」タブをクリックした後、「ホスト名/アドレス」に PostgreSQL のコンテナ名である "postgresql" を(docker-compose コマンドで起動しているのでコンテナ名で接続できます)、「パスワード」に PostgreSQL の postgres ユーザのパスワード(docker-compose.yml の postgresql コンテナに設定した POSTGRES_PASSWORD の文字列)を入力します。入力したら「保存」ボタンをクリックします。
※「ユーザ名」には PGADMIN_DEFAULT_EMAIL に設定した文字列がデフォルトでセットされます。今回は PostgreSQL に接続可能な "postgres" という文字列を PGADMIN_DEFAULT_EMAIL に設定していたので、改めて入力はしていません。
メイン画面上に追加した postgresql サーバが表示されます。
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
gradle の build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されます。
IntelliJ IDEA から Tomcat を起動して Spring Actuator の health check を見ると db が PostgreSQL で認識されています。
以下の手順で画面から動作を確認してみましたが、こちらも問題ありませんでした。
- ブラウザを起動して 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 を移動し、以下のようにファイル名を変更します。
sql ディレクトリを削除します。ここまでの操作で以下のディレクトリ構成になります。
.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
コマンドを実行します。
pgAdmin4 でログインして ksbylending データベース及びテーブルが作成されていることを確認します。
build タスクを実行して "BUILD SUCCESSFUL" のメッセージが出力することも確認できました。
次回は。。。
Spring Boot アプリケーションにも Flyway を導入し、PostgreSQL のメトリックスを Prometheus+Grafana で見られるようにする予定です。
履歴
2018/12/24
初版発行。