CentOS 7 にmod_jkをインストールして、ApacheとTomcatとAJPで連携させてみる

# TL;DR

* CentOS上で、mod_jkを使ってApache、Tomcatを連携させる
* mod_jkのビルドには`apxs`が必要
* CentOSの場合、apxsは`httpd-devel`でインストールできる
* mod_jkは共有メモリを使うので、SELinuxに阻まれることがあるので注意

というわけで、この記事ではCentOS上にインストールしたApache、Tomcatをmod_jkを使って連携させてみます。


# mod_jk

mod_jkとは、Tomcatプロジェクトが開発している、Webサーバーとの接続プラグインのひとつです。

[The Apache Tomcat Connectors: mod_jk, ISAPI redirector, NSAPI redirector](https://tomcat.apache.org/connectors-doc/)

[Configuring mod_jk for the Apache HTTP Server](https://tomcat.apache.org/connectors-doc/reference/apache.html)

mod_jkはApacheをターゲットとしており、他にはIIS、iPlanet Web Server用のプラグインもあるようです。

Tomcatとの通信は、AJP（Apache JServ Protocol）というプロトコルを使用します。


# 環境

今回の環境は、こちら。

```shell
$ cat /etc/redhat-release 
CentOS Linux release 7.8.2003 (Core)


$ java --version
openjdk 11.0.6 2020-01-14 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.6+10-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.6+10-LTS, mixed mode, sharing)
```

CentOS 7.8、OpenJDK 11です。

Apacheは2.4.6、Tomcatは9.0.34、mod_jkは1.2.48を使用します。


# Tomcatをインストールする

それでは、まずはバックエンドになるTomcatをインストールしましょう。今回は、Tomcat 9.0.34を使用します。

```shell
$ curl -OL https://downloads.apache.org/tomcat/tomcat-9/v9.0.34/bin/apache-tomcat-9.0.34.tar.gz
$ tar xf apache-tomcat-9.0.34.tar.gz 
$ cd apache-tomcat-9.0.34
```

AJPのConnectorの設定をしておきます。

`conf/server.xml`のAJP Connectorのコメントアウトを単純に解除すると、以下のようになるのですが

```xml
    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />
```

さらに`secretRequired`を`false`に設定します。

```xml
    <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443"
               secretRequired="false" />
```

でないと、AJP Connectorの起動に失敗します。

```
29-Apr-2020 07:43:56.765 重大 [main] org.apache.catalina.util.LifecycleBase.handleSubClassException コンポーネント[Connector[AJP/1.3-8009]]の開始に失敗しました。
	org.apache.catalina.LifecycleException: プロトコルハンドラの起動に失敗しました
		at org.apache.catalina.connector.Connector.startInternal(Connector.java:1066)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.StandardService.startInternal(StandardService.java:438)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.startup.Catalina.start(Catalina.java:633)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.base/java.lang.reflect.Method.invoke(Method.java:566)
		at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:343)
		at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:474)
	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:264)
		at org.apache.catalina.connector.Connector.startInternal(Connector.java:1063)
		... 12 more
```

これは、AJPがTomcatの内部データ構造を操作できるため、セキュリティ的な対策を入れるためのもののようです。

[The AJP Connector](https://tomcat.apache.org/tomcat-9.0-doc/config/ajp.html)

起動できるようにするには`secret`を設定するか、`secretRequired`を`false`にする必要があるようです。今回は、`secretRequired`を`false`にしました。

要するに、緩く設定しました、と。

では、動作確認のために適当にコンテンツを作りましょう。

```shell
$ mkdir webapps/sample
$ echo 'Hello Tomcat!!' > webapps/sample/index.jsp
```

起動。

```shell
$ bin/startup.sh
```

確認。ここで使っているのは、HTTPのConnectorですが。

```shell
$ curl localhost:8080/sample/index.jsp
Hello Tomcat!!
```

これで、Tomcatの準備はOKです。


# Apacheをインストールする

次は、Apacheをインストールします。こちらは、`yum`で簡単に。

```shell
$ sudo yum install httpd
```

起動。

```shell
$ sudo systemctl start httpd
```

Apache 2.4.6がインストールされました。

```shell
$ httpd -v
Server version: Apache/2.4.6 (CentOS)
Server built:   Apr  2 2020 13:13:23
```

とりあえず、起動していることは確認。

```shell
$ curl -I localhost
HTTP/1.1 403 Forbidden
Date: Wed, 29 Apr 2020 05:18:36 GMT
Server: Apache/2.4.6 (CentOS)
Last-Modified: Thu, 16 Oct 2014 13:20:58 GMT
ETag: "1321-5058a1e728280"
Accept-Ranges: bytes
Content-Length: 4897
Content-Type: text/html; charset=UTF-8
```

# mod_jkをインストールする

mod_jkをインストールしましょう。

```shell
$ curl -OL https://downloads.apache.org/tomcat/tomcat-connectors/jk/tomcat-connectors-1.2.48-src.tar.gz
$ tar xf tomcat-connectors-1.2.48-src.tar.gz
$ cd tomcat-connectors-1.2.48-src
```

こんな感じに展開されるので

```shell
$ ls -l
合計 32
-rw-r--r--.  1 user user  7274  2月 20 16:43 HOWTO-RELEASE.txt
-rw-r--r--.  1 user user 13597  2月 20 16:43 LICENSE
-rw-r--r--.  1 user user   455  2月 20 16:43 NOTICE
-rw-r--r--.  1 user user  1238  2月 20 16:43 README.txt
drwxr-xr-x.  2 user user    84  2月 20 16:43 conf
drwxr-xr-x. 10 user user   157  2月 20 16:43 docs
drwxr-xr-x.  6 user user   123  2月 20 16:43 jkstatus
drwxr-xr-x.  8 user user   277  2月 20 16:43 native
drwxr-xr-x.  2 user user   238  2月 20 16:43 support
drwxr-xr-x.  4 user user   111  2月 20 16:43 tools
drwxr-xr-x.  9 user user   217  2月 20 16:43 xdocs
```

nativeディレクトリに移動。

```shell
$ cd native
```

Cコンパイラーは入った状態で、configureしてみます。

```shell
$ ./configure
```

すると、Webサーバーの指定がないと怒られます。

```shell
no apache given
no netscape given
configure: error: Cannot find the WebServer
```

ヘルプを見てみます。

```shell
$ ./configure --help
```

Shared Apacheモジュールとして作成するか、Static Apacheモジュールとして作成するかを選べるようです。

```shell
  --with-apxs[=FILE]      Build shared Apache module.
                          FILE is the optional pathname to the apxs tool;
                          defaults to finding apxs in your PATH.

  --with-apache=DIR       Build static Apache module. DIR is the pathname to
                          the Apache source directory.
```

今回は、Shared Apacheモジュールでいきましょう。

`apxs`が必要になるということなので、httpd-develパッケージをインストールします。

```shell
$ sudo yum install httpd-devel
```

apxsがインストールされました。

```shell
$ which apxs
/usr/bin/apxs
```

というわけで、もう1度configure。

```shell
$ ./configure --with-apxs=`which apxs`
```

今度はうまくいくので、make、make install。

```shell
$ make && sudo make install
```

インストールされました。

```shell
$ ll /usr/lib64/httpd/modules/mod_jk.so 
-rwxr-xr-x. 1 root root 1565880  4月 29 05:42 /usr/lib64/httpd/modules/mod_jk.so
```

/etc/httpd/modulesのシンボリックリンクにもなっています。

```shell
$ ls -l /etc/httpd/modules
lrwxrwxrwx. 1 root root 29  4月 29 05:15 /etc/httpd/modules -> ../../usr/lib64/httpd/modules
```

# mod_jkを使って、ApacheとTomcatを連携させる

では、最後にApacheとTomcatを連携させてみましょう。

参考にするドキュメントは、以下あたりで。

[Quick Start HowTo](https://tomcat.apache.org/connectors-doc/common_howto/quick.html)

[Configuring mod_jk for the Apache HTTP Server](https://tomcat.apache.org/connectors-doc/reference/apache.html)

[The Apache Tomcat Connectors - Common HowTo](https://tomcat.apache.org/connectors-doc/common_howto/workers.html)

今回は、Proxyモジュールあたりに、mod_jkモジュールの読み込みを追加。

`/etc/httpd/conf.modules.d/00-proxy.conf`

```shell
LoadModule jk_module modules/mod_jk.so
```

Worker（＝Tomcat）の設定をしましょう。

`/etc/httpd/conf.d/workers.properties`

```
worker.list=worker1

worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=8009
```

単純にバックエンドのTomcatに転送するか、ロードバランシングもできるのですが、今回はシンプルにローカルのTomcatに転送するだけにしました。

mod_jkの設定。

`/etc/httpd/conf.d/welcome.conf`

```
JkWorkersFile /etc/httpd/conf.d/workers.properties
JkMount /* worker1
```

先ほど作成したWorkerの設定ファイルを指定し、リクエストを転送するパスとWorkerのマッピングを指定します。

で、Apacheを再起動すると…失敗します。

```shell
$ sudo systemctl restart httpd
Job for httpd.service failed because the control process exited with error code. See "systemctl status httpd.service" and "journalctl -xe" for details.
```

Apacheのエラーログを見ると、共有メモリを確保できないようです。

`/var/log/httpd/error_log`

```
[Wed Apr 29 05:51:44.727622 2020] [jk:emerg] [pid 27983] Initializing shm:/etc/httpd/logs/jk-runtime-status.27983 errno=13. Unable to start due to shared memory failure.
[Wed Apr 29 05:51:44.727631 2020] [jk:emerg] [pid 27983] Initializing shm:/etc/httpd/logs/jk-runtime-status.27983 errno=13. Unable to start due to shared memory failure.
```

これを単純に回避するには…SELinuxを無効にしますか。

```shell
$ sudo setenforce 0
```

今度は、再起動に成功します。

```shell
$ sudo systemctl restart httpd
```

確認。

```shell
$ curl localhost/sample/index.jsp
Hello Tomcat!!
```

これで、Apache＋mod_jkから、AJPでTomcatと連携できましたね。