およそ10年前のアプリケーションサーバを、サービスを停止することなく新サーバに載せ替えて欲しい、という依頼に対応したので、その移行手順を簡単に紹介。
仕様・要件
旧サーバで稼働する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 をデプロイする。
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と連携させる。
<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のプラグインでデコンパイルして直す…なんてことはリスク高いのでやらない。
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 version="1.0" encoding="utf-8"?>
<Context path="/foo" reloadable="true" docBase="/var/app/webapps/foo"></Context>
Tomcatがmod_proxy_ajpと通信する8009ポートを設定する。
<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をサービスに登録
[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
などの記事を参考に、暗黙キャストを追加する。
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リダイレクト(一時的な転送)にする。
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伝播したところで旧サーバを閉鎖する。
$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アドレスに変更