アプリケーションサーバをごっそり載せ替えた話

およそ10年前のアプリケーションサーバを、サービスを停止することなく新サーバに載せ替えて欲しい、という依頼に対応したので、その移行手順を簡単に紹介。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/7b757972-a4c9-d9c7-9d92-ee70c4664674.png)

# 仕様・要件
旧サーバで稼働するWebアプリケーションを新サーバに移転する。
新旧、両サーバの仕様は次の通りだ。

|種別     |旧サーバ     |新サーバ|
|:--      |:--         |:--|
|クラウド  |さくらのVPS |Azure VM |
|OS       |CentOS 5.11 |CentOS 7.4 |
|WWWサーバ |httpd 2.2.3|httpd 2.4.6 |
|APサーバ  |Tomcat 5.5.26  |Tomcat 5.5.26  |
|コネクタ  |mod_jk  |mod_proxy_ajp   |
|ランタイム|jdk 1.5.0_22   |jdk 1.8.0_162   |
|DBMS     |PostgreSQL 8.1.23   |PostgreSQL 9.2.23 |

- BtoBのWebサービスであり、小規模とはいえ数十社に使われている24時間365日稼働のサービスである。
- 新サーバには **PHP/Laravel** という新しめのフレームワークを追加し、データベースを **JavaServlet/JSP** と共有したいので、ミドルウェアも可能な限り新しくする。

# 新サーバの構築
Azureポータルの「リソースの作成」から **CentOS-based 7.4** をデプロイする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/390cf0e7-ce00-7601-ab70-f2915aac30a7.png)
VMにログインし、必要なモジュールを追加していく。

## Oracle Java8(JDK1.8)のインストール
OpenJDKはTomcatが公式にサポートしていないので使わなかった。

```
# rpm -ivh jdk-8u162-linux-x64.rpm

# java -version
java version "1.8.0_162"
Java(TM) SE Runtime Environment (build 1.8.0_162-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode)
```

## WWWサーバ(Apache)のインストール

```
yum -y install httpd
yum -y install mod_ssl

httpd -v
Server version: Apache/2.4.6 (CentOS)
```

Tomcatと連携させる。

```apache:/etc/httpd/conf.d/foo.conf
<Location /foo/>
    ProxyPass ajp://127.0.0.1:8009/foo/
</Location>
```

業務上必要なポートに穴を開ける。

```
firewall-cmd --list-all
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload
```

サーバ証明書まで入れたら、https://www.ssllabs.com/ssltest/ でSSL脆弱性診断を受け、指摘を直しておくと良いかも。

## Tomcatのインストール

5.5系のlatestである5.5.36を採用したところ、一部の画面で `HttpServletRequest#getAttribute` メソッドが `NullPointerException` を投げたので、従来の 5.5.26 を維持した。
ソースコードが開示されていない、というか開発元が倒産しているので、[IntelliJ IDEA](https://www.jetbrains.com/idea/)のプラグインでデコンパイルして直す…なんてことはリスク高いのでやらない。

```
cd /usr/local
wget https://archive.apache.org/dist/tomcat/tomcat-5/v5.5.26/bin/apache-tomcat-5.5.26.tar.gz
tar zxvf apache-tomcat-5.5.26.tar.gz

ln -s apache-tomcat-5.5.26 tomcat5
```

コンテキスト記述子を作成する。

```xml:/usr/local/tomcat5/conf/Catalina/localhost/foo.xml
<?xml version="1.0" encoding="utf-8"?>
<Context path="/foo" reloadable="true" docBase="/var/app/webapps/foo"></Context>
```

Tomcatがmod_proxy_ajpと通信する8009ポートを設定する。

```xml:/usr/local/tomcat5/conf/server.xml
<Connector port="8009"
    maxThreads="150" minSpareThreads="25" maxSpareThreads="100"
    acceptCount="100" connectionTimeout="0"
/>
```

`<Connector port="8080" ... />` はコメントアウトして無効にする。
サーブレットは、旧サーバから /var/app/webapps にまんまコピーしたので `<Host name="localhost" ... />` の appBase は `"/var/app/webapps"` に変更する。

### Tomcatをサービスに登録

```ini:/etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat 5 Servlet Container
After=syslog.target network.target

[Service]
Type=forking
ExecStart=/usr/local/tomcat5/bin/startup.sh
ExecStop=/usr/local/tomcat5/bin/shutdown.sh
KillMode=none

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

## JDBCドライバの更新
webapps下のJDBCは古いままなので、PostgreSQL 9.2に対応するJDBCに差し替える。

```
cd /var/app/webapps/foo/WEB-INF/lib
wget https://jdbc.postgresql.org/download/postgresql-42.2.2.jar
```

## PHP7のインストール

新サーバでは、JavaServlet/JSPに加えて、PHP/Laravelでもサービスを提供するためPHPが必要。

```
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
yum -y install --enablerepo=remi-php72 php php-mbstring php-pear php-fpm php-pecl-mcrypt php-pecl-zip php-xml php-gd php-pdo php-mysqlnd php-pgsql
```

ついでにComposerも入れておこう。（本番サーバでは使わないが念の為）

```
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
```

## DBサーバのインストール

```
# yum -y install postgresql-server
# postgresql-setup initdb

# psql --version
psql (PostgreSQL) 9.2.23
```

### データベースの移行
旧サーバでダンプを取得し、新サーバにコピーする。

```:旧サーバ
su - postgres
pg_dump foo > /tmp/foo.txt

scp /tmp/foo.txt bar@new.example.com:/tmp
```

新サーバでリストアする。

```
su - postgres
createdb foo
psql foo < /tmp/foo.txt
```

### 暗黙キャストの追加
PostgreSQLは、バージョン8.3 から型変換のチェックが厳密になり、暗黙の型変換がされなくなったので、
https://qiita.com/seiketkm/items/9d069348c2906a6ae011
https://qiita.com/6in/items/f23ead1314b9e6d2f2b7
などの記事を参考に、暗黙キャストを追加する。

```SQL
cat << 'EOF' > /tmp/cast.sql
CREATE CAST (int2 AS text) WITH INOUT AS IMPLICIT;
CREATE CAST (int4 AS text) WITH INOUT AS IMPLICIT;
CREATE CAST (int8 AS text) WITH INOUT AS IMPLICIT;
CREATE CAST (text AS numeric) WITH INOUT AS IMPLICIT;
CREATE CAST (numeric AS character) WITH INOUT AS IMPLICIT;
CREATE CAST (character varying AS numeric) WITH INOUT AS IMPLICIT;
CREATE CAST (timestamp without time zone AS text) WITH INOUT AS IMPLICIT;
CREATE CAST (timestamp without time zone AS character varying) WITH INOUT AS IMPLICIT;
CREATE CAST (character varying AS timestamp without time zone) WITH INOUT AS IMPLICIT;
CREATE CAST (timestamp with time zone AS text) WITH INOUT AS IMPLICIT;
CREATE FUNCTION textint4cat(text, int4) RETURNS text AS 'SELECT $1 || $2::pg_catalog.text' LANGUAGE sql IMMUTABLE STRICT;
CREATE OPERATOR || (PROCEDURE = textint4cat,LEFTARG = text, RIGHTARG = int4);
EOF

psql foo < /tmp/cast.sql
```

キャストの一覧を確認する。

```
psql foo
\dC
```

## 動作確認
ここまでで、いったんWebアプリケーションの動作を確認する。

# 移行
サービス利用者が圧倒的に少ない深夜帯に決行。
以下はすべて旧サーバのオペレーションである。

## アクセスログのモニタリング
ターミナルをもうひとつ起動し、アクセスログをモニタリングしておく。

```
tail -f /var/log/httpd/old.example.com.access.log
```

## データベースのコピー
アクセスがなければ、旧サーバのダンプを新サーバでリストアする。
もし、その間にアクセスがあれば、やり直し！

## リダイレクトの設定
301リダイレクト（恒久的な転送）はブラウザにリダイレクトキャッシュが残り、戻せなくなるので、302リダイレクト（一時的な転送）にする。

```apache:httpd.conf
Redirect temp /foo/ https://new.example.com/foo/
```

再度アクセスが無いことを確認できたら、httpdを再起動。
リクエストが新サーバに転送されているか確認しよう。

## 旧サーバのデータベース名を変更
なんかの間違いで旧サーバにアクセスできてしまった場合でも、エラーとして確実に報告されるようにする。

```
su - postgres
psql
alter database "foo" rename to "foo_BK";
```

## DNSの切り替え
バーチャルホストで運用している場合、公開時のドメインでアクセスしないと正しく表示されない。
DNSを書き替える前に新しいドメインでテストするには、クライアントの`hosts`ファイルを編集する。
Windowsの場合、`hosts`ファイルの場所は以下にある。

```
C:\Windows\System32\drivers\etc\hosts
```

メモ帳などのテキストエディタを管理者権限で起動し、「新IPアドレス（スペース）旧ドメイン」で追加しよう。

テストが問題なければDNSサーバのゾーンファイルを編集する。旧サーバのドメインに定義しているグローバルIPアドレスを、新サーバのグローバルIPアドレスに変更し、DNS伝播したところで旧サーバを閉鎖する。

```config:/usr/local/etc/namedb/master/example.com.zone
$TTL 3600
$ORIGIN example.com.
@       IN      SOA     xxx.example.com. (
        2019070101      ; Serial
        43200           ; Refresh after 12 hours
        3600            ; Retry after one hour
        2419200         ; Expire after 4 weeks
        1200    )       ; Negative cache TTL of 20 minutes
;
; Authoritative name servers
;
        IN      NS      ns01.example.com.
;
; Host
;
old     3600     IN      A       nnn.nnn.nnn.nnn  ; 新サーバのIPアドレスに変更
```