はじめに
今更感があるのですが、Apache HTTPd + Tomcatの連携を実装する必要があり、Docker上で実装してみることにしてみました。なお、ここで設定している内容は動作するための最小設定であるため、足りない設定などは適宜補足してください。
アプリの実装
Tomcat上で動くJavaアプリを作成します。あまり手間をかけたくなかったので、Spring Bootで以下のサンプルアプリを使って実装します。
warファイルとしてパッケージ化したかったので、Spring InitializrでPackagingにwarを指定します。
これだけで、必要な設定がなされたファイル群が生成されます。
あとは実装して./gradlew war
としてwarファイルを生成、webapps
フォルダ以下にapp.war
としてコピーしておきます。
設定ファイルの生成
Apache HTTPdとTomcatの設定ファイルを生成します。ネット上には設定ファイルの断片しか記述されていないことが多く、それだけをコピペするとうまく動かないです。
今回はhttpd:2.4.57-alpine
とtomcat:10.1.11-jre17
を使うことにしたので、以下のコマンドを実行します。
$ docker run --rm httpd:2.4.57-alpine cat /usr/local/apache2/conf/httpd.conf > my-httpd.conf $ docker run --rm tomcat:10.1.11-jre17 cat /usr/local/tomcat/conf/server.xml > my-server.xml
設定ファイルの編集(Apache HTTPd)
上記で生成した設定ファイルを編集します。まずはApache HTTPd。以下のLoadModule
の部分を有効化(先頭の#
を削除)します。
LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
また、以下の通りVirtualHostの設定を追記します。ここでは、/app
にアクセスがあったらAJPでTomcatに対して処理を流すことを設定しています。
<VirtualHost *:80> ProxyPass /app ajp://tomcat:8009/app </VirtualHost>
記述内にあるtomcat
は後述するdocker-compose.yml内のservices
内にあるTomcatの設定名と同じにします。
編集したファイルをconf/httpd.conf
として保存します。
設定ファイルの編集(Tomcat)
上記で生成した設定ファイルを編集します。ここではTomcat。以下の記述を追記します。
<Connector protocol="AJP/1.3" secretRequired="false" address="0.0.0.0" port="8009" redirectPort="8080" maxParameterCount="1000" />
もともと<Connector protocol="AJP/1.3"
で始まる部分のコメントを有効化するだけではなく、address
の部分を"0.0.0.0"
としておく必要があります。これをしないと起動時に以下の例外を出力します。
13-Aug-2023 04:56:03.128 SEVERE [main] org.apache.catalina.util.LifecycleBase.handleSubClassException Failed to initialize component [Connector[AJP/1.3-8009]] org.apache.catalina.LifecycleException: Protocol handler initialization failed at org.apache.catalina.connector.Connector.initInternal(Connector.java:1015) at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136) at org.apache.catalina.core.StandardService.initInternal(StandardService.java:549) at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136) at org.apache.catalina.core.StandardServer.initInternal(StandardServer.java:1011) at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136) at org.apache.catalina.startup.Catalina.load(Catalina.java:747) at org.apache.catalina.startup.Catalina.load(Catalina.java:769) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.base/java.lang.reflect.Method.invoke(Unknown Source) at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:307) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:477) Caused by: java.nio.channels.UnsupportedAddressTypeException at java.base/sun.nio.ch.Net.checkAddress(Unknown Source) at java.base/sun.nio.ch.ServerSocketChannelImpl.netBind(Unknown Source) at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(Unknown Source) at org.apache.tomcat.util.net.NioEndpoint.initServerSocket(NioEndpoint.java:247) at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:202) at org.apache.tomcat.util.net.AbstractEndpoint.bindWithCleanup(AbstractEndpoint.java:1278) at org.apache.tomcat.util.net.AbstractEndpoint.init(AbstractEndpoint.java:1291) at org.apache.coyote.AbstractProtocol.init(AbstractProtocol.java:622) at org.apache.catalina.connector.Connector.initInternal(Connector.java:1013) ... 13 more
また、secretRequired="false"
も必要です。これがないと起動時に以下の例外が出力されます。
13-Aug-2023 08:01:29.453 SEVERE [main] org.apache.catalina.util.LifecycleBase.handleSubClassException Failed to start component [Connector[AJP/1.3-8009]] org.apache.catalina.LifecycleException: Protocol handler start failed at org.apache.catalina.connector.Connector.startInternal(Connector.java:1046) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.core.StandardService.startInternal(StandardService.java:445) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:918) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.startup.Catalina.start(Catalina.java:795) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.base/java.lang.reflect.Method.invoke(Unknown Source) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:347) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:478) Caused by: java.lang.IllegalArgumentException: The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "". This combination is not valid. at org.apache.coyote.ajp.AbstractAjpProtocol.start(AbstractAjpProtocol.java:271) at org.apache.catalina.connector.Connector.startInternal(Connector.java:1043) ... 12 more
最後に標準で開いている8080ポートを閉じます。以下の記述全体を<!--
と-->
で囲みます。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxParameterCount="1000" />
結果として、以下のようなものとなります。
<!-- <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxParameterCount="1000" /> -->
編集したファイルをconf/server.xml
として保存します。
docker-compose.ymlファイルを作成する
docker-compose.yml
ファイルを作成します。内容は以下の通り。
version: '3' services: httpd: image: httpd:2.4.57-alpine ports: - 8080:80 volumes: - ./conf/httpd.conf:/usr/local/apache2/conf/httpd.conf tomcat: image: tomcat:10.1.11-jre17 ports: - 8009 volumes: - ./webapps:/usr/local/tomcat/webapps - ./conf/server.xml:/usr/local/tomcat/conf/server.xml
これで準備はOK。あとはdocker compose up
を実行し、ブラウザでhttp://localhost:8080/app
にアクセスしたら、Tomcat上で動いているアプリからの応答が確認できます。