tomcat上のwebアプリケーションをコンテナ化する
アプリケーションの稼働環境をdockerコンテナ化することは、環境の可搬性やネットワークの自由度などとても有用なので、弊社のサービスをコンテナ化しています。
先日、tomcatで動作しているwebアプリケーションをコンテナ化した際にやったことをブログに残したいと思います。
tomcatのwebアプリをコンテナに入れるにあたり、以下のことを行いました。
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)を使ってtomcatもmavenで起動するようにしました。
今回のアプリでは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を使用しました。
- [MTOMCAT-263] tomcat7:exec-war can't create .extract/webapps - ASF JIRA
- java - maven-tomcat7-plugin produces corrupted executable JAR - Stack Overflow
で、最終的に、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アプリを起動することができました。