EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

Spring Bootのサービス稼働環境

弊社では食品の検索に対してXMLで結果を返すRESTfulのAPIを運営しているのですが、今回、機能追加やJSONでの返却を可能にするバージョンアップを計画しています。

今まではJSP+Servletでの実装だったんですが、今回はSpring Bootで実装してみました。 Spring BootではFully Executable JARで簡単にサービス稼働環境を作ることができたので、ここで紹介します。

Fully Executable JARとは

Spring Bootでは、作成したJARファイルをjava -jarで起動することができますが、UNIX環境でより簡単に起動することができる仕組みです。 詳しくは Spring Boot Reference Guide をどうぞ。

JARファイルの作成

APIのプロジェクトはmavenで構成管理をしているのですが、mavenのpluginで簡単にFully Executable JARを作ることができました。

まず、pom.xmlのspring-boot-maven-plugin定義に<executable>true</executable>を追加します。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
+   <configuration>
+       <executable>true</executable>
+   </configuration>
</plugin>

次に、maven packageでビルドします。

$ mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building XXXXXXXX
[INFO] ------------------------------------------------------------------------
...
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ xxxx ---
[INFO] Building jar: /xxx/xxx/target/XXXXXXXX.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.0.3.RELEASE:repackage (default) @ xxxx ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.709 s
[INFO] Finished at: 2018-11-09T13:03:46+09:00
[INFO] Final Memory: 42M/290M
[INFO] ------------------------------------------------------------------------
$

という流れで、targetフォルダ下にJARファイルが作成されます。

起動のしかた

Fully Executable JARができると、起動はそのJARファイル自身を実行するだけです。例えば、JARファイルをapp.jarで作ったとすると、

$ cd target/
$ ./app.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.3.RELEASE)

2018-11-09 13:51:58.709  INFO 11707 --- [           main] xxx.Application           : Starting Application v2.0 on xxx with PID 11707 (app.jar started by xxx in /xxx/xxx/xxx/target)
2018-11-09 13:51:58.721  INFO 11707 --- [           main] xxx.Application           : No active profile set, falling back to default profiles: default
2018-11-09 13:51:58.886  INFO 11707 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@12f40c25: startup date [Fri Nov 09 13:51:58 JST 2018]; root of context hierarchy

という感じで、簡単に起動できます。

全てのライブラリはJARファイル内に含まれていますので、JARファイルを好きなところに配置して起動することができます。 先ほどの公式ドキュメントでは、init.dやsystemdのサービスとして起動する方法も書いてありました。

設定のしかた

Fully Executable JARでは、JARファイルと同じフォルダに、JARと同じ名前の.confファイルを作ることで、起動時の設定を行うことができます。

今回、Spring Bootを使うにあたり、JSP+Servletのシステムと異なるバージョンのJAVAを使用します。 また、アプリケーションサーバーのポート番号や、環境に応じたプロファイルの選択をしたいと思っています。

そのため、app.jarと同じフォルダに、以下の内容のapp.confというファイルを作成しました。

export JAVA_HOME=/usr/java/jre1.8.0_192
export JAVA_OPTS="-Dserver.port=8080 -Dlog4j.configuration=log4j-production.properties"
export SPRING_PROFILES_ACTIVE=production
export MODE="service"

JREは、既存の環境の/usr/javaフォルダ配下に展開だけして、デフォルトでの使用はしないようにしていますが、app.jarの起動時にJAVA_HOMEを指定して使用しています。

アプリケーションサーバーのポートやlog4jのプロパティは、JAVA起動オプションで指定して、Springのプロファイルも環境変数で与えています。

MODEはserviceを指定することで、start,stop等を使い、デーモンとして起動できます。

起動用の様々なオプションはこちら Spring Boot Reference Guide

Spring Bootの様々なオプションはこちら Appendix A. Common application properties

を参考にして下さい。

なぜ動くの?

なぜJARファイル自身を実行して、このようなことができるのでしょうか?

少し調べてみたところ、JARファイル自身の先頭に細工があるようです。

JARファイルの中身を見てみると

$ less app.jar

#!/bin/bash
#
#    .   ____          _            __ _ _
#   /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
#  ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
#   \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
#    '  |____| .__|_| |_|_| |_\__, | / / / /
#   =========|_|==============|___/=/_/_/_/
#   :: Spring Boot Startup Script ::
#

### BEGIN INIT INFO
# Provides:          caloriecheck-api
# Required-Start:    $remote_fs $syslog $network
# Required-Stop:     $remote_fs $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: XXXXXXXX
# Description:       Parent pom providing dependency and plugin management for applications built with Maven
# chkconfig:         2345 99 01
### END INIT INFO

[[ -n "$DEBUG" ]] && set -x

# Initialize variables that cannot be provided by a .conf file
WORKING_DIR="$(pwd)"
# shellcheck disable=SC2153
[[ -n "$JARFILE" ]] && jarfile="$JARFILE"
[[ -n "$APP_NAME" ]] && identity="$APP_NAME"
...

となっており、JARファイルの先頭がサービス起動のシェルスクリプトになっています。 そういえば、JARパッケージ時にspring-boot-maven-plugin:2.0.3.RELEASE:repackageと、何かの処理をされていたようです。

JARファイルというのは、実体はZIPファイル(拡張子を.zipに変更するとunzipできる)なのですが、こちらのブログ( Spring Boot の Fully Executable Jar はなぜ動くのか - RAMBO )によると、ZIPの解凍はファイルの後ろ側から処理をしていくために、先頭にスクリプトを載せても動くようです。

面白いことをするなあと感心しました。