CentOS 5をプログラミング環境で使う

サービス(デーモン)の設定

サービス(デーモン)

OS起動時に、自動起動してある種の機能を果たすプログラム、例えばWebサーバやデータベース管理システムなどの起動・停止に関する設定についてのページです。

デーモン・プログラム

普通に作成するプログラムは、ターミナルと結びついており、プログラムの標準入力・標準出力はそのプログラムが起動したターミナルとなっています。ターミナルを終了すると、ターミナル上で動いていたプログラムも(バックグラウンドで実行していたとしても)終了します。

一方、特定のターミナルとは結びつかずに、OSが稼動している間、ずっと動いているプログラムも必要です。この種のプログラムは以前は「デーモン・プログラム(daemon program)」と呼ばれていました。デーモンは特定のコンソールを持たないため、標準入力・標準出力は通常不要で(外部とデータをやり取りする手段としては使わない)、ソケット等のプロセス間通信手段により外部とのデータ入出力を行います。CentOSでは、この種のプログラムをサービスと呼んでおり、サービスの起動・終了設定について専用の仕組みを提供しています。

デーモン・プログラムの作成

ターミナルからの切り離し

簡単なのは、daemon関数を呼ぶことです。daemon関数を呼ぶと、自らのプロセスをfork/execして端末から切り離します。daemon関数の引数で、カレントディレクトリを/にする、標準出力・標準エラー出力・標準入力を切り離す指定ができます。

    #include <unistd.h>
        :
    int ret = daemon(0, 0); 

シグナルHUPのハンドラで設定ファイルの再読み込み

必須ではありませんが、通常デーモン・プログラムはシグナルHUPを受け取ると、設定ファイルを再読み込みし、新しい設定で動作を始めます。

マルチスレッドでシグナルを扱うときは、シグナルハンドラを登録してコールバックで動かすのではなく、シグナルを受け取るブロッキングコールを専用のスレッドで行うのが常套手段です。このとき、sigwait()を使います。

#include <signal.h>
#include <boost/thread.hpp>
    : 
void wait_sighup(sigset_t sigset);

namespace {
// シグナルHUP発生時のスレッド間通知用変数とメモリバリア同期用mutex
bool isReload = false;
boost::recursive_mutex reload_mutex;
}

int main() {   
        :
    sigset_t hupset;
    sigemptyset(&hupset);
    sigaddset(&hupset, SIGHUP);
    sigprocmask(SIG_BLOCK, &hupset, 0);

    boost::thread thr_wait_sighup(wait_sighup, hupset);
        :
}

void wait_sighup(sigset_t sigset) {
    int sig;
    while (true) {
        reload_mutex.lock();
        if (isReload) {
            reload_mutex.unlock();
            break;
        }
        reload_mutex.unlock();

        int err = sigwait(&sigset, &sig);
        syslog(LOG_INFO, "Signal catched: %d", sig);

        if (err || sig != SIGHUP)
            break;
        reload_mutex.lock();
        isReload = true;
        reload_mutex.unlock();
    }
}

ログ出力

標準出力・標準エラー出力がないので、外部へ出力するメッセージはログに出します。独自のログに出力しても、システムロガーに出力してもよいでしょう。

syslogへの出力
#include <syslog.h>

    openlog("hoged", LOG_CONS | LOG_PID, LOG_DAEMON);
    syslog(LOG_CRIT, "Command line option parse.");
    closelog();

openlog()は、名前、オプション、ファシリティを指定します。

syslog()は、レベル、メッセージを指定します。メッセージはprintf様式の書式指定です。

アプリケーションを作成していて、オプションで指定するのは、LOG_CONS, LOG_PERROR, LOG_PIDあたりでしょう。
ファシリティは、LOG_DAEMON、LOG_USERあたりでしょうか。
レベルは、LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG が選択可能です。

サービスの管理

CentOSのサービス管理は、それぞれのサービスごとに、所定の様式でサービス起動/終了スクリプトを作成し、所定の場所に置くことが前提となります。サービス管理コマンドは、このスクリプトをOSのランレベル毎に有効・無効にすることでサービスの管理を行います。

サービス起動/終了スクリプト

CentOSのサービス起動/終了スクリプトの様式について、以下にメモします。

基本となるスクリプト様式

スクリプト様式
#!/bin/sh
# <デーモン名> <概要>
# chkconfig: <ランレベル指定> <起動優先度> <停止優先度>
# description: <サービス内容を記述、複数行に渡る場合は\
#               このように各行の末尾に\を記述する>
# processname: <プロセス名>
# config: <設定ファイルのパス>
# pidfile: <プロセスIDを保存するファイルパス>

. /etc/rc.d/init.d/functions

prog="<サービス名>"
lockfile=/var/lock/subsys/$prog
config="<設定ファイルのパス>"

start() {
    echo -n $"Starting $prog: "
    daemon <daemonオプション> <実行ファイルのパス> <実行オプション>
    RETVAL=$?
    [ $RETVAL -eq 0 ] && touch $lockfile
    return $RETVAL
}

stop() {
    echo -n $"Stopping $prog: "
    killproc $prog
    RETVAL=$?
    [ $RETVAL -eq 0 ] && rm -f $lockfile
    return $RETVAL
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status $prog
        RETVAL=$?
        ;;
    restart)
        stop
        start
        ;;
    condrestart)
        if [ -f $lockfile ]; then
            stop
            start
        fi
        ;;
    reload)
        action $"Reloading $prog: "
        ;;
    *)
        echo $"Usage: $0 {start|stop|restart|reload|condrestart|status}"
        exit 1
esac

exit $RETVAL

    
デーモン名
スクリプトファイル名と一致させる。chkconfigで管理する際の名称となる。
ランレベル指定
サービスを起動するランレベルを空白を空けずに続けて指定する。'-'を指定すると、2345を指定したことになる。
サービス内容を記述
サービスが何をするものかを記述する。複数行で記述する場合、行末に\を付ける。
起動優先度
他のサービスと起動する順序の制御を整数2桁で指定する。01-99
停止優先度
他のサービスと停止する順序の制御を整数2桁で指定する。通常、100から起動優先度を引いた値を指定する。
プロセス名
省略可。デーモン・プロセスを実行する実行ファイルのパスを記述する。
設定ファイルのパス
個々で記述した設定ファイルを更新すると、Linuxはこのサービスを再起動する。
プロセスIDを保存するファイルパス
デーモン・プロセスのプロセスIDを保存するファイルを指定する。
サービス名
デーモン名と同じく、スクリプトファイル名と一致させる。
daemon オプション
/etc/rc.d/init.d/functionsで定義されるdaemonシェル関数のオプションを記述する。
--user: デーモンをroot権限ではなく特定のユーザ権限で実行する場合に指定する。
--check:
--pidfile: サービス二重起動をチェックする場合に指定する。(実行ファイルでpidファイルを生成している必要がある。)
--force:
[-+][0-9]*: ナイス値の増減指定
実行ファイルのパス
デーモン・プロセスの実行ファイルパスを記述する。
実行オプション
デーモン・プロセスのコマンドライン引数を記述する。

echo の $"..." は、ロケール変換文字列で、この場合、"Starting"や"Stopping"、"Reloading"がロケールに応じた文字列に変換されます。

daemonは、/etc/rc.d/init.d/functionsで定義される関数です。オプションに指定した実行ファイルを起動し、成否に応じてコンソールに結果を表示します。いくつかオプションがありますが、使うのは--userが多いでしょう。--pidfileを使うには、デーモン・プログラム側が起動したときに自身のプロセスIDを取得し所定のファイルに保存するよう作られている必要があります。

killprocは、/etc/rc.d/init.d/functionsで定義される関数です。オプションに指定したプロセス名のプロセスに対してシグナル(SIGKILL)を発行して終了させます。

同じプロセス名が複数存在する場合、killprocで終了できない

javaやperl, pythonなどのスクリプト言語は、実行プロセス名がjava/perl/pythonとなるため、killprocで終了することができません。この場合、psコマンドでプロセス名とプロセス起動時の引数名を含めて検索し、対象プロセスのプロセスIDを調べて終了させます。

stop() {
    echo -n $"Stopping $prog: "
    PID=`ps ax|grep hudson.war|grep -v grep|awk '{print $1}'`
    if [ -z $PID ]; then
        failure $"prog stop"
        return
    fi
    kill $PID
    RETVAL=$?
    if [ $RETVAL = 0 ]; then
        rm -f ${lockfile}
        success $"$prog stop"
    else
        failure $"$prog stop"
    fi
}

failure および success は、/etc/rc.d/init.d/functionsで定義される関数です。起動結果の成否を表示します。