かんがるーさんの日記

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

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
初版発行。