PEARパッケージのSystem_Daemon は、作ったPHPプログラムをデーモンとして常駐して動かしてくれます。
なんで、LL言語のPHPをデーモンとして動かす必要があるのか、またはcron使えばいいじゃんってところはあるのですが、作者のページ にも書かれているように、開発が容易で高速に開発できるPHPの特性を活かしてデーモンとして動かしたい場合や、cronで動作させる場合は単体で処理が繰り返されるために前後の処理の状況をチェックするのが面倒、または不具合の原因にもなるからってことで、このPEARパッケージを開発したようです。
LAMP主体のサービスを構築する企業なんかは、PHPで作られた豊富なライブラリを持ってたりもするでしょうから、PHPの資産を活かしつつデーモンとして動かしたいプログラムを開発するって場合はいいかもしれませんね。
System_Daemonのインストール
2010年04月29日現在、System_Daemonのバージョンは0.10.2でベータ版として提供されているため、pearコマンドでインストールする場合は、パッケージ名の最後に「-beta」を付けてインストールする必要があります。
# pear install System_Daemon-beta WARNING: channel "pear.php.net" has updated its protocols, use "channel-update pear.php.net" to update Did not download optional dependencies: pear/Log, use --alldeps to download automatically pear/System_Daemon can optionally use package "pear/Log" (version >= 1.0) downloading System_Daemon-0.10.2.tgz ... Starting to download System_Daemon-0.10.2.tgz (31,154 bytes) .........done: 31,154 bytes install ok: channel://pear.php.net/System_Daemon-0.10.2
メッセージの途中で出ているPEARのLogパッケージは特に無くても動作します。
その場合、独自実装のログ出力になる模様。
System_Daemonを利用してPHPプログラムをデーモン化してみる
では、早速PHPのデーモンプログラムを書いてみます。
#!/usr/bin/php <?php require_once 'System/Daemon.php'; // デーモンの名前 $app_name = "date_deamon"; $options = array("appName" => $app_name, // デーモンの名前 "authorEmail" => "foo@example.com", // 作者のE-Mail "appDir" => dirname(__FILE__), // アプリを動かすディレクトリ "logLocation" => dirname(__FILE__) . "/" . $app_name . ".log", // アプリのログのパス(デフォルトだと/var/log/{アプリ名}.log) "appPidLocation" => dirname(__FILE__) . "/" . $app_name . "/" . $app_name . ".pid", // プロセスIDのパス(デフォルトだと/var/run/{アプリ名}/{アプリ名}.pid) "appRunAsUID" => 500, "appRunAsGID" => 500); // 上記オプションをまとめてセット System_Daemon::setOptions($options); // デーモンの起動 System_Daemon::start(); // これ以降の処理がデーモンとして動作させたい処理 // デーモンが生きてるかチェックしながらループ while (!System_Daemon::isDying()) { // アプリのログに日付をはき続ける System_Daemon::log(System_Daemon::LOG_INFO, date('Y/m/d H:i:s')); // SLEEP処理(5秒待機) System_Daemon::iterate(5); } System_Daemon::stop();
デーモンとして動かすほどのプログラムでもないのですが、これを起動させてみてプロセスを表示してみると常駐して動作していることがわかります。
$ php date_deamon.php [Apr 30 01:51:39] notice: Starting date_deamon daemon, output in: '/home/work/workspace/date_deamon.log' $ ps aux | grep date_deamon.php work 21997 0.0 0.7 22172 3908 pts/0 S 01:51 0:00 php date_deamon.php
実行結果は、「System_Daemon::setOptions」の"logLocation"で指定したログファイルの中に日付が吐き出され続けます。
[Apr 30 01:55:34] info: 2010/04/30 01:55:34 [Apr 30 01:55:39] info: 2010/04/30 01:55:39 [Apr 30 01:55:44] info: 2010/04/30 01:55:44 [Apr 30 01:55:49] info: 2010/04/30 01:55:49
停止したい場合は、killコマンドを使い、その後にプロセス番号が書かれているpidファイルを削除しておきます。
$ kill -9 21997 $ rm date_deamon/date_deamon.pid
今回の例では、uidとgidが500のユーザーでデーモンを起動しています。
それぞれ、「System_Daemon::setOptions」の"appRunAsUID"および"appRunAsGID"で指定するわけですが、これを指定しなかった場合、System_Daemonははrootユーザーでデーモンを実行させようとします。
この時、プロセスIDを格納するためのpidファイルの所有者およびグループをrootユーザーにしようとするため、エラーが出てデーモンが起動できません。
[Apr 30 00:42:46] err: Unable to change group of file '/home/work/workspace/date_deamon' to 0 [l:1427]
rootユーザー以外でデーモンを起動したい場合は、必ず"appRunAsUID"および"appRunAsGID"オプションを指定しておく必要があります。
また、ログファイルやpidファイルのデフォルトの保存場所も「/var/log」や「/var/run」となるため、root以外のユーザーで実行する場合は、適宜権限のあるパスを指定しておきます。
ループ処理の中で、System_Daemon::iterateで5秒間待機させていますが、これはPHPのsleep関数で代替できそうですが、実際にはclearstatcache を呼び出すなど余分なキャッシュなどの掃除をしてくれるみたいです。
参考: 常駐プロセス(デーモン)として動くアプリを自作する(PHPとPEAR:: System_Daemon編) @ Web屋のネタ帳
ディスク容量をチェックするデーモン
もう少し実用的なものにするため、指定のディレクトリの容量をチェックするデーモンを作ってみます。
#!/usr/bin/php <?php require_once 'System/Daemon.php'; // 監視対象ディレクトリ $check_dir = "/home/work/workspace/"; // 閾値(MB) $limit = 100; System_Daemon::setOption("appName", "diskcheck"); System_Daemon::setOption("authorEmail", "foo@example.com"); // デーモンの説明 System_Daemon::setOption("appDescription", "Disk Check Daemon"); // 作者の名前 System_Daemon::setOption("authorName", "itboy"); System_Daemon::start(); System_Daemon::log(System_Daemon::LOG_INFO, "Disk-Check-Daemon started"); while (!System_Daemon::isDying()) { $disk_size = getDirSize($check_dir); if ($disk_size > ($limit * 1024 * 1024)) { System_Daemon::log(System_Daemon::LOG_WARNING, $check_dir . " over " . $limit . "MB !!" . " / size : " . number_format($disk_size) . "Byte"); } else { System_Daemon::log(System_Daemon::LOG_INFO, $check_dir . " size : " . number_format($disk_size) . "Byte"); } System_Daemon::iterate(5); } System_Daemon::stop(); // ディレクトリの容量をチェックする関数(参照: http://itpro.nikkeibp.co.jp/article/COLUMN/20070827/280408/ function getDirSize($path) { $total_size = 0; if (is_file($path)) { return filesize($path); } elseif (is_dir($path)) { $basename = basename($path); if ($basename == '.' || $basename == '..') { return 0; } $file_list = scandir($path); foreach ($file_list as $file) { $total_size += getDirSize($path .'/'. $file); } return $total_size; } else { return 0; } }
今回は、rootユーザーで実行する前提にしたので、オプションの数が少なくなっています。
※ "appDescription"と"authorName"のオプションを追加してますが、自動起動スクリプトの作成(後述)のためです。
オプションは配列でまとめて指定も出来ますが、今回のように「System_Daemon::setOption」で一つずつオプションを指定することも可能です。
ディレクトリ内のファイル容量をチェックする関数は、「54. 再帰関数を使ってみよう @ ITpro 」のものを使わせていただきました。
動作原理もそれほど複雑ではなく、getDirSize関数で指定のディレクトリ内のファイルの容量をチェックし、閾値に設定している10MBを超えた場合は、LOG_WARNINGでアラートをログに出すようにしています。
実際にはメールを飛ばすなんかの手段の方がよいかもしれませんね。
# php disk_check.php [May 01 01:02:27] notice: Starting diskcheck daemon, output in: '/var/log/diskcheck.log' # ps aux | grep disk_check.php root 25752 0.0 0.7 22172 4020 pts/0 S 01:02 0:00 php disk_check.php
この時に、容量の大きいファイルを作ってみて閾値を超える状態を作り出してみます。
$ dd if=/dev/zero of=largefile.dmp bs=1M count=100 100+0 records in 100+0 records out 104857600 bytes (105 MB) copied, 0.197439 seconds, 531 MB/s
この一連の流れをログで確認してみると・・・
[May 01 01:06:38] info: /home/work/workspace/ size : 55,772Byte [May 01 01:06:43] info: /home/work/workspace/ size : 55,772Byte [May 01 01:06:48] info: /home/work/workspace/ size : 55,772Byte [May 01 01:06:53] warning: /home/work/workspace/ over 100MB !! / size : 104,913,372Byte [l:23] [May 01 01:06:58] warning: /home/work/workspace/ over 100MB !! / size : 104,913,372Byte [l:23] [May 01 01:07:03] info: /home/work/workspace/ size : 55,772Byte [May 01 01:07:08] info: /home/work/workspace/ size : 55,772Byte
途中で閾値の100MBの容量を超えたため、warningがログに吐き出され、容量の大きいファイルを削除した後は単なる情報としてディスクのサイズをログに書き出すようになっています。
作成したSystem_DaemonのPHPプログラムを自動起動スクリプトに登録する
System_Daemonの面白い機能の一つに、自動起動スクリプト(/etc/init.d)へ登録してくれるものがあります。
自動起動スクリプトに登録するためには、幾つかの準備が必要です。
1. オプションに"appName"、"authorEmail"、"appDescription"、"authorName"を指定する
2. System_Daemon::start();の後に、System_Daemon::writeAutoRun();の記述を追加する
System_Daemon::start(); $path = System_Daemon::writeAutoRun();
3. 実行するPHPスクリプトに実行権限を付与しておく
# chmod 755 disk_check.php
これで、disk_check.phpを一度実行させれば「/etc/init.d」ディレクトリ以下に自動起動スクリプトが作成されます。
ここから、起動させたければ
# /etc/init.d/diskcheck start [May 01 01:41:09] notice: Starting diskcheck daemon, output in: '/var/log/diskcheck.log' Starting diskcheck: [ OK ]
とすれば、起動してくれます。
こちらの方がプロセス番号を見てkillしたり、pidファイルを掃除する必要も無いので楽でしょうね。
サーバー起動時に自動起動させたければ、
# chkconfig --add diskcheck # chkconfig --level 35 diskcheck on # chkconfig --list | grep diskcheck diskcheck 0:off 1:off 2:off 3:on 4:off 5:on 6:off
としておけば、幸せかもしれません。
[PR]固定IPが【業界最安値クラス】+【5分で発行】+【2ヶ月無料体験】
[PR]光ファイバー/ADSLを楽しむなら「安心」「安全」の@nifty!
関連記事
[Composer] PHPのパッケージ管理にComposerを使う
コードレビューに便利!PHPの重複コードを見つけてくれるPHPCPD
[PHP] 余計なHTMLタグや属性を消してくれるHTML Purifier
PHP_CodeSnifferでコーディング規約に準拠しているかチェックをする
[PHP] デーモンとして動かせるTwitter botの作り方
ソケット通信でサーバーの状況を監視する
[PEAR] オリジナルのWikiエンジンを作れるText_Wiki
[PHP] inotify関数を使ってログを監視するスクリプトを作ろう