マルチティアWebアーキテクチャの設計において、静的コンテンツと動的アプリケーションロジックの処理を分離することは、システム全体の可用性とリソース効率を向上させるための標準的なアプローチです。Tomcat単体で静的ファイル(HTML、CSS、JavaScript、画像など)を処理させる構成は、JVMのガベージコレクション(GC)のオーバーヘッドを増加させ、スレッドプールを不要に消費する原因となります。
本稿では、フロントエンドのWebサーバーおよびリバースプロキシとしてApache HTTP Server(以下、Apache)を配置し、バックエンドのWebアプリケーションサーバー(WAS)であるApache Tomcat(以下、Tomcat)へ動的リクエストを転送する連携基盤の構築手順を解説します。連携モジュールには、バイナリプロトコルであるAJP(mod_jk)に比べ、設定がシンプルでHTTP/1.1の標準的なセマンティクスを維持しやすいmod_proxy_httpを採用します。
システムトポロジーと検証環境
本構成は、同一ホスト内にApacheとTomcatを共存させるコロケーションモデルを前提としています。マルチノード構成に拡張する場合は、プロキシ転送先のアドレスを対象WASのIPアドレスに適宜変更してください。
- OS: Ubuntu 24.04 LTS
- フロントエンド: Apache HTTP Server (Port 80)
- バックエンド: Apache Tomcat 10.1.59 (Port 8080 / localhost)
Apache HTTP Serverの導入と初期設定
まず、リバースプロキシとなるApacheおよび負荷テスト等で使用するユーティリティパッケージをインストールします。
# パッケージインデックスの更新
sudo apt update
# Apache2およびユーティリティツールのインストール
sudo apt install apache2 apache2-utils -y
# サービスの有効化と起動
sudo systemctl enable --now apache2
インストール完了後、ブラウザまたはcurlコマンドを用いて、Apacheがポート80で正常に待機していることを確認します。
Apache Tomcatの導入とサービス化
Tomcat 10系は動作環境としてJava 11以上を要求します。セキュリティを担保するため、Tomcat専用の非特権システムユーザーを作成した上でバイナリを配置します。
1. Java Runtime Environment (JDK) のインストール
sudo apt install openjdk-11-jdk -y
2. システムユーザーおよびディレクトリの作成
シェルログイン権限を持たないシステムアカウントを作成し、プロセスの実行権限を隔離します。
# インストール先ディレクトリの作成
sudo mkdir -p /opt/tomcat
# ログインシェルを無効化したtomcatユーザーの作成
sudo useradd -r -s /bin/false -d /opt/tomcat tomcat
3. バイナリのダウンロードと権限設定
cd /tmp
wget https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.59/bin/apache-tomcat-10.1.59.tar.gz
# アーカイブを展開し、最上位ディレクトリのラッパーを除去して配置
sudo tar xzvf apache-tomcat-10.1.59.tar.gz -C /opt/tomcat --strip-components=1
# 所有権をtomcatユーザーに変更
sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod +x /opt/tomcat/bin/*.sh
4. systemdサービスユニットの定義
TomcatのライフサイクルをOSのサービス管理下に置くため、/etc/systemd/system/tomcat.serviceを作成します。
[Unit]
Description=Tomcat 10 servlet container
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64"
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_BASE=/opt/tomcat"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
[Install]
WantedBy=multi-user.target
ここでType=forkingを指定することは極めて重要です。Tomcatの起動スクリプト(startup.sh)は、内部でJavaプロセスをバックグラウンドへフォーク(fork)して実行します。もしType=simpleを指定した場合、systemdは起動スクリプトの終了と同時にサービスが停止したと誤認し、プロセスを強制終了してしまいます。
# systemd設定の再読み込みとサービス起動
sudo systemctl daemon-reload
sudo systemctl enable --now tomcat
検証用JSPアーティファクトの作成
プロキシ経由での動的スクリプト実行を検証するため、Tomcatのドキュメントルートにシステム時刻を出力するJSPファイルを配置します。
sudo nano /opt/tomcat/webapps/ROOT/test.jsp
```jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.Date" %>
<html>
<head><title>Integration Test</title></head>
<body>
<h1>Apache & Tomcat Integration Test</h1>
<h3>Current Time: <%= new Date() %></h3>
Request URI: <%= request.getRequestURI() %>
</body>
</html>
mod_proxyによる連携設定
Apacheでリバースプロキシ機能を有効化するには、proxyモジュールおよびHTTPプロトコルを処理するproxy_httpモジュールを有効化する必要があります。
sudo a2enmod proxy proxy_http
次に、/etc/apache2/sites-available/000-default.confを編集し、プロキシ転送ルールを定義します。要件に応じて以下のいずれかの戦略を選択します。
パターンA:グローバルリバースプロキシ(全リクエストを転送)
すべてのクライアントリクエストをバックエンドのTomcatへ透過的に転送する構成です。
<virtualhost *:80="">
ProxyPreserveHost On
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
</virtualhost>
ProxyPreserveHost On: クライアントが送信したオリジナルのHostヘッダーをバックエンドのTomcatに引き渡します。これにより、Tomcat側でのバーチャルホスト判定やURL生成が正しく機能します。
ProxyPass: 特定のパスに対するリクエストを、指定したバックエンドサーバーへマッピングします。ProxyPassReverse: Tomcatが返却するリダイレクト応答(Locationヘッダーなど)に含まれるスキーマやホスト名を、Apache側の公開アドレスに書き換えます。これにより、クライアントが直接ポート8080にリダイレクトされるのを防ぎます。
パターンB:セレクティブルーティング(JSPのみを転送)
静的コンテンツはApacheが直接高速に処理し、.jsp拡張子を持つ動的リクエストのみをTomcatへルーティングする高効率な構成です。
<virtualhost *:80="">
ProxyRequests Off
ProxyPreserveHost On
<locationmatch "\.jsp$"="">
ProxyPass http://localhost:8080
ProxyPassReverse http://localhost:8080
</locationmatch>
</virtualhost>
ProxyRequests Off: オープンプロキシ(フォワードプロキシ)としての動作を明示的に無効化し、セキュリティ脆弱性を排除します。
<locationmatch "\.jsp$"="">: 正規表現を用いて、末尾が.jspで終わるURIのみをプロキシ転送の対象とします。
設定変更後、構文チェックを実行し、Apacheを再起動します。
# 設定ファイルの構文チェック
sudo apache2ctl configtest
# サービスの再起動
sudo systemctl restart apache2
ライフサイクルとゼロダウンタイムへの配慮
本番環境においてTomcatのローリングアップデートやコンテナの再配置を行う際、単純にTomcatプロセスを停止すると、Apache側で一時的に503 Service Unavailableが発生します。これを回避するためには、以下の設計を考慮する必要があります。
- 接続プーリングとタイムアウトの最適化:
ProxyPassディレクティブにconnectiontimeoutやtimeoutパラメータを明示的に設定し、バックエンドの無応答時に迅速にフェイルオーバーさせます。 - ドレイン(Draining)状態の活用: ロードバランサー配下に複数のTomcatノードを配置している場合、対象ノードへの新規セッション割り当てを停止(ドレイン)し、既存セッションが終了した段階で安全にアップデートを実施します。
Troubleshooting
1. ポート競合によるTomcatの起動失敗
すでに他のプロセスがポート8080を占有している場合、Tomcatは起動に失敗します。この場合、/opt/tomcat/conf/server.xmlを開き、<Connector>要素のport属性を競合しないポート(例: 8081)に変更してください。
<connector connectiontimeout="20000" port="8081" protocol="HTTP/1.1" redirectport="8443"></connector>
2. パーミッションエラーによるアクセス拒否
Tomcatの実行ユーザー(tomcat)に、/opt/tomcat配下のファイル読み込み・実行権限が正しく付与されていない場合、起動スクリプトがエラーを吐き出します。ディレクトリの所有権がtomcat:tomcatになっているか再確認してください。
sudo chown -R tomcat:tomcat /opt/tomcat
3. SELinux / AppArmorによる接続拒否
セキュリティモジュールが有効な環境では、Apacheからローカルポートへのアウトバウンド通信がブロックされることがあります。SELinux環境下でプロキシ通信を許可するには、以下のブール値を有効化します。
sudo setsebool -P httpd_can_network_connect 1
稼働状態の検証ログ
システムが正常に動作している場合の各種コマンドの出力プロトコル例を以下に示します。
ポートバインド状況の確認 (ss -tulpn)
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=1024,fd=4))
tcp LISTEN 0 100 127.0.0.1:8005 0.0.0.0:* users:(("java",pid=2048,fd=15))
tcp LISTEN 0 100 *:8080 *:* users:(("java",pid=2048,fd=12))
Tomcatサービスステータスの確認 (systemctl status tomcat)
● tomcat.service - Tomcat 10 servlet container
Loaded: loaded (/etc/systemd/system/tomcat.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-09-23 10:00:00 UTC; 5m ago
Process: 2040 ExecStart=/opt/tomcat/bin/startup.sh (code=exited, status=0/SUCCESS)
Main PID: 2048 (java)
Tasks: 32 (limit: 4615)
Memory: 142.5M
CPU: 1.82s
CGroup: /system.slice/tomcat.service
└─2048 /usr/lib/jvm/java-11-openjdk-amd64/bin/java -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties ...
HTTPレスポンスヘッダーの検証 (curl -I)
HTTP/1.1 200 OK
Date: Wed, 23 Sep 2026 10:05:00 GMT
Server: Apache/2.4.58 (Ubuntu)
Content-Type: text/html;charset=UTF-8
Content-Length: 245
Connection: keep-alive
⚠️ 注意: ServerヘッダーにApacheが表示されつつ、Content-TypeにTomcat側で処理されたcharset=UTF-8が返却されていることから、プロキシ連携が正常に機能していることが実証されます。
Operational Notes
- 本番環境におけるテストファイルの削除: 本番環境へのデプロイ前に、システム情報やパス構造を露呈させるリスクのある
test.jspなどの検証用スクリプトは必ず削除してください。 - タイムアウト値のチューニング: クライアントとApache間、およびApacheとTomcat間のタイムアウト値(
KeepAliveTimeout,ProxyTimeout)を整合させることで、不要なゾンビコネクションの滞留を防ぎ、リソースの枯渇を防止できます。