DockerでApache＋Tomcat構成を作る！

## 概要
Dockerを使って、ApacheとTomcatを分離したコンテナ構成を作成し、JavaのWebアプリ（WARファイル）をTomcatにデプロイして、Apache経由で動かすまでの手順をまとめました。

## メリット
オンプレ構成に沿うと、Apacheが入っている所謂WebサーバーとTomcatが入っているAPサーバーで分けることがあると思います。
その点コンテナにすれば1台で済むのが一つメリットです。
その他メリットを挙げるとこのようになります。

| 項目 | サーバーを2台に分けた構成 | Dockerコンテナ構成 |
|:-----------|------------:|:------------:|
| コスト       | サーバーを2台分        | サーバーは1台         |
| 環境構築の手間     | 各サーバーに手動インストール      | Dockerfile と docker-compose.yml で自動構築       |
| 通信       | サーバー間のセキュリティ担保        | 同一ホスト内で localhost 通信         |
| スケーラビリティ       | インスタンスごとの個別スケール        | サービス単位で水平スケールしやすい         |
| 再現性         | 手動構築でブレやすい          | どこでも同じコンテナが動作           |
| 障害切り分け       | ネットワークやサーバー自体の問題も絡む       | コンテナ単位でログ・原因が明確       |
| メンテナンス    | OSごとアップデート・管理     | コンテナ単位で最小構成＆短時間で更新      |

## 環境
・OS：Amazon Linux 2 または RHEL系（今回はパブリックIPを持つEC2インスタンス）
・Docker / Docker Compose v2
・Java Webアプリを .war 形式でデプロイ
・サーブレット + JSP（最低限のサンプル）

### ディレクトリ構成

apache-tomcat-docker/
├── apache/
│ 　　　  ├── Dockerfile
│ 　　　  └── httpd.conf
├── tomcat/
│ 　　　  ├── Dockerfile
│ 　　　  └── webapps/
│  　　　   　　　　　  └── app.war（←ここに自作WAR）
├── docker-compose.yml

## 手順

まずはディレクトリ作成を行います。

```
mkdir apache-tomcat-docker
cd apache-tomcat-docker
mkdir apache tomcat
mkdir -p tomcat/webapps
```

続いて必要なファイルを作成していきます。

「**vi apache/Dockerfile**」で以下ファイルを作成。

### 🐳 Apache コンテナ

```Dockerfile
FROM httpd:2.4
COPY httpd.conf /usr/local/apache2/conf/httpd.conf
```

「**vi tomcat/Dockerfile**」で以下ファイルを作成。

### 🐱 Tomcat コンテナ

```Dockerfile
FROM tomcat:9.0
COPY webapps/ /usr/local/tomcat/webapps/
```

**Dockerfile**でそれぞれのコンテナで何を使うかを定義しています。
Apacheは自身のhttpd.conf(Dockerfileが置いてある場所と同じ場所にあるファイル)をコンテナ内にコピーして使用します。

Tomcatは同じく自身のwarファイル（今回でいうapp.war）をコンテナ内にコピーして使用します。

次に、それぞれのコンテナで使用するポートやネットワークを定義した
**docker-compose.yml**を作成します。

### ⚙️ docker-compose.yml
```yaml
services:
  apache:
    build: ./apache
    ports:
      - "80:80"
    depends_on:
      - tomcat
    networks:
      - webnet

  tomcat:
    build: ./tomcat
    ports:
      - "8080:8080"
    networks:
      - webnet
networks:
  webnet:
```
以下の記載は、/apache配下のDockerfileを指定してコンテナを作成するという意味です。
```yaml
    build: ./apache
```
以下の記載は、tomcat用コンテナが作成されるのを待ってから作成するという意味です。
```yaml
    depends_on:
      - tomcat
```
以下はネットワークにwebnetを指定しています。
```yaml
    networks:
      - webnet
```
前述で指定しているネットワークはここで定義しており、apacheとtomcatのそれぞれのコンテナにこのネットワークを指定することで、ローカル通信を可能にしています。
```yaml
networks:
  webnet:
```

次に、Apache用のDockerfileに書いた、httpd.confを準備します。

```apache
ServerRoot "/usr/local/apache2"
Listen 80
ServerName localhost

# --- Load 必須モジュール ---
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule mime_module modules/mod_mime.so
LoadModule dir_module modules/mod_dir.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_core_module modules/mod_authz_core.so

# --- Proxy設定 ---
ProxyPreserveHost On
ProxyPass /app http://tomcat:8080/app
ProxyPassReverse /app http://tomcat:8080/app

DocumentRoot "/usr/local/apache2/htdocs"
<Directory "/usr/local/apache2/htdocs">
    AllowOverride None
    Require all granted
</Directory>
```

Dockerfile内で、「**/usr/local/apache2**」にファイルコピーをしているので、
ServerRootには同様のパスを記載します。

また、**docker-compose.yml**にてポート80を指定しているので、httpd.confのListenにも80を指定します。

モジュールは最低限必要なものを入れています。

プロキシ設定に関しては以下です。
**ProxyPreserveHost On**＝元のHostヘッダをそのまま保持し、バックエンド（Tomcat）に渡す
**ProxyPass /app http://tomcat:8080/app** = Apacheに来た /app パスへのリクエストを、Tomcat の http://tomcat:8080/app に転送する設定
**ProxyPassReverse /app http://tomcat:8080/app** = Tomcatから返された レスポンスの中に含まれるリダイレクトURLなどを、Apache経由のURLに書き換える ための設定

その下の記述の**DocumentRoot**は、外から例　http://サーバーIP/ でアクセスされたときにApacheが参照する場所
**AllowOverride None**は、.htaccess ファイルによる設定の「上書き」を許可しない設定
**Require all granted**は、全てのアクセス元に対して許可する設定（接続元IPを絞ることもできる）

あとはwarファイルの作成です。

## ☕ WARファイルの作成
内容は以下です。

app/
├── index.jsp
└── WEB-INF/
　　　　　    ├── web.xml
　　　　　    └── classes/
  　　　　　　　　　      └── HelloServlet.class

warファイルに関しては作成したことがなかったので生成AIに作ってもらってます。
中身はあまり分かっていないので解説は割愛します。

まずはディレクトリ作成をします。
```
mkdir -p app/WEB-INF/classes
cd app
```

続いてファイル作成をしていきます。

**vi index.jsp**
```jsp
<!-- index.jsp -->
<html>
  <body>
    <h1>Hello from JSP!</h1>
    <a href="hello">Servletへ</a>
  </body>
</html>
```

**vi WEB-INF/web.xml**
```xml
<!-- WEB-INF/web.xml -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="3.1">
  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>HelloServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
</web-app>
```

**vi HelloServlet.java**
```java
// HelloServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloServlet extends HttpServlet {
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<h1>Hello from Servlet!</h1>");
  }
}
```

コンパイルをするためにjavacコマンドを使用するため、JDKバイナリをインストールします。

```
cd /opt
curl -LO https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.10+7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.10_7.tar.gz

tar -xvf OpenJDK17U-jdk_x64_linux_hotspot_17.0.10_7.tar.gz
mv jdk-17.0.10+7 temurin-17
```

パスを通すため以下ファイルを作成します。

**vi /etc/profile.d/java.sh**
```
tee /etc/profile.d/java.sh > /dev/null <<'EOF'
export JAVA_HOME=/opt/temurin-17
export PATH=$JAVA_HOME/bin:$PATH
EOF

source /etc/profile.d/java.sh
```

「source /etc/profile.d/java.sh」を打って反映

それぞれコマンドが通ることを確認
```
java -version
javac -version
```

cd <HelloServlet.javaが存在しているディレクトリ（/app配下）>

コンパイルを行います。
```
javac -classpath ~/lib/javax.servlet-api-4.0.1.jar HelloServlet.java
mv HelloServlet.class WEB-INF/classes/
```

これでwarファイルを作成する準備が整ったので、warファイルを作成します。

```
cd ..
jar cvf app.war -C app .
## warファイル移動
mv app.war tomcat/webapps/
```

**Dockerを使用するために、必要なパッケージをインストールします。**

```
# Dockerインストール
dnf install -y docker
systemctl enable docker
systemctl start docker

# docker-composeインストール
mkdir -p $HOME/.docker/cli-plugins
curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 -o $HOME/.docker/cli-plugins/docker-compose
chmod +x $HOME/.docker/cli-plugins/docker-compose
```

docker-composeが使用できることを確認。
```
docker compose version
```

コンテナイメージをビルドして起動します。
```
docker compose up --build -d

## 確認
docker compose ps
```
以下のように2つコンテナが起動していれば成功です。

```
NAME                            IMAGE                         COMMAND              SERVICE   CREATED         STATUS         PORTS
apache-tomcat-docker-apache-1   apache-tomcat-docker-apache   "httpd-foreground"   apache    4 seconds ago   Up 3 seconds   0.0.0.0:80->80/tcp, [::]:80->80/tcp
apache-tomcat-docker-tomcat-1   apache-tomcat-docker-tomcat   "catalina.sh run"    tomcat    4 seconds ago   Up 4 seconds   0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp
```
起動に失敗する場合は、
「docker compose logs <サービス>」で原因が見れます。

以下でTomcatへの疎通確認を行います。
```
## Apache⇒Tomcat
curl http://<EC2のパブリックIP>/app/

## Apache⇒Tomcatのサーブレット
curl http://<EC2のパブリックIP>/app/hello
```
Tomcatへの疎通は以下が返ってこれば成功です。
```
<!-- index.jsp -->
<html>
  <body>
    <h1>Hello from JSP!</h1>
    <a href="hello">Servletへ</a>
  </body>
</html>
```

サーブレットへの疎通は以下が返ってこれば成功です。
```
<h1>Hello from Servlet!</h1>
```

## まとめ
warファイルは生成AIに言われるがまま設定しているので中身はほとんど分かっていませんが、
それでもApache⇒Tomcatへの疎通確認は行うことが出来ました。

コンテナを活用した構築はどんどんチャレンジしていきたいと思います。
この記事が誰かの助けになれば幸いです。

以上！！