EatSmartシステム部ブログ

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

tomcat上のwebアプリケーションをコンテナ化する

アプリケーションの稼働環境をdockerコンテナ化することは、環境の可搬性やネットワークの自由度などとても有用なので、弊社のサービスをコンテナ化しています。

先日、tomcatで動作しているwebアプリケーションをコンテナ化した際にやったことをブログに残したいと思います。

tomcatのwebアプリをコンテナに入れるにあたり、以下のことを行いました。

  1. mavenで構成管理する
  2. tomcat-maven-pluginでwebアプリを実行する
  3. tomcat-maven-pluginで実行可能jarを作成する
  4. 実行可能jarを入れたコンテナを作成する

mavenで構成管理する

もともとビルドのタスク実行はantを使用していたのですが、ライブラリの依存性管理や開発環境構築の利便性のために、mavenで構成管理をすることにしました。

ただ、単純にtomcatのWEB-INF/libフォルダ配下にjarファイルを置いて、全てをクラスパスに追加すれば良かった(もっともそのせいで、ライブラリ同士の依存関係が全く分からなくなっていたのですが…)のが、全てをpom.xmlに定義が必要となりました。

依存関係が管理されていなかったのでバージョンが不明だったり、全てのdependencyを記述するのが大変だったので、

<dependency>
    <groupId>jai_codec.jar</groupId>
    <artifactId>jai_codec.jar</artifactId>
    <version>1.0</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/WEB-INF/lib/jai_codec.jar</systemPath>
</dependency>

のように、元々のWEB-INF/lib配下のjarにsystemスコープで定義しているところもあります。 これでは依存性の解決にはなっていませんが…。

tomcat-maven-pluginでwebアプリを実行する

mavenで構成管理を行うようにしたので、tomcat-maven-plugin(Apache Tomcat Maven Plugin - About Apache Tomcat Maven Plugin)を使ってtomcatmavenで起動するようにしました。

今回のアプリではDB接続にJNDIを使用しているのですが、JNDI定義はtomcatのcontextファイルに記述しています。 pluginの定義でも指定のcontextファイルを使用するように、configurationのcontextFileで指定します。

また、古いtomcatの頃の名残で、JSPスクリプトレット内でエスケープ無しにダブルクォートしていたり(Tomcat5.5.27、6.0.18からJSPのスクリプトレットなどの中でクォートを使用する際にエスケープが必要になった - 不会忘記的一天)するので、STRICT_QUOTE_ESCAPINGをfalseに設定します。

結果、pom.xmlに以下の定義を追加しました。 pluginのバージョンが2.1の理由は後述します。

<plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.1</version>
    <configuration>
        <contextFile>${project.basedir}/context.xml</contextFile>
        <systemProperties>
            <org.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING>false</org.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING>
        </systemProperties>
    </configuration>
</plugin>

これで、mvn tomcat7:runで起動するようになりました。

$ mvn tomcat7:run

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building XXXXXXXXXXXXXXXXXx
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> tomcat7-maven-plugin:2.1:run (default-cli) > process-classes @ XXXXXXXX >>>
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ XXXXXXXX ---
・・・
・・・
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ XXXXXXXX ---
[INFO] Changes detected - recompiling the module!
・・・
・・・
[INFO] <<< tomcat7-maven-plugin:2.1:run (default-cli) < process-classes @ XXXXXXXX <<<
[INFO]
[INFO]
[INFO] --- tomcat7-maven-plugin:2.1:run (default-cli) @ XXXXXXXX ---
[INFO] Running war on http://localhost:8080/XXXXXXXX
[INFO] Creating Tomcat server configuration at /XXXXXXXX/XXXXXXXX/target/tomcat
[INFO] create webapp with contextPath: /XXXXXXXX
8 02, 2018 7:30:37 午後 org.apache.coyote.AbstractProtocol init
情報: Initializing ProtocolHandler ["http-bio-8080"]
8 02, 2018 7:30:37 午後 org.apache.catalina.core.StandardService startInternal
情報: Starting service Tomcat
8 02, 2018 7:30:37 午後 org.apache.catalina.core.StandardEngine startInternal
情報: Starting Servlet Engine: Apache Tomcat/7.0.37
8 02, 2018 7:30:41 午後 org.apache.catalina.core.ApplicationContext log

tomcat-maven-pluginで実行可能jarを作成する

tomcat-maven-pluginを使用して、実行可能jarを作る事ができます。コンテナ構築がjarファイルのコピーで済むと簡単で良いので、実行可能jarを作ってみます。

まず、pluginの定義にexec-war-onlyゴールを追加します。 また、実行するtomcatでJNDIを使うためにはenableNamingの設定が必要なようです(Apache Tomcat Maven Plugin :: Tomcat 7.x - tomcat7:standalone-war)。他にも色々と設定ができるようなので、参考にしてみて下さい。

ここで、以下のようにpluginのバージョン2.2、2.3にはexec-war-onlyが正常に動作しないとのことで、2.1を使用しました。

で、最終的に、pluginの設定は以下のようになりました。

<plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.1</version>
    <configuration>
        <contextFile>${project.basedir}/context.xml</contextFile>
    </configuration>
    <executions>
        <execution>
            <id>tomcat-run</id>
            <goals>
                <goal>exec-war-only</goal>
            </goals>
            <phase>package</phase>
            <configuration>
                <path>XXXXX[作成するJARの名前]</path>
                <enableNaming>true</enableNaming>
            </configuration>
        </execution>
    </executions>
</plugin>

ここでmvn packageコマンドを実行すると

$ mvn package

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building XXXXXXXX
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ XXXXXXXX ---
[INFO] Copying 1 resource
・・・
・・・
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ XXXXXXXX ---
[INFO] Changes detected - recompiling the module!
・・・
・・・
[INFO] --- maven-war-plugin:2.2:war (default-war) @ XXXXXXXX ---
[INFO] Packaging webapp
[INFO] Assembling webapp [XXXXXXXX] in [/XXXXXXXX/XXXXXXXX/target/XXXXXXXX]
[INFO] Processing war project
[INFO] Copying webapp resources [/XXXXXXXX/XXXXXXXX/src/main/webapp]
[INFO] Webapp assembled in [3288 msecs]
[INFO] Building war: /XXXXXXXX/XXXXXXXX/target/XXXXXXXX.war
[INFO] WEB-INF/web.xml already added, skipping
[INFO]
[INFO] --- tomcat7-maven-plugin:2.1:exec-war-only (tomcat-run) @ XXXXXXXX ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 15.023 s
[INFO] Finished at: 2018-08-02T19:58:47+09:00
[INFO] Final Memory: 49M/275M
[INFO] ------------------------------------------------------------------------

とjarの作成が成功しました。

さらにtargetフォルダの下にできたjarファイルを実行することで

$ java -jar ./target/XXXXXXXX-war-exec.jar

8 02, 2018 8:00:11 午後 org.apache.catalina.core.StandardContext setPath
8 02, 2018 8:00:11 午後 org.apache.coyote.AbstractProtocol init
情報: Initializing ProtocolHandler ["http-bio-8080"]
8 02, 2018 8:00:11 午後 org.apache.catalina.core.StandardService startInternal
情報: Starting service Tomcat
8 02, 2018 8:00:11 午後 org.apache.catalina.core.StandardEngine startInternal
情報: Starting Servlet Engine: Apache Tomcat/7.0.37
8 02, 2018 8:00:18 午後 org.apache.catalina.core.ApplicationContext log
情報: No Spring WebApplicationInitializer types detected on classpath

と、tomcatを使ってwebアプリを起動することができました。

実行可能jarを入れたコンテナを作成する

あとは、javaが動くコンテナ内にjarを入れれば良いのですが、今回は接続先DB等を外部化するために、起動時にパラメータでserver.xmlを指定するようにしてみました。

server.xmlのパスは、javaコマンドで起動時に-serverXmlPathで指定することができます(Apache Tomcat Maven Plugin - Executable War )。

#ビルド時にpom.xmlのserverXml項目で指定することもできます(Apache Tomcat Maven Plugin :: Tomcat 7.x - tomcat7:standalone-war)。

今回は、jarファイル、JNDIのDB定義をしたserver.xmlファイル、コンテナ起動時にtomcatを起動するスクリプトを、コンテナ内の同一フォルダに配置するようにしました。

起動スクリプトは、startup.shという名前で

#!/bin/sh
DIR=$1
cd $DIR
java -Xmx1024m -jar $DIR/XXXXXXXX-war-exec.jar -serverXmlPath $DIR/server.xml

というファイルを用意し、以下の内容のDockerfileを作りました。

・・・
・・・
 
RUN mkdir -p /usr/local/XXXXXXXX
ADD ./startup.sh /usr/local/XXXXXXXX/
ADD ./server.xml /usr/local/XXXXXXXX/
ADD ./XXXXXXXX-war-exec.jar /usr/local/XXXXXXXX/
 
EXPOSE 8080/tcp
 
CMD ["/usr/local/XXXXXXXX/startup.sh","/usr/local/XXXXXXXX"]

あとは、コンテナをbuildして、

$ docker build -t esma/XXXXXXXX ./

Sending build context to Docker daemon  39.85MB
・・・
・・・
Removing intermediate container 81eaa50676f6
 ---> aa9795082fde
Step 3/8 : RUN mkdir -p /usr/local/XXXXXXXX
 ---> Running in 0b34a06c54bd
Removing intermediate container 0b34a06c54bd
 ---> 2d4acc4a17a0
Step 4/8 : ADD ./startup.sh /usr/local/XXXXXXXX/
 ---> affd7178601c
Step 5/8 : ADD ./server.xml /usr/local/XXXXXXXX/
 ---> 836ca962776d
Step 6/8 : ADD ./XXXXXXXX-war-exec.jar /usr/local/XXXXXXXX/
 ---> b1477fde89e0
Step 7/8 : EXPOSE 8080/tcp
 ---> Running in 5c77492af7da
Removing intermediate container 5c77492af7da
 ---> 3e656de10f13
Step 8/8 : CMD ["/usr/local/XXXXXXXX/startup.sh","/usr/local/XXXXXXXX"]
 ---> Running in a61e2cf508ca
Removing intermediate container a61e2cf508ca
 ---> 2c7c01b9d549
Successfully built 2c7c01b9d549
Successfully tagged esma/XXXXXXXX:latest

そして、実行すると

$ docker run -d --name=XXXXXXXX -p 8080:8080 -it esma/XXXXXXXX

aef000541e0795693610849743dc0b97835d5b54b9d3de26e31e606eb9bdbc86

$ docker logs XXXXXXXX

Aug 02, 2018 3:16:32 PM org.apache.catalina.core.AprLifecycleListener init
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: /usr/java/packages/lib/amd64:/usr/lib/x86_64-linux-gnu/jni:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/usr/lib/jni:/lib:/usr/lib
Aug 02, 2018 3:16:33 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-8080"]
Aug 02, 2018 3:16:33 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["ajp-bio-8009"]
Aug 02, 2018 3:16:33 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 1104 ms
・・・
・・・
Aug 02, 2018 3:16:37 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'dispatcherServlet'
Aug 02, 2018 3:16:39 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8080"]
Aug 02, 2018 3:16:39 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["ajp-bio-8009"]
Aug 02, 2018 3:16:39 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 6363 ms
Aug 02, 2018 3:16:49 PM org.apache.naming.NamingContext lookup

と、無事コンテナ内でwebアプリを起動することができました。