WordPress
monit
HHVM
KUSANAGI

kusanagi で hhvm が落ちた時、何が何でもリスタートする

hhvm プロセスはあるけど http(s) 応答がないときの monit スクリプト

検証環境

  • Conoha で配っている kusanagi ディストリビューション(CentOS7)を使用
  • hhvm + apache + monit は同一サーバーに配置
  • /var/log/monit.log など kusanagi ディストリビューションの設定です
  • もうすでに WordPress などが稼働しているものとします
    • kusanagi 自身のインストール方法には触れません

起きている問題と解決する monit 設定ファイル

PHP 実行環境 hhvm で WordPress を 3 つ動かしたあたりからやたら hhvm が落ちだします。そういうのは想定済みたいで、kusanagi 標準添付の monit の hhvm.conf では hhvm の無応答またはプロセスが落ちたことを検出して再起動をかけくれます。しかし、時々次のような現象が起きます:

  • hhvm プロセスは残ったままで
  • hhvm のポート(9000/tcp)も開いたまま
  • でも apache 経由でアクセスすると一切応答がない
  • この状態で service hhvm stop するとやたら時間がかる
    • 一番ひどいときは 180 秒くらい
  • hhvm を再起動すると復活するつまり apache の問題ではない

以上、hhvm プロセスが残ったまま http(s) の応答がないケースにも対応させるために以下 hhvm.conf の置き換えで アプリケーション層 にて再起動を判断し、また、service hhvm stop にやたら時間がかかる現象にも対応します:

/etc/monit.d/hhvm.conf
check host heartbeathhvm with address heartbeat.selfnavi.com
    start program =   "/bin/systemctl start hhvm.service"
    stop program =    "/bin/systemctl stop hhvm.service" with timeout 360 seconds
    depends on httpd
    if failed
        port 80
        protocol http
        and request /
        with content = "It works!"
        with timeout 3 seconds
    then restart
    if 3 restarts within 3 cycles then unmonitor
    group hhvm

heartbeat.selfnavi.com は生死判定をするサイトで、同一サーバー内に設置してください。各自設置時はこの名前からは変更しなければなりませんが、この記事では heartbeat.selfnavi.com とします。 wget で It works! が出力されることを確認しときます。

/home/kusanagi/heartbeat/DocumentRoot/index.php
<?php
echo "It works!";
$ wget -qO - http://heartbeat.selfnavi.com
It works

以上でわかればここで終わり。もう少し詳しい説明が必要なら読み進めてください:

  • 生死判定をするサイトの設置
  • monit の設定
  • /etc/monit.d/hhvm.conf についての検証

生死判定をするサイトの設置

  1. kusanagi コマンドで apache の設定まで終わらせます

    • Webサイトで使用するホスト名(FQDN) はなんでも構わないのでご自身のものとしてください
    • 上記 FQDN は kusanagi により /etc/hosts に追記され、 127.0.0.1 を指すようになります
    • データベースを作らないと先に進んでくれませんので後でけします
    # kusanagi provision --lamp heartbeat
    ターゲットディレクトリは /home/kusanagi/heartbeat に変更されました。
    ↵
    Webサイトで使用するホスト名(FQDN)を入力してください。 例) kusanagi.tokyo
    heartbeat.selfnavi.com
    Webサイトで使用するホスト名(FQDN)をもう一度入力してください。
    heartbeat.selfnavi.com
    ↵
    Let's Encryptを使用される場合、Let's Encrypt の使用規約に同意される必要があります。
    使用規約に同意される場合、あなたのメールアドレスを入力してください。同意されない場合、Enterキーを二回押してください。
    使用規約は次のURLより確認できます: https://letsencrypt.org/repository/
    [Enter]
    メールアドレスを再入力してください。
    [Enter]
    データベース名を入力してください。
    heartbeat
    データベース名を再度入力してください。
    heartbeat
    heartbeat のユーザー名を入力してください。
    heartbeat
    heartbeat のユーザー名を再度入力してください。
    heartbeat
    データベースユーザ'heartbeat'のパスワードを入力してください。[a-zA-Z0-9.!#%+_-]の文字列が使用できます。最小は8文字以上です。
    heartbeat
    再度 'heartbeat' のパスワードを入力してください。
    heartbeat
    Job for nginx.service invalid.
    heartbeat のプロビジョニングは完了しました。heartbeat.selfnavi.com にアクセスし、lampをインストールしてください!
    完了しました。
    
  2. kusanagi コマンドで作ってしまったデータベースを消します

    • your_mysql_root_password は、ご自身の mysql root パスワードに置き換えてください
    $ mysql -uroot -pyour_mysql_root_password
    Welcome to the MariaDB monitor.  Commands end with ; or \g.
    Your MariaDB connection id is 1847575
    Server version: 10.0.29-MariaDB-wsrep MariaDB Server, wsrep_25.16.rc3fc46e
    ↵
    Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.
    ↵
    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
    MariaDB [(none)]> drop user 'heatbeat'@'localhost';
    Query OK, 0 rows affected (0.00 sec)
    MariaDB [(none)]> drop database heatbeat;
    Query OK, 0 rows affected (0.48 sec)
    MariaDB [(none)]> exit
    
  3. 生きていると応答をする php スクリプトの配置

    /home/kusanagi/heartbeat/DocumentRoot/index.php
    <?php
    echo "It works!";
    
  4. hhvm の設定( /etc/hhvm/php.inihhvm.server.safe_file_access = true 時のみ)

    • /etc/hhvm/php.inihhvm.server.safe_file_access = true の行が あるときのみ
    • hhvm.server.safe_file_access = true 自身を追記する必要はありません

      /etc/hhvm/php.ini
      hhvm.server.safe_file_access = true
      hhvm.virtual_host[heartbeat[prefix] = heartbeat.selfnavi.com
      hhvm.virtual_host[heartbeat][overwrite][server][allowed_directories][] = /home/kusanagi/heartbeat/DocumentRoot
      
    • もし追記したのであれば

      # service hhvm restart
      
  5. 動作確認

    # service httpd reload
    # exit
    $ wget -qO - http://heartbeat.selfnavi.com
    It works
    

monit の設定

  1. 要らない設定ファイルを保存する disable ディレクトリを作成し、元の hhvm.conf を退避します。

    # cd /etc/monit.d
    # mkdir disable
    # mv hhvm.conf disable/hhvm.conf.orig
    
  2. /etc/monit.d には 500 エラーを検出するよう kusanagi が設定ファイルを作成しているはずですが、わたしは jetpack から監視しているのですべて disable ディレクトリにいれてます。 /etc/monit.d/hhvm.conf を上記の通り新規で作成し、結局、次のようになりました:

    # ls -la
    total 36
    drwxr-xr-x    3 root root   96 Jul 11 16:17 .
    drwxr-xr-x. 100 root root 8192 Jul 11 10:39 ..
    -rw-------    1 root root  351 Sep  6  2017 alert
    drwxr-xr-x    2 root root 4096 Jul 12 10:06 disable
    -rw-r--r--    1 root root  431 Jul 11 15:02 hhvm.conf
    -rw-------    1 root root  175 Mar  7  2017 httpd.conf
    -rw-r--r--    1 root root   51 Sep 13  2015 logging
    -rw- ------    1 root root  175 Mar  7  2017 nginx.conf
    
  3. apache を使っているので nginx を監視しないようにし、新しくいれた hhvm.conf を有効にします:

    # monit start all
    # monit unmonitor nginx
    # monit summary
    The Monit daemon 5.14 uptime: 19h 55m
    ↵
    Program 'nginx'                     Not monitored
    Program 'httpd'                     Status ok
    Remote Host 'heartbeathhvm'         Online with all services
    System 'kusanagi.cs2cloud.internal' Running
    

/etc/monit.d/hhvm.conf についての検証

動作について説明しておきましょう:

/etc/monit.d/hhvm.conf(再掲)
check host heartbeathhvm with address heartbeat.selfnavi.com
    start program =   "/bin/systemctl start hhvm.service"
    stop program =    "/bin/systemctl stop hhvm.service" with timeout 360 seconds
    depends on httpd
    if failed
        port 80
        protocol http
        and request /
        with content = "It works!"
        with timeout 3 seconds
    then restart
    if 3 restarts within 3 cycles then unmonitor
    group hhvm
  • monit で指定される間隔( /etc/monitrcdaemon パラメータ: 30 秒)で http://heartbeat.selfnavi.com/ にアクセスして、 It works! という文字列が 3 秒以内に応答されることを確認する。
    • もし、3 秒以内に It works! が戻ってこなければ、hhvm の再起動をかける
    • 500 エラーでも hhvm の再起動
    • hhvm の再起動は stop, start の順で、stop 時は 360 秒まつ。
  • 再起動を 3 回繰り返しても条件が満たさなければ、監視を停止する

stop program = "/bin/systemctl stop hhvm.service" with timeout 360

hhvm を停止させる際 /bin/systemctl stop hhvm.service が完了するまで最大 360 秒待ってくれるという意味です。 hhvm がなかなか止まってくれない状態を再現するために hhvm.service

ExecStopPost=/bin/sleep 50
TimeoutStopSec=360

を挟みます。全体は以下のようになります( TimeoutStopSec="infinity"あるらしい のですけど、認識してくれませんでした):

/etc/systemd/system/hhvm.service
[Unit]
Description=HHVM virtual machine, runtime, and JIT for the PHP language
Documentation=http://www.hhvm.com/
After=network.target remote-fs.target nss-lookup.target

[Service]
ExecStart=/usr/bin/hhvm --mode daemon
ExecStopPost=/bin/sleep 50
TimeoutStopSec=360

[Install]
WantedBy=multi-user.target

書き換えたら

# systemctl daemon-reload

index.phpIt works! 以外に 書き換えて再起動条件を作る。 with timeout 360 seconds のおかげで、再起動条件検出から hhvm の停止まで根気よく待ってくれる。

/home/kusanagi/heartbeat/DocumentRoot/index.php
<?php
echo "It works.";
/var/log/monit.log
[JST Jul 11 20:01:29] error    : 'heartbeathhvm' failed protocol test [HTTP] at [heartbeat.selfnavi.com]:80/ [TCP/IP] -- HTTP error: Regular expression doesn't match: No match
[JST Jul 11 20:01:29] info     : 'heartbeathhvm' trying to restart
[JST Jul 11 20:01:29] info     : 'heartbeathhvm' stop: /bin/systemctl

with timeout 360 seconds がなかったらどうなるか

/etc/systemd/system/hhvm.service(書き換え前)
stop program =    "/bin/systemctl stop hhvm.service" with timeout 360 seconds
/etc/systemd/system/hhvm.service(書き換え後)
stop program =    "/bin/systemctl stop hhvm.service"
# monit reload
Reinitializing monit daemon

結果だけみると、再起動してくれるのだが:

/var/log/monit.log
[JST Jul 11 20:57:14] error    : 'heartbeathhvm' failed protocol test [HTTP] at [heartbeat.selfnavi.com]:80/ [TCP/IP] -- HTTP error: Regular expression doesn't match: No match
[JST Jul 11 20:57:14] info     : 'heartbeathhvm' trying to restart
[JST Jul 11 20:57:14] info     : 'heartbeathhvm' stop: /bin/systemctl
[JST Jul 11 20:57:44] error    : 'heartbeathhvm' failed to stop (exit status -1) -- Program /bin/systemctl timed out
[JST Jul 11 20:58:14] error    : 'heartbeathhvm' failed protocol test [HTTP] at [heartbeat.selfnavi.com]:80/ [TCP/IP] -- HTTP error: Server returned status 503
[JST Jul 11 20:58:14] info     : 'heartbeathhvm' trying to restart
[JST Jul 11 20:58:14] info     : 'heartbeathhvm' stop: /bin/systemctl
[JST Jul 11 20:58:15] info     : 'heartbeathhvm' stopped
[JST Jul 11 20:58:15] info     : 'heartbeathhvm' start: /bin/systemctl

stop 時にデフォルトの待ち時間 30 秒を経過したところでタイムアウトが記録されている(動作解説 CONFIGURATION EXAMPLES のあたり)ここで monit は動作を打ち切り start はしない:

failed to stop (exit status -1) -- Program /bin/systemctl timed out

タイムアウト後再び monit から監視がはいるが hhvm は stop のまま、httpd は生きていているので 500 が戻ってくる

failed protocol test [HTTP] at [heartbeat.selfnavi.com]:80/ [TCP/IP] -- HTTP error: Server returned status 503

500 の応答を受けもう一度再起動がかかるが、hhvm は stop のままなのでタイムアウトにはならず、再起動に成功する。

with timeout 3 seconds

/etc/monit.d/hhvm.conf(抜粋)
    if failed
        port 80
        protocol http
        and request /
        with content = "It works!"
        with timeout 3 seconds

生死判定をするサイトに応答に 5 秒かけるよう細工を施す:

/home/kusanagi/heartbeat/DocumentRoot/index.php
<?php
sleep(5);
echo "It works!";

Resource temporarily unavailable として処理されるようです:

/var/log/monit.log
[JST Jul 11 16:18:24] error    : 'heartbeathhvm' failed protocol test [HTTP] at [heartbeat.selfnavi.com]:80/ [TCP/IP] -- HTTP: Error receiving data -- Resource temporarily unavailable
[JST Jul 11 16:18:24] info     : 'heartbeathhvm' trying to restart
[JST Jul 11 16:18:24] info     : 'heartbeathhvm' stop: /bin/systemctl
[JST Jul 11 16:19:16] info     : 'heartbeathhvm' start: /bin/systemctl

if 3 restarts within 3 cycles then unmonitor

3 回再起動(within 3 cycles)で 3 回リスタート条件を満たす(3 restarts)ときは監視からはずされる(unmonitor)。
index.phpIt works! 以外 に書き換えて再起動条件を作る。

/home/kusanagi/heartbeat/DocumentRoot/index.php
<?php
echo "It works.";
/var/log/monit.log
[JST Jul 11 21:51:33] error    : 'heartbeathhvm' failed protocol test [HTTP] at [heartbeat.selfnavi.com]:80/ [TCP/IP] -- HTTP error: Regular expression doesn't match: No match
[JST Jul 11 21:51:33] info     : 'heartbeathhvm' trying to restart
[JST Jul 11 21:51:33] info     : 'heartbeathhvm' stop: /bin/systemctl
[JST Jul 11 21:51:33] info     : 'heartbeathhvm' start: /bin/systemctl
[JST Jul 11 21:52:08] error    : 'heartbeathhvm' failed protocol test [HTTP] at [heartbeat.selfnavi.com]:80/ [TCP/IP] -- HTTP error: Regular expression doesn't match: No match
[JST Jul 11 21:52:08] info     : 'heartbeathhvm' trying to restart
[JST Jul 11 21:52:08] info     : 'heartbeathhvm' stop: /bin/systemctl
[JST Jul 11 21:52:08] info     : 'heartbeathhvm' start: /bin/systemctl
[JST Jul 11 21:52:43] error    : 'heartbeathhvm' failed protocol test [HTTP] at [heartbeat.selfnavi.com]:80/ [TCP/IP] -- HTTP error: Regular expression doesn't match: No match
[JST Jul 11 21:52:43] info     : 'heartbeathhvm' trying to restart
[JST Jul 11 21:52:43] info     : 'heartbeathhvm' stop: /bin/systemctl
[JST Jul 11 21:52:43] info     : 'heartbeathhvm' start: /bin/systemctl
[JST Jul 11 21:53:14] error    : 'heartbeathhvm' service restarted 3 times within 3 cycles(s) - unmonitor