地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【systemd】Type=notifyについて調べたメモ

systemdのカスタムサービスファイルを書くときにどのTypeで起動を判定するかのTypeでType=notifyについて調べたメモです。

github.com

Type=notifyとは

以下のような記載があります。フォアグラウウンドで実行を継続するデーモンで使えるType=simpleとほぼ同じようですが起動/停止などをsd_notify(3)というsystemd用の関数用いて通知を行います。ちなみに使用するにはsystemd-develをインストールする必要があります。

Type=notify: Type=simple と同じですが、利用可能になったときにデーモンが systemd に信号を送るように条件がつけられます。この通知のリファレンス実装は libsystemd-daemon.so によって提供されています。

参考: systemd - ArchWiki

simpleとの違い

simpleはコマンドを実行したタイミングで起動完了と判断します。極端な例ですが以下のようなソースでinit_service()でサービスの初期化処理をしてそこで失敗してデーモンが終了してもsystemctlのstartコマンドは正常終了となります。

#include <unistd.h>
#include <stdio.h>

int init_service()
{
        // config読んだり、他サービスから情報を取ってきたりしてデーモン起動準備をする。失敗する可能性もある
}

int main(int arg, char **argv)
{
        if(init_service())
            return 0;
        start_daemon();
}

これ自体は何が問題になるかというとinit_service()の結果を待たずにコマンドが戻ってきてしまうのでsystemctl start test && 前段のコマンドが成功している前提のコマンドみたいに&&を繋いだだけの処理は失敗する可能性が出てきてしまいます。(例えば何らかのデータストアを起動させつつクライアントアプリを起動するみたいなケース)

この話自体はそこまで問題にはならなそうでExecStartPre/Postを使うことでサービス起動前後にsleepなりコネクションの状態をloopで監視するスクリプトを入れることで解決はできる。解決策はいろいろありそうだがその中の一つである"Type=notify"を例に挙げて調べてみる。

Type=notifyを使うにはsd_notify(3)という関数を用いて起動が完了したことをsystemdに通知することができる。以下のようにsleep(30)の部分でsystemctlは戻らずに通知が来るまでコマンドが停止してくれる。

#include <unistd.h>
#include <stdio.h>
#include <systemd/sd-daemon.h>

int main(int arg, char **argv)
{
        sleep(30)
    if (sd_notify(0, "READY=1"))
        fprintf(stderr, "failed sd_notify");
    return 0;
}

これを入れることでsystemctl start test && systemctl start test2とか内部のユニットファイルでtest.serviceとtest2.serviceで依存関係を持たせてもtestのサービス起動通知はまではtest2は起動を待ってくれるようになる。パッと用途は思いつきませんでしたが起動以外にもステータスをsystemdに送信することができてSTOPPINGなどでプロセス停止中などを通知することも可能となります。

www.freedesktop.org

apache/httpdなんかでも利用されていて有効にしておくことでsystemctl statusの結果に統計情報を出力することが可能となるようです。こんな機能があるんですね〜という。

static int systemd_monitor(apr_pool_t *p, server_rec *s)
{    
    sd_notifyf(0, "READY=1\n"
               "STATUS=Total requests: %lu; Idle/Busy workers %d/%d;" // STATUSの部分がsystemctl statusに出力されるようになる
               "Requests/sec: %.3g; Bytes served/sec: %sB/sec\n",
               sload.access_count, sload.idle, sload.busy,
               ((float) sload.access_count) / (float) up_time, bps);

    return DECLINED;
}

どうやって通知してるのか

qiita.com

上記の記事にある通りUnixドメインソケットを用いた通信を行なっているようです。netstatgrepすると以下のようになっている。

$ netstat --protocol=unix |grep notify
unix  3      [ ]         DGRAM                    10138    /run/systemd/notify

httpdをtraceすると以下のような通信を行なっていることがわかった。sd_notify(3)なしで通信も容易にできそうですね。

sendmsg(8, {msg_name={sa_family=AF_UNIX, sun_path="/run/systemd/notify"}, msg_namelen=21, msg_iov=[{iov_base="READY=1\nSTATUS=Total requests: 2; Current requests/sec: 0; Current traffic:   0 B/sec\n", iov_len=86}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 86