Linux(CentOS)にApacheとTomcatをインストールしてJavaアプリをデプロイするまで

# はじめに
Linuxに関する知識を身につけたくて、LinuxサーバのレンタルからJDK, Apache, Tomcatのインストール、自分のローカルPCで作ったJavaアプリケーション（Hello Worldレベル）のデプロイを自力でやってみました。その際にやったこと、詰まったことをどうやって解消したかを書き残したのがこの記事です。
最近はクラウドが圧倒的に主流だけど、クラウドはいろいろブラックボックスすぎると感じています。自分はLinux（インフラ）に関してはとにかく基礎ができていないので、まずはクラウドではなくプレーンなLinuxサーバをレンタルし、そこにアプリケーションをデプロイする作業を自分でやってみる。さらに今後は、アプリケーションを拡張して運用も自分でやってみる。このような試みを通じて、Linuxの基礎を身につけたい。そう思ってこんなことをやってみました。今回はアプリケーションをデプロイするまでで、拡張や運用は今後の話です。デプロイしたのは、アプリケーションと言ってもHello Worldレベルなので、Javaについては書きません。LinuxサーバをレンタルしてApache、Tomcatをインストールして設定してURL叩いたらHello Worldが出た！っていう程度のものです。

# 実際にやったこと
## 個人向けレンタルサーバをレンタル
Javaが使える個人向けレンタルサーバをレンタル。今回はさくらインターネットを使うことにした。いろいろあるので自分で探して適切なものを選択しましょう。

- [個人向けレンタルサーバー比較](https://www.homepage-tukurikata.com/rental-server/)
- [Javaが使えるレンタルサーバーを分析・比較](https://find-server.net/javaok-rental-server/)

## OSの確認

### CentOS のバージョン確認
これからレンタルしたサーバにJavaやApacheやTomcatをインストールしていくのですが、その前にOSの基本情報を確認。まずは、以下のコマンドで CentOS のバージョンを確認します。
`cat /etc/redhat-release`

```:実行結果
CentOS Linux release 7.7.1908 (Core)
```

CentOSのバージョン7であることがわかる。

### アーキテクチャ（OSが32bit, 64bitどちらなのか）を確認
以下のコマンドでOSが32bit, 64bitどちらなのかを確認します。

`arch`

```:実行結果(64bit)
X86_64
```
これは64bitであることを示しています。32bitの場合は以下のように表示されます。

```:実行結果(32bit)
i686
```

以下のコマンドでも確認することができます。
`uname -a`

CentOS 7 の場合は以下のように表示されます。

```:CentOS7の表示例(64bit)
Linux ik1-344-32350.vs.sakura.ne.jp 3.10.0-957.10.1.el7.x86_64 #1 SMP Mon Mar 18 15:06:45 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
```

参考ですが、CentOS 6 の場合は以下のように表示されます。

```:CentOS6の表示例(64bit)
Linux localhost.localdomain 2.6.32-279.2.1.el6.x86_64 #1 SMP Fri Jul 20 01:55:29 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
```

参考：[CentOSのバージョン確認コマンドとアーキテクチャ確認コマンド](https://mawatari.jp/archives/check-centos-version)

### パッケージのアップデート
以下コマンドでパッケージをアップデートします。
`yum update`

本来であれば、現在動作中のプログラムに影響を与えないようにするため、パッケージを指定してアップデートします。しかし今回はレンタルしたばかりで特に何のアプリケーションも稼働していないので、雑に`yum update`ですべてのパッケージを更新しました。
パッケージを指定するコマンドは以下の通りです。普通はこっちを使って稼働中プログラムに影響を与えないようにしてください。
`yum update [パッケージ]`

参考：[【yum update】yumのパッケージを安全にアップデートする](https://uxmilk.jp/9173)

## Java 8 (OpenJDK) インストール

ランタイムだけのインストールで良ければ java-1.8.0-openjdk を、OpenJDK の開発環境もインストールしたければ java-1.8.0-openjdk-devel もインストールします。

### Java 8 (JDK) ランタイムのインストール
以下のコマンドを実行してJava 8 (JDK) ランタイムをインストールします。
`yum install java-1.8.0-openjdk`

### java 8 (JDK) 開発環境のインストール
開発環境が必要であれば、以下のコマンドを実行してjava 8 (JDK) 開発環境をインストールします。私は自分のPCで開発してそれをサーバにデプロイするので開発環境はインストールしませんでした。
`yum install java-1.8.0-openjdk-devel`


### インストール完了確認
念のためインストールが完了したのかを確認してみましょう。次のコマンドを打って、インストールした Java のバージョンが表示されれば確認終了です。

```:実行コマンド
java -version
```


```:実行結果
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b08)
OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)
```

参考：[CentOS 7 に Java 8 (OpenJDK) を yum インストールする手順](https://weblabo.oscasierra.net/installing-openjdk8-on-centos7/)

## Apacheインストール

### Apacheをインストールしてサービス登録する
以下コマンドでApacheをインストールします。

```:実行コマンド
yum -y install httpd
```

サービス登録し、起動します。

```:実行コマンド
systemctl enable httpd.service
systemctl start httpd.service
```

以下コマンドでサービスが起動していることを確認します。

```:実行コマンド
systemctl status httpd.service
```


`active (running)`が表示されればOKです。
最後に、テストページ（`http://<IPアドレス>`）が表示されることを確認します。

参考：[さくらクラウドのCentOS7でApacheとTomcatの環境構築した時のまとめ](https://livlog.jp/technical/environment-tomcat/)


## Tomcatインストール
### Apache Tomcat 9 のインストール

まず、Linux 上で Tomcat を動かすための専用ユーザとして tomcat を追加します。

```:実行コマンド
useradd -s /sbin/nologin tomcat
```

次に、curl コマンドで [Apache Tomcat 9 のダウンロードページ](http://tomcat.apache.org/download-90.cgi)から、tar.gz 形式の Apache Tomcat 9 本体をダウンロードします。2020年05月現在はバージョン 9.0.34 が最新版なので次のURLを指定します。最新のバージョンのURLを確認の上で実行してください。

```:実行コマンド
cd ~
curl -O http://ftp.riken.jp/net/apache/tomcat/tomcat-9/v9.0.34/bin/apache-tomcat-9.0.34.tar.gz
```

ダウンロードした tar.gz ファイルを解凍して配置します。次のように tar コマンドで解凍し、/opt に配置します。また、解凍した Apache Tomcat の所有者を、先ほど作成した tomcat ユーザの所有とします。

```:実行コマンド
tar -xzvf ~/apache-tomcat-9.0.34.tar.gz
mv ~/apache-tomcat-9.0.34 /opt
chown -R tomcat:tomcat /opt/apache-tomcat-9.0.34
```

### サービスの作成と登録
CentOS 7 からはサービスを systemd が管理するようになりました。ここでは Apache Tomcat 9 をサービスとして登録します。まずは `/etc/systemd/system/tomcat.service` を新たに作成して、次のように記述して保存します。これはサービスの定義ファイルです。

```:tomcat.service
[Unit]
Description=Apache Tomcat 9
After=network.target

[Service]
User=tomcat
Group=tomcat
Type=oneshot
PIDFile=/opt/apache-tomcat-9.0.34/tomcat.pid
RemainAfterExit=yes

ExecStart=/opt/apache-tomcat-9.0.34/bin/startup.sh
ExecStop=/opt/apache-tomcat-9.0.34/bin/shutdown.sh
ExecReStart=/opt/apache-tomcat-9.0.34/bin/shutdown.sh;/opt/apache-tomcat-9.0.34/bin/startup.sh

[Install]
WantedBy=multi-user.target
```

次のコマンドで、作成した定義ファイルの権限を 755 に変更します。

```:実行コマンド
chmod 755 /etc/systemd/system/tomcat.service
```

定義ファイルの作成が完了したら、次のコマンドでサービスを有効にします。

```:実行コマンド
systemctl enable tomcat
```

以下が表示されればOKです。

```:実行結果
Created symlink from /etc/systemd/system/multi-user.target.wants/tomcat.service to /etc/systemd/system/tomcat.service.
```

最後に、`http://<IPアドレス>:8080`にアクセスし、Tomcatのテストページが表示されることを確認します。

【表示される画面のサンプル】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254168/14f8bd6b-a1f1-8f90-0137-02a7e3e29a5c.png)

このような画面が表示されればTomcatのインストール、およびサービス登録完了です。

参考：[Apache Tomcat 9 を CentOS 7 にインストールする手順](https://weblabo.oscasierra.net/installing-tomcat9-centos7-1/)

## ApacheとTomcatの連携
AJP (Apache JServ Protocol)を使ってApacheとTomcatを連携させます。AJP を利用するためには mod_proxy_ajp というモジュールを利用します。

参考：[Apache httpd と Tomcat を連携させる方法](https://weblabo.oscasierra.net/tomcat-mod-proxy-ajp/)

## apacheの設定
### proxy_moduleとproxy_ajp_moduleをLoad
私が apache をインストールしたとき、`httpd.conf`はこの状態でした。

```http:httpd.conf（一部抜粋）

～中略～

#
# Dynamic Shared Object (DSO) Support
#
# To be able to use the functionality of a module which was built as a DSO you
# have to place corresponding `LoadModule' lines at this location so the
# directives contained in it are actually available _before_ they are used.
# Statically compiled modules (those listed by `httpd -l') do not need
# to be loaded here.
#
# Example:
# LoadModule foo_module modules/mod_foo.so
#
Include conf.modules.d/*.conf

～中略～

```

ここに記載されている「/etc/httpd/conf.modules.d」に行ってみると、以下の７個のconfファイルが確認できます。

```
01-cgi.conf
00-systemd.conf
00-proxy.conf
00-mpm.conf
00-lua.conf
00-dav.conf
00-base.conf
```

この中の「00-proxy.conf」を開くと以下の設定があります。

```:00-proxy.conf（一部抜粋）
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
```

AJP を利用するためには mod_proxy_ajp が必要で、mod_proxy_ajp は mod_proxy モジュールに依存しています。そのため、これら２つのモジュールが必要です。00-proxy.confに上記設定があれば必要なmoduleがちゃんとLoadできています。
ちなみに、こられのモジュール「mod_proxy.so」と「mod_proxy_ajp.so」は、`/usr/lib64/httpd/modules`ディレクトリに行くと確認できます。
（/etc/httpd配下に/usr/lib64/httpd/modulesへのショートカットがあります）

### ProxyPassの設定
httpd.confの一番最後の行に以下のような設定があるため、`/etc/httpd/conf.d`ディレクトリにconfファイルを作成すればその内容が読み込まれるようになっています。

```http:httpd.conf

～中略～

# Supplemental configuration
#
# Load config files in the "/etc/httpd/conf.d" directory, if any.
IncludeOptional conf.d/*.conf
```

以下の内容で`/etc/httpd/conf.d`ディレクトリに`proxy-ajp.conf`ファイルを作成します。

```
cat /etc/httpd/conf.d/proxy-ajp.conf
<Location /docs/>
  ProxyPass ajp://127.0.0.1:8009/docs/
</Location>
```

portを8009にしているのは、TomcatがAJP 1.3の通信をport 8009で受け付けているからです。Tomcatの設定はこの後出てきます。
Apacheの設定はこれで完了です。

## Tomcatの設定
### server.xmlの修正

`/opt/apache-tomcat-9.0.34/conf/server.xml`を修正します。修正前は以下のようになっています。

```xml:server.xml（修正前）
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

～中略～

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

インターネットからのすべてのアクセスをapache経由にするのであればport 8080での待ち受けは不要になるため、コメントアウトします。
修正後は以下の通りとなります。

```xml:server.xml（修正後）
    <!--
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->

～中略～

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

これでapacheとTomcatを連携するための設定は完了です。

### はまったこと１
上記作業が終わり、`http://<IPアドレス>/docs`をブラウザに入力したのですが、503エラーがでてアクセスできませんでした。apacheのログ(error_log)を確認したところ、以下のログが出ていました。

```java:Apacheログ
[Sun Apr 26 14:33:39.753098 2020] [proxy:error] [pid 30144] (111)Connection refused: AH00957: AJP: attempt to connect to 127.0.0.1:8009 (127.0.0.1) failed
[Sun Apr 26 14:33:39.753136 2020] [proxy:error] [pid 30144] AH00959: ap_proxy_connect_backend disabling worker for (127.0.0.1) for 60s
[Sun Apr 26 14:33:39.753141 2020] [proxy_ajp:error] [pid 30144] [client 220.100.106.105:51467] AH00896: failed to make connection to backend: 127.0.0.1
```

最初はこのログの文言を元にいろいろググったのですが、どうやったら解決できるのかまったく見当がつきませんでした。
次にTomcatのログ(catalina.out)を確認したところ、Tomcatを起動する度に以下のログが出力されていることが確認できました。

```java:Tomcat起動時のログ１
18-Apr-2020 16:36:49.322 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:1041)
		at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136)
		at org.apache.catalina.core.StandardService.initInternal(StandardService.java:533)
		at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136)
		at org.apache.catalina.core.StandardServer.initInternal(StandardServer.java:1057)
		at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136)
		at org.apache.catalina.startup.Catalina.load(Catalina.java:584)
		at org.apache.catalina.startup.Catalina.load(Catalina.java:607)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:303)
		at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:473)
	Caused by: java.net.SocketException: Protocol family unavailable
		at sun.nio.ch.Net.bind0(Native Method)
		at sun.nio.ch.Net.bind(Net.java:433)
		at sun.nio.ch.Net.bind(Net.java:425)
		at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:220)
		at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:85)
		at org.apache.tomcat.util.net.NioEndpoint.initServerSocket(NioEndpoint.java:228)
		at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:211)
		at org.apache.tomcat.util.net.AbstractEndpoint.bindWithCleanup(AbstractEndpoint.java:1141)
		at org.apache.tomcat.util.net.AbstractEndpoint.init(AbstractEndpoint.java:1154)
		at org.apache.coyote.AbstractProtocol.init(AbstractProtocol.java:581)
		at org.apache.catalina.connector.Connector.initInternal(Connector.java:1038)
		... 13 more
```

「Protocol family unavailable」とか「Connector[AJP/1.3-8009]」でググったら[このサイト](https://stackoverflow.com/questions/61186022/tomcat-ajp-8009-protocol-family-unavailable)にたどり着きました。どうやらIPv6をサポートしていないサーバでIPv6のアドレス"::1"が指定されていると起きる事象らしい。
"::1"を"0.0.0.0"に書き換えることで解決しました。

```xml:server.xml（"0.0.0.0"に修正）
    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector protocol="AJP/1.3"
               address="0.0.0.0"
               port="8009"
               redirectPort="8443" />
```

### はまったこと２

上記「はまったこと１」を対処した後Tomcatを再起動したら、次はこんなログが出力されました。

```java:Tomcat起動時のログ２
	org.apache.catalina.LifecycleException: Protocol handler start failed
		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 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		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
```

調べてみると、server.xml の Connector の属性に`secretRequired`というものがあり、これがデフォルトtrueになっている。trueの場合、secretという属性で合言葉的なものを指定する必要があるらしい。（[公式サイト](https://tomcat.apache.org/tomcat-8.5-doc/changelog.html)）
[このサイト](http://softwaremetrics.seesaa.net/article/473746192.html)では、「secretRequired="false"」を明示的に指定して解決していたのでこれを真似してみたら解決しました。

```xml:server.xml（secretRequired追加）
    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector protocol="AJP/1.3"
               address="0.0.0.0"
               port="8009"
               redirectPort="8443"
               secretRequired="false" />
```

これでTomcatの設定は完了です。

## warのデプロイ
最後に、自分のローカルPCで作成したwarファイルをレンタルサーバにデプロイします。warファイルを`/opt/apache-tomcat-9.0.34/webapps`に格納すると、Tomcatが自動でデプロイしてくれます。
デプロイが完了したらアクセスしてHello Worldが出ることを確認して終わりです。

以上。

# 参考
- [CentOSのバージョン確認コマンドとアーキテクチャ確認コマンド](https://mawatari.jp/archives/check-centos-version)
- [【yum update】yumのパッケージを安全にアップデートする](https://uxmilk.jp/9173)
- [CentOS 7 に Java 8 (OpenJDK) を yum インストールする手順](https://weblabo.oscasierra.net/installing-openjdk8-on-centos7/)
- [Apache Tomcat 9 を CentOS 7 にインストールする手順](https://weblabo.oscasierra.net/installing-tomcat9-centos7-1/)
- [Apache Tomcat 9](http://tomcat.apache.org/tomcat-9.0-doc/proxy-howto.html)
- [さくらクラウドのCentOS7でApacheとTomcatの環境構築した時のまとめ](https://livlog.jp/technical/environment-tomcat/)
- [Apache httpd と Tomcat を連携させる方法](https://weblabo.oscasierra.net/tomcat-mod-proxy-ajp/)
- [Eclipseでwarファイルを作成し、Tomcatにデプロイする手順](https://itsakura.com/tomcat-warfile)