CentOS8.2でhttpdとTomcat9を連携させる手順

| [../](https://qiita.com/taconana/items/e45f19e83c825a9ba75e) |
|---|

VPSサーバー（CentOS8.2）にTomcat9をインストールしている。インターネットからhttp(80ポート)やhttps(443ポート)で受けたリクエストを、Tomcat9で動的に制御できるようにしたい。AJPプロトコル(Apache Jserv Protocol)の実装であるmod_proxy_ajpを利用したい。その手順をメモしておく。

Tomcat9では、初期設定で、8080ポートで HTTP 1.1 の通信を受け付け、8009ポートで AJP 1.3 の通信を受け付けるようになっている。私の環境では、80ポートや443ポートで受け付けた通信は、バーチャルホストを使ってサイトごとに制御し、動的サイトはすべてTomcat9で処理するようにしようと考えている。例えば、kankeri.comはLet's Encyptでサーバー証明し、xxx.comはプライベートのサーバー証明とする。サーバー認証は、httpd側で行い、Tomcat9側では行わないようにする。

- kankeri.com/ --> ようこそkankeriへ (Let's Encypt証明)
- xxx.com/ --> ようこそxxxへ (プライベートサーバー証明)

## バージョンの確認

```bash

$ httpd -version
Server version: Apache/2.4.37 (centos)
Server built:   Sep 15 2020 15:41:16

$ ${CATALINA_HOME}/bin/version.sh
...
Server version: Apache Tomcat/9.0.40
Server built:   Nov 12 2020 15:35:02 UTC
Server number:  9.0.40.0
OS Name:        Linux
OS Version:     4.18.0-193.28.1.el8_2.x86_64
Architecture:   amd64
JVM Version:    11.0.9+11-LTS
JVM Vendor:     Red Hat, Inc.
```

## httpd：プロキシモジュールの確認

まず、00-proxy.conf に以下の設定が含まれていることを確認する。

```bash
$ vi /etc/httpd/conf.modules.d/00-proxy.conf
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
```
## httpd：プロキシの設定

ProxyPassとProxyPassReverseの記述は、/etc/httpd/conf/httpd.confや/etc/httpd/conf.d/ssl.confにはしない。それぞれのバーチャルホストのconfファイルを作成し、そこに記述する方針とした。

- vhost-02-kankeri.conf （kankeri.com:80用）
- vhost-02-kankeri-le-ssl.conf （kankeri.com:443用）
- vhost-03-xxx.conf （xxx.com:80用）
- vhost-03-xxx-ssl.conf （xxx.com:443用）

## vhost-02-kankeri.conf（kankeri.com:80用）

このconfファイルは、手作業で作成したもの。Rewriteの4行はCertbotで追記される。http:/のリクエストはhttps:/に置換される。

```bash
$ vi /etc/httpd/conf.d/vhost-02-kankeri.conf

<VirtualHost kankeri.com:80>
ServerName kankeri.com
ServerAlias www.kankeri.com
ServerAdmin webmaster@kankeri.com
DocumentRoot "/opt/tomcat9/webapps/kankeri"

<Directory "/opt/tomcat9/webapps/kankeri">
    Options FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

ErrorLog logs/kankeri-error_log
CustomLog logs/kankeri-access_log combined

RewriteEngine on
RewriteCond %{SERVER_NAME} =kankeri.com[OR]
RewriteCond %{SERVER_NAME} =www.kankeri.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
```

## vhost-02-kankeri-le-ssl.conf（kankeri.com:443用）

Certbotで自動生成されたconfファイルを編集する。Let's EncyptのSSLサーバー証明を使う。 [SSL証明書（Let's Encrypt）をセットアップする手順](https://qiita.com/taconana/items/a9e197e80b8c20c7d5ac)で作成する。そこに、mod_proxy_ajpで遷移できるようにProxyPassとProxyPassReverseを追記する。

```bash
$ vi /etc/httpd/conf.d/vhost-02-kankeri-le-ssl.conf
<IfModule mod_ssl.c>
<VirtualHost kankeri.com:443>
ServerName kankeri.com
ServerAlias www.kankeri.com
ServerAdmin webmaster@kankeri.com
DocumentRoot "/opt/tomcat9/webapps/kankeri"

<Directory "/opt/tomcat9/webapps/kankeri">
    Options FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

ProxyPass / ajp://localhost:8009/kankeri/

ErrorLog logs/kankeri-error_log
CustomLog logs/kankeri-access_log combined

SSLCertificateFile /etc/letsencrypt/live/kankeri.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/kankeri.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
```

ProxyPassの末尾には`/`が必要だが、ProxyPassReverseの末尾には`/`は不要らしい。リダイレクト時にLocationヘッダを書き換える必要がないからだそうだ。

## vhost-03-xxx.conf（xxx.com:80用）

このconfファイルは、手作業で作成したもの。vhost-02-kankeri.confをコピーして修正したものになる。

```bash
$ vi /etc/httpd/conf.d/vhost-03-xxx.conf

<VirtualHost xxx.com:80>
ServerName xxx.com
ServerAlias www.xxx.com
ServerAdmin webmaster@xxx.com
DocumentRoot "/opt/tomcat9/webapps/xxx"

<Directory "/opt/tomcat9/webapps/xxx">
    Options FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

ErrorLog logs/xxx-error_log
CustomLog logs/xxx-access_log combined

RewriteEngine on
RewriteCond %{SERVER_NAME} =xxx.com [OR]
RewriteCond %{SERVER_NAME} =www.xxx.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
```

## vhost-04-xxx-ssl.conf（xxx.com:443用）

xxx.comはプライベートのSSLサーバー証明を使う。 [SSL証明書（プライベート認証局）をセットアップする手順](https://qiita.com/taconana/items/863f50c7ba13d29cc6a7)で行う。そこに、mod_proxy_ajpで遷移できるようにProxyPassとProxyPassReverseを追記する。

```
$ vi /etc/httpd/conf.d/vhost-04-xxx-ssl.conf
<IfModule mod_ssl.c>
<VirtualHost xxx.com:443>
ServerName xxx.com
ServerAlias www.xxx.com
ServerAdmin webmaster@xxx.com
DocumentRoot "/opt/tomcat9/webapps/xxx"

<Directory "/opt/tomcat9/webapps/xxx">
    Options FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

ProxyPass / ajp://localhost:8009/xxx/

ErrorLog logs/xxx-error_log
CustomLog logs/xxx-access_log combined

SSLCertificateFile /etc/pki/tls/xxx.com/server.crt
SSLCertificateKeyFile /etc/pki/tls/xxx.com/server.key
</VirtualHost>
</IfModule>
```

## Tomcat9：server.xmlの設定

続いて、Tomcat側の設定をする。server.xmlを編集するだけである。実は、結構苦戦させられたのだが、解決には、https://qiita.com/polarbear08/items/f016a0675e6c9637e7b8 の記事が参考になった。「Tomcat9では9.0.31以降からAJPでの通信をするためにはTomcat側で初期設定が必要となる」とのことだった。

```bash
$ vi ${CATALINA_HOME}/conf/server.xml

    <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />
	↓
    <Connector protocol="AJP/1.3"
               address="localhost"
               secretRequired="false" 
               tomcatAuthentication="false" URIEncoding="UTF-8"
               port="8009"
               redirectPort="8443" />
```
address属性は、`localhost`にしておく。`0.0.0.0`を設定すると、全てのIPアドレスを許容するようになる。また、secretRequired="false" の属性を追加する必要がある。httpd側でパスワードを設定して、Tomcat9側で認証することもできるようだが、それは行わないことにした。

## 再起動と確認

それぞれのDocumentRootにダミーのHTMLを配置し、Apache httpdとTomcat9のサービスを再起動し、ブラウザからアクセスしてみる。

```bash
$ ls -la ${CATALINA_HOME}/webapps/kankeri
-rw-r--r--  1 tomcat9 tomcat9   98   2 11:58 index.html
$ ls -la ${CATALINA_HOME}/webapps/xxx
-rw-r--r--  1 tomcat9 tomcat9   98   2 11:58 index.html
$ systemctl restart httpd
$ systemctl restart tomcat9

http://xxx.com/ 
http://www.xxx.com/
https://xxx.com/	
https://www.xxx.com/
```

## 補足1: Tomcatのログ情報の使い方

当初、AJPの設定がよく分からず、以下のようなエラーがよく出ていた。

```
Service Unavailable
The server is temporarily unable to service your request 
due to maintenance downtime or capacity problems. Please try again later.
```
httpdの出力ログを見ると、以下のような感じである。

```bash
$ tail -50 /var/log/httpd/kankeri-error_log  | more
[Sun Dec 06 11:57:10.385924 2020] [proxy:error] 
[pid 400345:tid 140206267074304] (111)Connection refused: AH00957: AJP: attempt to connect to 127.0.0.1:8009 (localhost) failed
[Sun Dec 06 11:57:10.386019 2020] [proxy_ajp:error] 
[pid 400345:tid 140206267074304] [client 27.xx.xx.xx:51397] AH00896: failed to make connection to backend: localhost
```
この「AJP: attempt to connect to 127.0.0.1:8009 (localhost) failed」には、しばらく悩まされた。Tomcatのログ(catalina.out)で詳細をみるために、ログレベルをINFOからDEBUGに変更して、Tomcatを再起動し、再現させ、ログを眺めてみた。

```bash
$ vi ${CATALINA_HOME}/conf/logging.properties
 INFO
 ↓
 DEBUG
$ systemctl restart tomcat9

$ tail -50 ${CATALINA_HOME}/logs/catalina.out | more
06-Dec-2020 13:11:09.262 情報 [main] org.apache.catalina.util.LifecycleBase.handleSubClassException 
コンポーネント[Connector[AJP/1.3-8009]]の開始に失敗しました。
org.apache.catalina.LifecycleException: 
プロトコルハンドラの起動に失敗しました
Caused by: java.lang.IllegalArgumentException: 
AJP コネクタは secretRequired="true" として構成されていますが、
secret 属性は null または空文字列が設定されています。
この組み合わせは有効ではありません
```

この情報を元にsecretRequired="true"あたりの問題だとたどり着くことができた。また、ログレベルがDEBUGだと、server.xmlの記述ミスがあれば、詳細が出力され、分析が容易になる。

## 補足2: AJP:8009の前に8080ポートでの接続確認

また、ajp:8009ポートで接続できない場合、まず、http:8080ポートで接続できるかを先に確認した方がよい。原因の切り分けにはなるだろう。

```
    ProxyPass / http://localhost:8080/kankeri 
	↓
    ProxyPass / ajp://localhost:8009/kankeri
```

以上

| [../](https://qiita.com/taconana/items/e45f19e83c825a9ba75e) |
|---|