かんがるーさんの日記

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

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その35 )( Docker で起動しているサーバの TimeZone を Asia/Tokyo に変更する )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その34 )( Docker で複数の Tomcat を起動して動作確認する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回の記事で jar ファイル起動用コンテナを生成する Dockerfile に ENV TZ="Asia/Tokyo" を定義したのですが、これは Web アプリケーションを起動後にログを見たら日時が日本時間になっていないことに気づいたからです。
    • PostgreSQL のコンテナとかはおそらく日本時間になっていなんだろうな。。。と思ったので、確認して必要があれば変更します。

参照したサイト・書籍

目次

  1. 日本時間になっていないサーバを洗い出す
  2. 環境変数 TZ=Asia/Tokyo を設定してみる
  3. docker image を作成して rabbitmq1~3 に環境変数 TZ=Asia/Tokyo の設定が反映されるようにする

手順

日本時間になっていないサーバを洗い出す

docker-compose up -d コマンドを実行してから各サーバのログ等を見たところ、以下のサーバが日本時間になっていませんでした。結局全てのサーバでしたが。。。

  • docker logs コマンドで出力されるログを見て分かったもの
    • prometheus
    • grafana
    • redis-cluster-1~6
    • redis_exporter
    • rabbitmq1~3
    • rabbitmq_exporter
    • postgresql
    • pgadmin4
    • postgres_exporter
    • mail-server
  • ログが出力されていなかったので docker exec <コンテナ名> date コマンドを実行して分かったもの
    • haproxy
    • haproxy-rsyslog
    • rainloop
  • 実行完了していて分からなかったもの
    • redis-cluster-make
    • flyway

この状態で pgadmin4 から select now() を実行するとログと同じく日本時間ではありませんでした。

環境変数 TZ=Asia/Tokyo を設定してみる

docker-compose.yml の全てのコンテナに以下の設定を追加してみます。

    environment:
      - TZ=Asia/Tokyo

コンテナを起動し直して確認してみると全てのコンテナが日本時間にはなりませんでした。以下の結果です。

  • ログあるいは date コマンドの日時が変わらなかったもの
    • prometheus
    • redis_exporter
    • rabbitmq1~3
    • rabbitmq_exporter
    • pgadmin4
    • postgres_exporter
    • rainloop
  • ログあるいは date コマンドの日時が日本時間に変わったもの
    • grafana
    • redis-cluster-1~6
    • postgresql
    • mail-server
    • haproxy
    • haproxy-rsyslog

今は何がなんでも設定する必要はないので、日本時間に変わらなかったものについては rabbitmq1~3 以外は TZ=Asia/Tokyo の設定をコメントアウトすることにします。rabbitmq1~3 は Dockerfile を作成して TZ=Asia/Tokyo が反映されるイメージを作成してみます。

docker image を作成して rabbitmq1~3 に環境変数 TZ=Asia/Tokyo の設定が反映されるようにする

docker/rabbitmq/Dockerfile を新規作成し、以下の内容を記述します。

ARG RABBITMQ_VERSION
FROM rabbitmq:${RABBITMQ_VERSION}-alpine
RUN apk add --no-cache tzdata

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

  rabbitmq1:
    build:
      context: ./docker/rabbitmq
      args:
        - RABBITMQ_VERSION=${RABBITMQ_VERSION}
    image: rabbitmq:${RABBITMQ_VERSION}-alpine-custom
    container_name: rabbitmq1
    hostname: rabbitmq1
    environment:
      - TZ=Asia/Tokyo
      - RABBITMQ_ERLANG_COOKIE
      - RABBITMQ_DEFAULT_USER
      - RABBITMQ_DEFAULT_PASS
      - RABBITMQ_DEFAULT_VHOST
  rabbitmq2:
    image: rabbitmq:${RABBITMQ_VERSION}-alpine-custom
    container_name: rabbitmq2
    hostname: rabbitmq2
    environment:
      - TZ=Asia/Tokyo
      - RABBITMQ_ERLANG_COOKIE
    volumes:
      - ./docker/rabbitmq/cluster-entrypoint.sh:/usr/local/bin/cluster-entrypoint.sh
    entrypoint: /bin/sh -c /usr/local/bin/cluster-entrypoint.sh
    depends_on:
      - rabbitmq1
  rabbitmq3:
    image: rabbitmq:${RABBITMQ_VERSION}-alpine-custom
    container_name: rabbitmq3
    hostname: rabbitmq3
    environment:
      - TZ=Asia/Tokyo
      - RABBITMQ_ERLANG_COOKIE
    volumes:
      - ./docker/rabbitmq/cluster-entrypoint.sh:/usr/local/bin/cluster-entrypoint.sh
    entrypoint: /usr/local/bin/cluster-entrypoint.sh
    depends_on:
      - rabbitmq1
  • rabbitmq1 に build の記述を追加し、image に記述するイメージ名の末尾に -custom を追加します。
  • rabbitmq2, 3 は image に記述するイメージ名の末尾に -custom を追加します。これで rabbitmq1 で生成したイメージが使用されます。

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

f:id:ksby:20190122001633p:plain

ログに出力される日時が日本時間になっていることが確認できます。(1/22 00:16に起動しています)。

f:id:ksby:20190122001906p:plain

rabbitmq:3.7.8-management-alpine と rabbitmq:3.7.8-management-alpine-custom のサイズを docker image ls rabbitmq コマンドで確認してみると 1.7MB の差異でした。

f:id:ksby:20190122002530p:plain

履歴

2019/01/22
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その34 )( Docker で複数の Tomcat を起動して動作確認する )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その33 )( PC の IP アドレスが変更された時に修正するファイルを最小限にする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • これまでは jar ファイルを作成してから Windows のサービスで Tomcat を1インスタンスだけ起動して動作確認していましたが、Docker が使えるようになったので複数インスタンスを起動して動作確認してみます。
    • LoadBalancer には HAProxy を使用します。

参照したサイト・書籍

  1. Spring Boot with Docker
    https://spring.io/guides/gs/spring-boot-docker/

  2. Networking in Compose
    https://docs.docker.com/compose/networking/

  3. docker-compose creating multiple instances for the same image
    https://stackoverflow.com/questions/39663096/docker-compose-creating-multiple-instances-for-the-same-image

  4. Compose file versions and upgrading
    https://docs.docker.com/compose/compose-file/compose-versioning/

  5. Get Started, Part 3: Services
    https://docs.docker.com/get-started/part3/

  6. COPY failed: stat /var/lib/docker/tmp/docker-builder918577595/...
    https://github.com/moby/moby/issues/34986

  7. Web Server Load-Balancing with HAProxy on Ubuntu 14.04
    https://www.howtoforge.com/tutorial/ubuntu-load-balancer-haproxy/

  8. How to Enable HAProxy Stats
    https://tecadmin.net/how-to-configure-haproxy-statics/

  9. 多機能なロードバランサとして使える多機能プロクシサーバー「HAProxy」入門
    https://knowledge.sakura.ad.jp/8078/

  10. Using SSL Certificates with HAProxy
    https://serversforhackers.com/c/using-ssl-certificates-with-haproxy

    • 今回の記事では設定していませんが、HAProxy で http, https の両方を受け付けられるようにする記事を見つけたのでメモ書きとして残しておきます。
  11. How can I set the timezone please?
    https://github.com/gliderlabs/docker-alpine/issues/136

  12. How to set locale in Docker Alpine?
    https://stackoverflow.com/questions/49042223/how-to-set-locale-in-docker-alpine

  13. gitattributes
    https://git-scm.com/docs/gitattributes

目次

  1. 方針
  2. docker-compose.app.yml を作成する
  3. HAProxy の設定ファイル haproxy.cfg を作成する
  4. logback-spring.xml を変更し、spring.profiles.active = product の時にログをコンソールに出力できるようにする
  5. jar ファイル起動用コンテナを生成するための Dockerfile を作成する
  6. 動作確認
  7. *.sh をチェックアウトした時に必ず改行コードが LF になるよう .gitattributes を作成する

手順

方針

  • docker-compose.yml とは別に docker-compose.app.yml というファイル作成し、docker-compose up -d コマンドを実行した後に docker-compose -f docker-compose.app.yml up -d コマンドで起動します。
  • docker-compose.app.yml で jar ファイルからの Tomcatインスタンスと HAProxy を起動します。HAProxy の 8080番ポートにアクセスすると Tomcat の3インスタンスのどれかにリクエストが振り分けられます。
  • jar ファイルから起動する時の Profile は既存の product を使用します。また、Docker で起動する時に product の設定から変更するものは docker-compose.app.yml に環境変数(environment)として設定します。
  • product で起動する時はログファイルに出力するように設定していますが、Docker で起動する時にはコンソールに出力されるようにします。

docker-compose.app.yml を作成する

docker-compose.app.yml を新規作成し、以下の内容を記述します。

# docker-compose -f docker-compose.app.yml --compatibility up -d
# docker-compose -f docker-compose.app.yml --compatibility down
version: '3'

services:
  app:
    build:
      context: .
      dockerfile: docker/app/Dockerfile
    image: ksbysample-webapp-lending
    volumes:
      - ./build/libs/ksbysample-webapp-lending-2.0.8-RELEASE.jar:/app.jar
      - ./docker/app/docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh
    environment:
      - SPRING_DATASOURCE_HIKARI_JDBC_URL=jdbc:postgresql://postgresql/ksbylending
      - SPRING_MAIL_HOST=mail-server
      - SPRING_RABBITMQ_HOST=haproxy
    deploy:
      mode: replicated
      replicas: 3
    # entrypoint: /bin/sh
    # stdin_open: true
    # tty: true

  haproxy-app:
    image: haproxy:1.8.14-alpine
    container_name: haproxy-app
    volumes:
      - ./docker/app/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    ports:
      - "8080:8080"
    depends_on:
      - app
  haproxy-app-rsyslog:
    image: rafpe/docker-haproxy-rsyslog
    container_name: haproxy-app-rsyslog
    volumes:
      - ./docker/app/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    depends_on:
      - haproxy-app

networks:
  default:
    external:
      name: ksbysample-webapp-lending_default
  • app、haproxy-app、haproxy-app-rsyslog の3つのコンテナの設定を記述します。
  • コンテナから接続する docker network は docker-compose up -d コマンド実行時に自動生成される ksbysample-webapp-lending_default にします。docker-compose.app.yml の最後に networks: ksbysample-webapp-lending_default: external: name: ksbysample-webapp-lending_default の設定を記述します。
  • app コンテナの設定のポイントは以下の通りです。
    • jar ファイルを Docker コンテナにコピーすることも考慮して(今回はコピーしませんが)、build の下の context の設定を . にし、Dockerfile のパスを dockerfile に記述します。
    • jar ファイルはコンテナ内にコピーせず、/app.jar にマウントされるよう volumes に記述します。build し直した時にコンテナを生成し直さなくてもよいようにします。
    • 以下の設定はサーバをコンテナ名に変更したものを environment に記述します。
      • SPRING_DATASOURCE_HIKARI_JDBC_URL
      • SPRING_MAIL_HOST
      • SPRING_RABBITMQ_HOST
    • deploy の設定を記述し、docker-compose up -d コマンドでコンテナを生成した時に app コンテナのインスタンスを自動で複数起動されるようにします。また deploy の設定は本来 docker swarm 用の設定ですが、--compatibility オプションを追加すれば docker-compose up -d コマンドでも使用できるようになります。

HAProxy の設定ファイル haproxy.cfg を作成する

docker の下に app ディレクトリを新規作成した後、docker/app/haproxy.cfg を新規作成して以下の内容を記述します。

global
    log 127.0.0.1   local1
    maxconn 4096

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    retries 3
    option  redispatch
    maxconn 2000
    timeout connect 5000
    timeout client 50000
    timeout server 50000

listen app
    bind *:8080
    stats enable
    stats hide-version
    stats realm Haproxy\ Statistics
    stats refresh 5s
    stats uri /haproxy?stats
    balance         roundrobin
    option          httpclose
    option          forwardfor
    server          ksbysample-webapp-lending_app_1 ksbysample-webapp-lending_app_1:8080  check inter 5s rise 2 fall 3
    server          ksbysample-webapp-lending_app_2 ksbysample-webapp-lending_app_2:8080  check inter 5s rise 2 fall 3
    server          ksbysample-webapp-lending_app_3 ksbysample-webapp-lending_app_3:8080  check inter 5s rise 2 fall 3
  • docker-compose -f docker-compose.app.yml --compatibility up -d で複数起動させた app コンテナのコンテナ名は <プロジェクト名(ksbysample-webapp-lending)>_<コンテナ名(app)>_<連番(1~)> になるので、ksbysample-webapp-lending_app_1、ksbysample-webapp-lending_app_2、ksbysample-webapp-lending_app_3 の3サーバにリクエストを振り分けるように設定します。

logback-spring.xml を変更し、spring.profiles.active = product の時にログをコンソールに出力できるようにする

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    <property name="LOGGING_APPENDER" value="${logging.appender:-FILE}"/>

    <if condition='"${spring.profiles.active}" == "product" &amp;&amp; "${LOGGING_APPENDER}" == "FILE"'>
        <then>
            <property name="LOG_FILE" value="${LOG_FILE}"/>
            <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <encoder>
                    <pattern>${FILE_LOG_PATTERN}</pattern>
                </encoder>
                <file>${LOG_FILE}</file>
                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                    <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}</fileNamePattern>
                    <maxHistory>30</maxHistory>
                </rollingPolicy>
            </appender>
        </then>
    </if>

    <if condition='"${spring.profiles.active}" == "develop"'>
        <then>
            <root>
                <appender-ref ref="CONSOLE"/>
            </root>
        </then>
    </if>
    <if condition='"${spring.profiles.active}" == "product"'>
        <then>
            <root>
                <appender-ref ref="${LOGGING_APPENDER}"/>
            </root>
        </then>
    </if>
</configuration>
  • <property name="LOGGING_APPENDER" value="${logging.appender:-FILE}"/> を追加します。
  • <appender name="FILE" ... を定義している部分の <if condition='"${spring.profiles.active}" == "product"'><if condition='"${spring.profiles.active}" == "product" &amp;&amp; "${LOGGING_APPENDER}" == "FILE"'> に変更します。
  • <if condition='"${spring.profiles.active}" == "product"'> ... </if> の中に記述している <appender-ref ref="FILE"/><appender-ref ref="${LOGGING_APPENDER}"/> に変更します。

これで jar ファイル起動時に java の起動時オプションに -Dlogging.appender=CONSOLE を指定すれば Profile が product でも CONSOLE に出力されるようになります。

jar ファイル起動用コンテナを生成するための Dockerfile を作成する

docker/app/docker-entrypoint.sh を新規作成し、以下の内容を記述します。

#!/bin/sh
# 改行コードを LF にすること。LF でないと実行されない。

export JAVA_OPTS="-Xms1024m -Xmx1024m -XX:MaxMetaspaceSize=384m"

exec java $JAVA_OPTS \
          -Djava.security.egd=file:/dev/./urandom \
          -Dspring.profiles.active=product \
          -Dlogging.appender=CONSOLE \
          -jar /app.jar

docker/app/Dockerfile を新規作成し、以下の内容を記述します。

FROM openjdk:8-jdk-alpine
RUN apk add --no-cache tzdata
ENV TZ="Asia/Tokyo"
ENV LANG="ja_JP.UTF-8"
VOLUME /tmp
EXPOSE 8080
ENTRYPOINT ["docker-entrypoint.sh"]
  • Spring Boot with Docker の「Containerize It」に記載されている Dockerfile から以下の点を変更しました。
    • 以下の2行を削除します。
      • ARG JAR_FILE
      • COPY ${JAR_FILE} app.jar
    • jar ファイルから出力されるログの日時を日本時間にしたいので、以下の2行を追加します。
      • RUN apk add --no-cache tzdata
      • ENV TZ="Asia/Tokyo"
    • openjdk:8-jdk-alpine イメージのデフォルトの環境変数 LANG の値は C.UTF-8 なので、ENV LANG="ja_JP.UTF-8" を追加します。
    • 8080番ポートを公開するので EXPOSE 8080 を追加します。
    • ENTRYPOINT には "docker-entrypoint.sh" だけ記述します。

動作確認

docker-compose up -d コマンドを実行した後、build タスクを実行して jar ファイルを作成します。

f:id:ksby:20190120143407p:plain

docker-compose -f docker-compose.app.yml --compatibility up -d コマンドを実行します。

f:id:ksby:20190120143816p:plain

http://localhost:8080/haproxy?stats にアクセスして全てのインスタンスが緑色になるまで待ちます。

f:id:ksby:20190120143556p:plain f:id:ksby:20190120143718p:plain

IntelliJ IDEA の Docker Plugin を見ると ksbysample-webapp-lending_app_1、ksbysample-webapp-lending_app_2、ksbysample-webapp-lending_app_3 の3つのコンテナが起動しており、ログがファイルではなくコンソールに出力されていることが確認できます。TimeZone を設定しているので、ログの日時が日本時間になっていることも確認できます。

f:id:ksby:20190120144031p: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 でログインします。
  • 貸出申請結果確認画面が表示されるので内容を確認します。

動作確認は特に問題ありませんでした。

HAProxy の stats page を見るとセッション数も In と Out のバイト数もほぼ均等になっており、リクエストが各 Tomcat に転送されていることが分かります。

f:id:ksby:20190120145343p:plain

またセッション情報が Spring Session により Redis に格納されているのでログイン後にどの Tomcat にアクセスしてもログイン状態が維持されていますが、ログインした後、

f:id:ksby:20190120145656p:plain f:id:ksby:20190120145742p:plain

Redis の master に保存されているデータを全てクリアしてから、

f:id:ksby:20190120150105p:plain

メニューから「貸出希望書籍登録」を選択すると、セッション情報が削除されてログイン状態ではないのでログイン画面が表示されます。

f:id:ksby:20190120150322p:plain f:id:ksby:20190120150358p:plain

最後に docker-compose -f docker-compose.app.yml --compatibility down コマンドでコンテナを終了します。

f:id:ksby:20190120150735p:plain

これで簡単に Tomcat のマルチインスタンスでの動作確認が出来るようになりました。

*.sh をチェックアウトした時に必ず改行コードが LF になるよう .gitattributes を作成する

今回作成した docker/app/docker-entrypoint.sh は改行コードが LF でないと動作しないのですが、現在の git の設定ではチェックアウト時に改行コードを自動で CRLF になるように設定しているので(.gitconfig に autocrlf = true が設定されています)チェックアウトし直した時に改行コードが CRLF に変換されてしまいます。

*.sh だけ LF のまま維持する方法がないか調べたところ、https://git-scm.com/docs/gitattributes のページを見つけました。.gitattributes を作成して設定すれば維持できるようです。

.gitattributes を新規作成し、以下の内容を記述します。

*           text=auto
*.sh        text eol=lf

実際に別のディレクトリに git clone してみたところ、docker/app/docker-entrypoint.sh の改行コードが LF になっていました。

履歴

2019/01/20
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その33 )( PC の IP アドレスが変更された時に修正するファイルを最小限にする )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その32 )( Spring Boot を 2.0.6 → 2.0.8 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 別の記事を書いている途中に WindowsUpdate で PC を再起動したら IP アドレスが変更されたのですが、変更を反映するファイルが以下の5ファイルと多かったので最低限のファイルだけ修正すればよいようにします。
      • .env
      • docker/prometheus/prometheus.yml
      • src/main/resources/application-develop.properties
      • src/main/resources/application-product.properties
      • src/main/resources/application-unittest.properties
    • また HAProxy に stats page というものがあることに気づいたので、表示できるようにします。

参照したサイト・書籍

  1. Accessing host machine from within docker container
    https://forums.docker.com/t/accessing-host-machine-from-within-docker-container/14248

    • docker container から PC で起動している Spring Boot の Web アプリケーションにアクセスする時に docker run --add-host="localhost:192.168.65.1" のように --add-host オプションを使うとホスト名でアクセスできると書かれていました。
    • https://docs.docker.com/compose/compose-file/ を見ると docker-compose の場合には、extra_hosts で定義できるようです。

目次

  1. docker-compose.yml、prometheus.yml を変更する
  2. application.properties を変更する
  3. RabbitMQ 用 HAProxy の stats page を表示する

手順

docker-compose.yml、prometheus.yml を変更する

まずは Prometheus と Grafana から。docker-compose.yml に extra_hosts を設定すると IP アドレスではなく定義したホスト名でアクセスできるようにできるので、docker-compose.yml を以下のように変更します。

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./docker/prometheus/storage:/prometheus
    extra_hosts:
      - "app:${HOST_IP_ADDRESS}"
  • prometheus コンテナに extra_hosts の定義を追加します。PC のホスト名は app にします。

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

- job_name: 'spring-actuator'
  metrics_path: '/actuator/prometheus'
  scrape_interval: 15s
  basic_auth:
    username: actuator
    password: xxxxxxxx
  static_configs:
  # Docker で起動した Prometheus からローカルPCで起動している Spring Boot のアプリケーション
  # にアクセスする。docker-compose.yml の extra_hosts に定義した PC のホスト名 app を設定する。
  - targets: ['app:8080']
    labels:
      application: 'lending'
  • targets: ['172.31.16.1:8080']targets: ['app:8080'] に変更します。

Grafana の Data Source で URL に IP アドレスを記述していましたが、コンテナ名でアクセスできるので prometheus に変更します。

f:id:ksby:20190116072253p:plain

これで Grafana から app:8080 の Instance 名で Spring Boot の Web アプリケーションのメトリックスが見られるようになります。

f:id:ksby:20190116072848p:plain

application.properties を変更する

src/main/resources/application.properties を以下のように変更します。

host.ip.address=172.31.16.1
doma.dialect=org.seasar.doma.jdbc.dialect.PostgresDialect

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

#spring.autoconfigure.exclude=com.integralblue.log4jdbc.spring.Log4jdbcAutoConfiguration
spring.datasource.hikari.jdbc-url=jdbc:postgresql://localhost/ksbylending
spring.datasource.hikari.username=ksbylending_user
spring.datasource.hikari.password=xxxxxxxx
spring.datasource.hikari.driver-class-name=org.postgresql.Driver
spring.datasource.hikari.leak-detection-threshold=60000
spring.datasource.hikari.register-mbeans=true

spring.mail.host=localhost
spring.mail.port=25

spring.redis.cluster.nodes[0]=${host.ip.address}:6379
spring.redis.cluster.nodes[1]=${host.ip.address}:6380
spring.redis.cluster.nodes[2]=${host.ip.address}:6381
spring.redis.cluster.nodes[3]=${host.ip.address}:6382
spring.redis.cluster.nodes[4]=${host.ip.address}:6383
spring.redis.cluster.nodes[5]=${host.ip.address}:6384

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=rabbitmq
spring.rabbitmq.password=12345678

spring.session.store-type=redis

spring.freemarker.cache=true
spring.freemarker.settings.number_format=computer
spring.freemarker.charset=UTF-8
spring.freemarker.enabled=false
spring.freemarker.prefer-file-system-access=false

spring.thymeleaf.mode=HTML

valueshelper.classpath.prefix=

logging.level.root=INFO
logging.level.org.seasar.doma=ERROR
  • host.ip.address=172.31.16.1 を追加します。
  • 以下の設定を追加し、application-develop.properties, application-unittest.properties, application-product.properties から削除します。
    • spring.mail. から始まる設定(2項目)
    • spring.redis. から始まる設定 (6項目)
    • spring.rabbitmq. から始まる設定 (4項目)
  • spring.redis.cluster.nodes の値の IP アドレスの部分を ${host.ip.address} に変更します。

これで IP アドレスが変更された時には .env の HOST_IP_ADDRESS と src/main/resources/application.properties の host.ip.address を変更するだけでよくなりました。

RabbitMQ 用 HAProxy の stats page を表示する

docker/rabbitmq/haproxy.cfg に listen stats の設定を記述していましたが、コピーして書いていただけで何の設定か理解していなかったので stats page を表示できるように設定していませんでした。表示すると RabbitMQ の起動状況が見えるようになるので、必要な設定をします。

まず docker/rabbitmq/haproxy.cfg の設定が少し足りなかったのと変えたい部分があったので以下のように変更します。

listen stats
    bind *:1936
    mode http
    stats enable
    stats hide-version
    stats realm Haproxy\ Statistics
    stats refresh 5s
    stats uri /haproxy?stats
  • stats page を自動更新させたいので、stats refresh 5s の記述を追加し 5秒おきに自動更新されるようにします。
  • stats page であることが分かるよう URI を変更します。stats uri /stats uri /haproxy?stats にします。

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

  haproxy:
    image: haproxy:1.8.14-alpine
    container_name: haproxy
    volumes:
      - ./docker/rabbitmq/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    depends_on:
      - rabbitmq1
      - rabbitmq2
      - rabbitmq3
    ports:
      - "1936:1936"
      - "5672:5672"
      - "15672:15672"
  • haproxy コンテナの設定の ports- "1936:1936" を追加します。

これで docker-compose up -d コマンドでコンテナを起動してから http://localhost:1936/haproxy?stats にアクセスすると HAProxy の stats page が表示されます。

起動直後は全ての rabbitmq が赤色ですが、

f:id:ksby:20190118003345p:plain

起動が認識されると黄色に変わって、

f:id:ksby:20190118003357p:plain

最後に緑色になります。全ての rabbitmq が緑色で表示されれば Spring Boot の Web アプリからアクセスしても問題ありません。

f:id:ksby:20190118003410p:plain

履歴

2019/01/18
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その32 )( Spring Boot を 2.0.6 → 2.0.8 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その31 )( Spring Actuator の Basic 認証用ユーザの認証成功時には AuthenticationSuccessEvent イベントが発生しないようにする+いろいろ調整する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Spring Boot を 2.0.x 系の最新バージョンである 2.0.8 へバージョンアップします。
    • ライブラリも出来るだけ最新バージョンにします。

参照したサイト・書籍

目次

  1. build.gradle を変更する

手順

build.gradle を変更する

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

buildscript {
    ext {
        group "ksbysample"
        version "2.0.8-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.8.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"
}

..........

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:42.2.5"
    def spockVersion = "1.2-groovy-2.5"
    def domaVersion = "2.21.0"
    def lombokVersion = "1.18.4"
    def errorproneVersion = "2.3.1"
    def powermockVersion = "2.0.0"
    def spotbugsVersion = "3.1.10"
  • buildscript block 内で version "2.0.6-RELEASE"version "2.0.8-RELEASE" に変更します。
  • plugins block 内で id "org.springframework.boot" version "2.0.6.RELEASE"id "org.springframework.boot" version "2.0.8.RELEASE" に変更します。
  • dependencies block の以下の点を変更します。
    • def domaVersion = "2.20.0"def domaVersion = "2.21.0"
    • def powermockVersion = "2.0.0-RC.4"def powermockVersion = "2.0.0"

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

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

f:id:ksby:20190112145224p:plain

履歴

2019/01/12
初版発行。

IntelliJ IDEA を 2018.3.2 → 2018.3.3 へバージョンアップ

IntelliJ IDEA を 2018.3.2 → 2018.3.3 へバージョンアップする

IntelliJ IDEA の 2018.3.3 がリリースされているのでバージョンアップします。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。

    f:id:ksby:20190112135103p:plain

  3. Plugin の update も表示されました。このまま「Update and Restart」ボタンをクリックします。

    f:id:ksby:20190112135204p:plain

  4. Patch がダウンロードされて IntelliJ IDEA が再起動します。

  5. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20190112135515p:plain

  6. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2018.3.3 へバージョンアップされていることを確認します。

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

  8. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20190112140952p:plain

  9. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20190112141627p:plain

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その31 )( Spring Actuator の Basic 認証用ユーザの認証成功時には AuthenticationSuccessEvent イベントが発生しないようにする+いろいろ調整する )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その30 )( Redis のクライアントライブラリを Jedis → Lettuce に変更する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Prometheus が Spring Actuator にアクセスした時に、コンソールにレスポンスの内容が出力されて他のログが見にくかったり、Basic 認証の成功時に AuthenticationSuccessEvent が発生して update 文が実行されたりしていたので、修正します。
    • 他に以下の点を変更します。
      • PostgreSQL のコンテナ起動時に毎回検索対象図書館を登録し直さなくてもよいようにします。
      • build タスク実行時に PMD の This analysis could be faster, ... のメッセージが出力されないようにします。
      • HikariCP を最新バージョンに変更して leak-detection-threshold の設定を変更します。
      • spring-boot-properties-migrator が何もメッセージを出力していないので削除します。

参照したサイト・書籍

  1. @EventListener for AuthenticationSuccessEvent or InteractiveAuthenticationSuccessEvent not fired
    https://stackoverflow.com/questions/41076500/eventlistener-for-authenticationsuccessevent-or-interactiveauthenticationsucces

目次

  1. Prometheus が Spring Actuator にアクセスした時に、コンソールにレスポンスの内容が出力されないようにする
  2. Prometheus が Spring Actuator にアクセスした時に AuthenticationSuccessEventListener#onApplicationEvent の update 文が実行されないようにする
  3. library_forsearch テーブルに初期データを登録して、最初に検索対象図書館を登録不要にする
  4. pmdMain タスク実行時の This analysis could be faster, ... のメッセージが出力されないようにする
  5. HikariCP は最新の 3.3.0 を dependenceis に指定し、spring.datasource.hikari.leak-detection-threshold の値を 5000 → 60000 に変更する
  6. runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") を削除する
  7. 次回は。。。

手順

Prometheus が Spring Actuator にアクセスした時に、コンソールにレスポンスの内容が出力されないようにする

src/main/resources/application-develop.properties で logging.level.org.springframework.web=DEBUG を設定しているため、Prometheus が Spring Actuator にアクセスした時のレスポンスの内容もコンソールに出力されます。

f:id:ksby:20190106233612p:plain

このままではコンソールに大量に出力されるため、ログが全然確認できなくて調査等の時に困ったので出ないようにします。org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor の DEBUG ログなので、このクラスだけ log.level を INFO に変更して出力されないようにします。

src/main/resources/application-develop.properties に logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor=INFO を追加します。

# Spring MVC
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor=INFO

Prometheus が Spring Actuator にアクセスした時に AuthenticationSuccessEventListener#onApplicationEvent の update 文が実行されないようにする

Prometheus が Spring Actuator にアクセスした時のレスポンスの内容が出力されないようにしたら、今度は Prometheus が Spring Actuator にアクセスした時に Basic 認証で成功したら ksbysample.webapp.lending.security.AuthenticationSuccessEventListener#onApplicationEvent が呼び出されて update user_info set cnt_badcredentials = 0 where mail_address = 'actuator' 文が実行されていることに気づきました。Spring Actuator の Basic 認証用ユーザの場合には AuthenticationSuccessEvent イベントが発生しないようにします。

f:id:ksby:20190106235221p:plain

src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java の以下の点を変更します。

@Configuration
public class WebSecurityConfig {

    public static final String DEFAULT_SUCCESS_URL = "/booklist";
    public static final String REMEMBERME_KEY = "ksbysample-webapp-lending";
    public static final String ACTUATOR_USERNAME = "actuator";

    ..........

    /**
     * Spring Actuator の Basic認証用ユーザの場合には AuthenticationSuccessEvent を発生させないための
     * AuthenticationEventPublisher
     */
    static class CustomAuthenticationEventPublisher extends DefaultAuthenticationEventPublisher {

        /**
         * コンストラクタ
         *
         * @param applicationEventPublisher {@link ApplicationEventPublisher} オブジェクト
         */
        public CustomAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
            super(applicationEventPublisher);
        }

        /**
         * 認証成功時のメソッド
         * Spring Actuator の Basic認証用ユーザの場合には AuthenticationSuccessEvent を発生させない
         *
         * @param authentication {@link Authentication} オブジェクト
         */
        @Override
        public void publishAuthenticationSuccess(Authentication authentication) {
            if (StringUtils.equals(authentication.getName(), ACTUATOR_USERNAME)) {
                return;
            }
            super.publishAuthenticationSuccess(authentication);
        }

    }

    /**
     * @param auth ???
     * @throws Exception
     */
    @SuppressWarnings("PMD.SignatureDeclareThrowsException")
    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth
            , ApplicationEventPublisher applicationEventPublisher) 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_USERNAME)
                .password("{noop}xxxxxxxx")
                .roles("ENDPOINT_ADMIN");
        auth.authenticationEventPublisher(new CustomAuthenticationEventPublisher(applicationEventPublisher))
                .userDetailsService(userDetailsService);
    }

}
  • public static final String ACTUATOR_USERNAME = "actuator"; を追加し、configAuthentication メソッド内の .withUser("actuator").withUser(ACTUATOR_USERNAME) に変更します。
  • DefaultAuthenticationEventPublisher を継承した CustomAuthenticationEventPublisher クラスを定義し、publishAuthenticationSuccess メソッドで Spring Actuator の Basic認証用ユーザの場合には何もしないようにします。
  • configAuthentication メソッドの以下の点を変更します。
    • 引数に ApplicationEventPublisher applicationEventPublisher を追加します。
    • .authenticationEventPublisher(new CustomAuthenticationEventPublisher(applicationEventPublisher)) を追加します。

これで update 文は実行されないようになります(DispatcherServlet の DEBUG ログだけが出ています)。

f:id:ksby:20190108021639p:plain

library_forsearch テーブルに初期データを登録して、最初に検索対象図書館を登録不要にする

PostgreSQL を Docker で起動して Flyway で初期データを登録するように変更しましたが、library_forsearch テーブルには初期データを登録していなかったため起動直後は検索対象図書館登録画面から図書館を選択する必要がありました。面倒なので初期データを登録するようにします。

f:id:ksby:20190108024127p:plain

src/main/resources/db/migration/V1.1__insert_data.sql を以下のように変更します。

..........
INSERT INTO public.user_role (role_id, user_id, role) VALUES (9, 8, 'ROLE_USER');

INSERT INTO public.library_forsearch (systemid, formal) VALUES ('Tokyo_NDL', '国立国会図書館東京本館');
  • INSERT INTO public.library_forsearch (systemid, formal) VALUES ('Tokyo_NDL', '国立国会図書館東京本館'); を追加します。

これで最初から「国立国会図書館東京本館」が選択された状態になります。

f:id:ksby:20190108030044p:plain

pmdMain タスク実行時の This analysis could be faster, ... のメッセージが出力されないようにする

build タスクを実行した時に pmdMain タスクで This analysis could be faster, please consider using Incremental Analysis: https://pmd.github.io/pmd-6.10.0/pmd_userdocs_incremental_analysis.html のメッセージが出力されますが、出力しないようにする方法が分かったので反映します。

f:id:ksby:20190108033429p:plain

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

pmd {
    toolVersion = "6.10.0"
    sourceSets = [project.sourceSets.main]
    ignoreFailures = true
    consoleOutput = true
    ruleSetFiles = rootProject.files("/config/pmd/pmd-project-rulesets.xml")
    ruleSets = []
}
pmdMain {
    def backupLoggerLevel
    doFirst {
        backupLoggerLevel = logger.context.level
        logger.context.level = LogLevel.QUIET
    }
    doLast {
        logger.context.level = backupLoggerLevel
    }
}
  • pmdMain { ... } の部分を追加します。

build タスクを実行すると以下のように This analysis could be faster, ... のメッセージが出力されません。

f:id:ksby:20190108034709p:plain

また PMD で警告が出る場合は以下のように出力されます(WebSecurityConfig クラスで使用されない import 文が残るように変更して build しています)。

f:id:ksby:20190108035432p:plain

HikariCP は最新の 3.3.0 を dependenceis に指定し、spring.datasource.hikari.leak-detection-threshold の値を 5000 → 60000 に変更する

ここまでの状態で Tomcat を起動していろいろ画面を操作してみたのですが、そこそこの頻度で Connection leak detection triggered for org.postgresql.jdbc.PgConnection@4d36771 on thread http-nio-8080-exec-4, stack trace follows のようなメッセージがコンソールに出力されることに気づきました。現在 5 秒で設定していますが、もう少し長めにするとこのエラーは出なくなるようなので 60秒に変更します。

f:id:ksby:20190109214311p:plain

また依存関係にある HikariCP のバージョンが Spring Boot の 2.0.6 では 2.7.9、2.1.1 だと 3.2.0 なのですが、HikariCP/CHANGES を見ると Changes in 3.0.0update to Micrometer 1.0.0. という記述を見つけたので、最新の 3.3.0 に変更するとにします。

まずは build.gradle に implementation("com.zaxxer:HikariCP:3.3.0") を追加した後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

dependencies {
    ..........
    implementation("org.flywaydb:flyway-core:5.2.4")
    implementation("com.zaxxer:HikariCP:3.3.0")
    testImplementation("org.dbunit:dbunit:2.6.0")
    ..........

src/main/resources/application.properties を以下のように変更します。

#spring.autoconfigure.exclude=com.integralblue.log4jdbc.spring.Log4jdbcAutoConfiguration
spring.datasource.hikari.jdbc-url=jdbc:postgresql://localhost/ksbylending
spring.datasource.hikari.username=ksbylending_user
spring.datasource.hikari.password=xxxxxxxx
spring.datasource.hikari.driver-class-name=org.postgresql.Driver
spring.datasource.hikari.leak-detection-threshold=60000
spring.datasource.hikari.register-mbeans=true
  • spring.datasource.hikari.leak-detection-threshold=5000spring.datasource.hikari.leak-detection-threshold=60000 に変更します。

runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") を削除する

Tomcat 起動時に spring-boot-properties-migrator がメッセージを出力していなかったので、build.gradle の dependencies block から runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") を削除します。削除後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

次回は。。。

Spring Framework の 5.1.4 がリリースされたので、まもなく Spring Boot の 2.0.8 が出るはずです。2.0.8 にバージョンアップしてから jar ファイルを作成→動作確認してから、最後に感想を書いて終わりにします。

履歴

2019/01/11
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その30 )( Redis のクライアントライブラリを Jedis → Lettuce に変更する )

概要

記事一覧はこちらです。

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

  • 今回の手順で確認できるのは以下の内容です。
    • Spring Boot 2.0 Migration Guide - RedisLettuce is now used instead of Jedis as the Redis driver when you use spring-boot-starter-data-redis. という記述があり、spring-boot-starter-data-redis の依存関係に入っている Redis のクライアントライブラリが Jedis から Lettuce になっていることに気づきました。
    • 以前 Redis Cluster への接続処理を Jedis で実装したのですが、Lettuce に変更します。

参照したサイト・書籍

  1. Spring Data Redis
    https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/

  2. Redis Spring data with Lettuce: com.lambdaworks.redis.RedisCommandExecutionException: MOVED error
    https://stackoverflow.com/questions/53402326/redis-spring-data-with-lettuce-com-lambdaworks-redis-rediscommandexecutionexcep

  3. RedisCommandExecutionException: MOVED error when used cluster configuration endpoint
    https://jira.spring.io/browse/DATAREDIS-898

  4. LINE LIVE のチャットが?30,000+/min のコメント投稿を捌くようになるまで
    https://www.slideshare.net/linecorp/line-live-30000min-98811987

  5. Lettuce Reference Guide
    https://lettuce.io/core/release/reference/index.html

目次

  1. spring-boot-starter-data-redis の依存関係に Lettuce が入っていることを確認する
  2. build.gradle を変更する
  3. 単純に JedisConnectionFactory → LettuceConnectionFactory に変更してみる
  4. Lettuce で failover 発生時にサーバの切替が認識されるように実装する
  5. build.gradle に spring-boot-configuration-processor を追加する

手順

spring-boot-starter-data-redis の依存関係に Lettuce が入っていることを確認する

gradlew dependencies コマンドを実行すると spring-boot-starter-data-redis の依存関係に io.lettuce:lettuce-core が入っていることが確認できます。

+--- org.springframework.boot:spring-boot-starter-data-redis -> 2.0.6.RELEASE
|    +--- org.springframework.boot:spring-boot-starter:2.0.6.RELEASE (*)
|    +--- org.springframework.data:spring-data-redis:2.0.11.RELEASE
|    |    +--- org.springframework.data:spring-data-keyvalue:2.0.11.RELEASE
|    |    |    +--- org.springframework.data:spring-data-commons:2.0.11.RELEASE (*)
|    |    |    +--- org.springframework:spring-context:5.0.10.RELEASE (*)
|    |    |    +--- org.springframework:spring-tx:5.0.10.RELEASE (*)
|    |    |    \--- org.slf4j:slf4j-api:1.7.25
|    |    +--- org.springframework:spring-tx:5.0.10.RELEASE (*)
|    |    +--- org.springframework:spring-oxm:5.0.10.RELEASE
|    |    |    +--- org.springframework:spring-beans:5.0.10.RELEASE (*)
|    |    |    \--- org.springframework:spring-core:5.0.10.RELEASE (*)
|    |    +--- org.springframework:spring-aop:5.0.10.RELEASE (*)
|    |    +--- org.springframework:spring-context-support:5.0.10.RELEASE (*)
|    |    \--- org.slf4j:slf4j-api:1.7.25
|    \--- io.lettuce:lettuce-core:5.0.5.RELEASE
|         +--- io.projectreactor:reactor-core:3.1.6.RELEASE -> 3.1.10.RELEASE
|         |    \--- org.reactivestreams:reactive-streams:1.0.2
|         +--- io.netty:netty-common:4.1.24.Final -> 4.1.29.Final
|         +--- io.netty:netty-transport:4.1.24.Final -> 4.1.29.Final
|         |    +--- io.netty:netty-buffer:4.1.29.Final
|         |    |    \--- io.netty:netty-common:4.1.29.Final
|         |    \--- io.netty:netty-resolver:4.1.29.Final
|         |         \--- io.netty:netty-common:4.1.29.Final
|         \--- io.netty:netty-handler:4.1.24.Final -> 4.1.29.Final
|              +--- io.netty:netty-buffer:4.1.29.Final (*)
|              +--- io.netty:netty-transport:4.1.29.Final (*)
|              \--- io.netty:netty-codec:4.1.29.Final
|                   \--- io.netty:netty-transport:4.1.29.Final (*)

build.gradle を変更する

build.gradle の dependencies block から implementation("redis.clients:jedis") を削除します。変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

単純に JedisConnectionFactory → LettuceConnectionFactory に変更してみる

src/main/java/ksbysample/webapp/lending/config/RedisClusterConfig.java を以下のように変更します。

package ksbysample.webapp.lending.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

import java.util.List;

/**
 * Redis Cluster 用 Configuration クラス
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class RedisClusterConfig {

    List<String> nodes;

    /**
     * Redis Cluster に接続するための {@link LettuceConnectionFactory} オブジェクトを生成する
     *
     * @return {@link LettuceConnectionFactory} オブジェクト
     */
    @Bean
    public RedisConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory(new RedisClusterConfiguration(nodes));
    }

}
  • connectionFactory メソッド内で、JedisConnectionFactoryLettuceConnectionFactory に変更します。

動作確認してみます。docker-compose up -d コマンドを実行した後、Tomcat を起動します。

Spring Actuator の health check で Redis に接続できていることを確認します。

f:id:ksby:20190106135844p:plain

docker exec -it redis-cluster-6382 redis-cli コマンドで Redis に接続した後、cluster nodes コマンドを実行してクラスタ構成を確認します。

f:id:ksby:20190106140128p:plain

今は以下の構成になっています。

  • redis-cluster-6379(master) - redis-cluster-6382(slave)
  • redis-cluster-6380(master) - redis-cluster-6383(slave)
  • redis-cluster-6381(master) - redis-cluster-6384(slave)

まず http://localhost:8080/ にアクセスしてログイン画面を表示した後、tanaka.taro@sample.com / taro でログインします。

f:id:ksby:20190106140559p:plain f:id:ksby:20190106140639p:plain

IntelliJ IDEA の Docker Plugin から Redis の master 3台(redis-cluster-6379, redis-cluster-6380, redis-cluster-6381)を停止します。

f:id:ksby:20190106141051p:plain

redis-cli から cluster nodes コマンドを実行して以前の master のステータスが fail になっていること、slave → master に昇格していることを確認します。

f:id:ksby:20190106141240p:plain

ブラウザの右上の「ログアウト」リンクをクリックしてログアウトしてみます。JedisConnectionFactory の時はログアウトできたのですが、JedisConnectionFactoryLettuceConnectionFactory に変更しただけの状態だとエラーになりログアウトできません(Ctrl+F5 を押して何度かリロードしてみましたがずっとエラーのままでした)。

f:id:ksby:20190106141514p:plain

コンソールには org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: io.lettuce.core.RedisConnectionException: Unable to connect to 172.23.136.33:6381 というエラーが出ていて、どうもライブラリの方で master が変わったことが認識できていないようです。

Tomcat を停止し、docker-compose down コマンドを実行してコンテナも停止します。

Lettuce で failover 発生時にサーバの切替が認識されるように実装する

Spring Data Redis の記述だと分からなかったのですが、以下のドキュメントを見るとオプションの設定が必要だったようです。

src/main/java/ksbysample/webapp/lending/config/RedisClusterConfig.java を以下のように変更します。

package ksbysample.webapp.lending.config;

import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

import java.time.Duration;
import java.util.List;

/**
 * Redis Cluster 用 Configuration クラス
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class RedisClusterConfig {

    List<String> nodes;

    /**
     * Redis Cluster に接続するための {@link LettuceConnectionFactory} オブジェクトを生成する
     *
     * @return {@link LettuceConnectionFactory} オブジェクト
     */
    @Bean
    public RedisConnectionFactory connectionFactory() {
        ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                .enablePeriodicRefresh(Duration.ofSeconds(30))
                .enableAllAdaptiveRefreshTriggers()
                .build();
        ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
                .validateClusterNodeMembership(false)
                .topologyRefreshOptions(clusterTopologyRefreshOptions)
                .build();
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .commandTimeout(Duration.ofSeconds(15))
                .shutdownTimeout(Duration.ZERO)
                .clientOptions(clusterClientOptions)
                .build();

        RedisClusterConfiguration serverConfig = new RedisClusterConfiguration(nodes);

        return new LettuceConnectionFactory(serverConfig, clientConfig);
    }

}

動作確認してみます。先程と同じ手順で、docker-compose up -d コマンド実行 → Tomcat 起動 → ログイン画面から tanaka.taro@sample.com / taro でログインする、まで実行します。

cluster nodes コマンドの実行結果は以下のようになっており、構成は先程と同じです。

f:id:ksby:20190106163424p:plain

IntelliJ IDEA の Docker Plugin から Redis の master 3台(redis-cluster-6379, redis-cluster-6380, redis-cluster-6381)を停止します。

redis-cli から cluster nodes コマンドを実行して以前の master のステータスが fail になっていること、slave → master に昇格していることを確認します。

f:id:ksby:20190106163734p:plain

ブラウザの右上の「ログアウト」リンクをクリックすると、今度はログアウトすることができました。

f:id:ksby:20190106163838p:plain

この後ログイン/ログアウトを繰り返したり、Redis の failover を何度も行ってみましたが、エラーは出ませんでした。

build.gradle に spring-boot-configuration-processor を追加する

@ConfigurationProperties を付与すると IntelliJ IDEA では以下のような画面(エディタの上部に Spring Boot Configuration Annotation Processor not found in classpath のメッセージが表示され、@ConfigurationProperties に赤波下線が付く )になります。

f:id:ksby:20190106213918p:plain

build.gradle の dependencies block に compileOnly("org.springframework.boot:spring-boot-configuration-processor") を追記して出ないようにします。

dependencies {
    ..........
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    compileOnly("org.springframework.boot:spring-boot-configuration-processor")
    implementation("org.springframework.session:spring-session-data-redis")
    ..........

Recommend the use of the compileOnly or annotationProcessor configurations for spring-boot-configuration-processor の Issue を見ると compileOnly ではなく annotationProcessor でも良さそうな気がしますが、annotationProcessor だとエディタ上のメッセージが消えず、build 実行時に compileTestJava タスクで 警告:次のオプションはどのプロセッサでも認識されませんでした: '[org.springframework.boot.configurationprocessor.additionalMetadataLocations]' という警告が出るので compileOnly にしました。

履歴

2019/01/06
初版発行。