「C++の応用 - Systemd」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

ページの作成:「== 概要 == サービスとは、PCの起動時に自動的に実行され、バックグラウンドで仕事をするために待機するソフトウェアのことである。<br> <br> 一般的に、サービスはグラフィカルユーザインターフェースを持たず、ユーザの操作無しに動作する。<br> 最もよく知られているサービスは、Web、メール、データベース等のサーバで、Apache、MySQL等がある。<br…」
 
編集の要約なし
 
(同じ利用者による、間の1版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
サービスとは、PCの起動時に自動的に実行され、バックグラウンドで仕事をするために待機するソフトウェアのことである。<br>
Systemdは、Linuxにおける代表的なシステムおよびサービスマネージャであり、起動、停止、依存関係管理、ログ収集、リソース制御を一元的に扱う。<br>
<br>
<br>
一般的に、サービスはグラフィカルユーザインターフェースを持たず、ユーザの操作無しに動作する。<br>
C++からSystemdを利用する場合、最も重要な入口は<u>libsystemd</u>である。<br>
最もよく知られているサービスは、Web、メール、データベース等のサーバで、Apache、MySQL等がある。<br>
libsystemdはC APIを提供するライブラリであり、純粋なC++アプリケーションから直接利用できる。<br>
<br>
<br>
また、ハードウェアの検出やUSBメモリの自動統合(マウント)等もサービスによって行われる。<br>
代表的なヘッダには、<u>sd-bus.h</u>、<u>sd-event.h</u>、<u>sd-journal.h</u>、<u>sd-daemon.h</u>がある。<br>
これらを組み合わせることで、ユニット操作、イベントループ統合、journaldへの構造化ログ出力、<code>Type=notify</code>サービスとの連携を実装できる。<br>
<br>
<br>
サービスには、システム起動時に関連するタスクやハードウェアに関連するタスクを行う"内部サービス"と、<br>
Systemd Managerと通信する場合、通常はD-Bus経由で <u>org.freedesktop.systemd1</u> にアクセスする。<br>
その後にユーザがインストールするサービス(通常は全てのサーバサービスを含む)の2種類がある。<br>
このとき、サービスの開始や停止は単純なシェルコマンド実行ではなく、<code>StartUnit</code> や <code>StopUnit</code>等のメソッド呼び出しとして扱う。<br>
<br>
<br>
技術用語やコンピュータ用語では、サービスは伝統的にデーモンと呼ばれている。<br>
特に重要なのは、<code>StartUnit</code> の戻り値が「サービスが完全に起動した」という意味ではなく、ジョブが作成されたことを示す点である。<br>
そのため、サーバコンポーネントであるsshdやmysqldのように、サービスを表すプログラムの最後の文字として"d"が用いられることが多い。<br>
実際の状態確認には、ジョブの監視や <code>ActiveState</code> プロパティの確認が必要になる。<br>
<br>
<br>
一方、Systemdは、システムおよびセッションマネージャ(initシステム)であり、<br>
また、Systemdはシステムユニットだけでなく、ユーザごとのユーザユニットも管理できる。<br>
コンピュータの起動プロセスからシャットダウンまでの全動作時間にわたって、システム上で動作するすべてのサービスを管理する役割を担っている。<br>
一般ユーザのサービスを制御する場合は <code>sd_bus_open_user()</code>、システム全体のユニットを制御する場合は <code>sd_bus_open_system()</code> を使い分ける。<br>
<br>
<br>
プロセスは常に(可能な限り)並行して起動され、起動プロセスを可能な限り短くする。<br>
近年のlibsystemdでは、<code>sd-json</code> や <code>sd-varlink</code> 等の公開APIが追加され、イベントループや通知機能も強化されている。<br>
ここで、.serviceで終わる設定ファイルを作成して、Systemdが制御・監視するプロセスに関するコードを保持する場合をSystemd Service Unitファイルと呼ぶ。<br>
一方で、実務で最も利用頻度が高いのは、依然として <code>sd-bus</code>、<code>sd-event</code>、<code>sd-journal</code>、<code>sd_notify</code>である。<br>
<br>
<br>
Systemdには、サービス、タイマ、マウントポイント、ソケット、スワップスペース、デバイス等のユニットが存在する。<br>
C++でlibsystemdを使う場合は、GUIフレームワークに依存せず、<code>std::unique_ptr</code> や 専用デリータを使用してRAIIでリソース管理する構成が扱いやすい。<br>
そのため、Systemdは管理用の設定の全てをファイルから取得する。<br>
また、ビルド時は <code>pkg-config --cflags --libs libsystemd</code> を使用するのが最も安全である。<br>
<br>
Systemdの用語では、これらを"ユニット"と呼び、システム全体に適用されるユニットと各ユーザ領域にのみ適用されるユニットがある。<br>
<br>
ユニットには、サービスを開始するためのサービスユニットや、ある時点でのアクションを(繰り返し)実行するためのタイマユニット等、様々な種類がある。<br>
<br>
各タイプのユニットファイルに共通しているのは、iniファイルに似た構造をしていることである。<br>
ユニットファイルは、いくつかのセクション(多くの場合、3セクション)で構成されている。<br>
<br>
Systemdではセクションと呼ばれ、その中に一連のキーと値のペア(Systemdではディレクティブと呼ばれる)が格納されている。<br>
<br><br>
<br><br>


== Systemdライブラリのインストール ==
== libsystemdの導入 ==
==== ライセンス ====
==== ライセンス ====
Systemdライブラリのライセンスは、LGPL 2.1以降、または、 GPL 2.0で利用可能である。<br>
通常のアプリケーションがリンクする<u>libsystemd</u>は、主としてLGPL 2.1以降の条件で利用できる。<br>
ただし、systemdのソースツリー全体にはGPL系コンポーネントも含まれるため、再配布方針が厳密な環境では公式ライセンス表記も確認すること。<br>
<br>
<br>
==== パッケージ管理システムからインストール ====
==== パッケージ管理システムからインストール ====
  # RHEL
  # RHEL
45行目: 37行目:
<br>
<br>
==== ソースコードからインストール ====
==== ソースコードからインストール ====
最新のupstream機能を利用する場合は、ソースコードからビルドする。<br>
ただし、依存ライブラリが多く、ディストリビューションのBuildRequires相当のパッケージが必要になるため、通常は配布パッケージの利用を優先する。<br>
<br>
Systemdのビルドに必要なライブラリをインストールする。<br>
Systemdのビルドに必要なライブラリをインストールする。<br>
  sudo zypper install meson ninja python3-Jinja2 glib2-devel dbus-1-devel p11-kit-devel libarchive-devel pcre2-devel libcurl-devel libcap-devel \
<br>
                    libmount-devel libfdisk-devel libblkid-devel libdw-devel libpwquality-devel passwdqc-devel libkmod-devel libbpf-devel \
# RHEL
                    zlib-devel liblz4-devel libzstd-devel xz-devel libbz2-devel \
sudo dnf install -y epel-release
                     pam-devel libgnutls-devel libopenssl-devel libopenssl-1_1-devel libcryptsetup-devel libgcrypt-devel libgpg-error-devel \
sudo dnf config-manager --set-enabled crb
                    qrencode-devel libiptc-devel libidn2-devel libmicrohttpd-devel \
sudo dnf install meson ninja-build python3-jinja2 glib2-devel dbus-devel p11-kit-devel libarchive-devel pcre2-devel        \
                  libcurl-devel libcap-devel libmount-devel libfdisk-devel libblkid-devel elfutils-devel libpwquality-devel \
                  kmod-devel libbpf-devel zlib-ng-compat-devel lz4-devel libzstd-devel xz-devel bzip2-devel iptables-devel  \
                  pam-devel gnutls-devel openssl-devel cryptsetup-devel libgcrypt-devel libgpg-error-devel \
                  libmicrohttpd-devel libxkbcommon-devel libfido2-devel tpm2-tss-devel libseccomp-devel    \
                  libacl-devel audit-libs-devel libselinux-devel
# SUSE
  sudo zypper install meson ninja python3-Jinja2 glib2-devel dbus-1-devel p11-kit-devel libarchive-devel pcre2-devel         \
                    libcurl-devel libcap-devel libmount-devel libfdisk-devel libblkid-devel libdw-devel libpwquality-devel \
                    passwdqc-devel libkmod-devel libbpf-devel zlib-devel liblz4-devel libzstd-devel xz-devel libbz2-devel \
                     pam-devel libgnutls-devel libopenssl-devel libopenssl-1_1-devel libcryptsetup-devel libgcrypt-devel   \
                    libgpg-error-devel qrencode-devel libiptc-devel libidn2-devel libmicrohttpd-devel           \
                     libxkbcommon-devel libfido2-devel tpm2-0-tss-devel libseccomp-devel libacl-devel audit-devel \
                     libxkbcommon-devel libfido2-devel tpm2-0-tss-devel libseccomp-devel libacl-devel audit-devel \
                     libapparmor-devel  # AppArmorを使用する場合
                     libapparmor-devel  # AppArmorを使用する場合
56行目: 64行目:
                     xen-devel          # Xenを使用する場合
                     xen-devel          # Xenを使用する場合
<br>
<br>
[https://github.com/systemd/systemd SystemdのGithub]にアクセスして、ソースコードをダウンロードする。<br>
次に、[https://github.com/systemd/systemd SystemdのGithub]にアクセスして、ソースコードをダウンロードする。<br>
ダウンロードしたファイルを解凍する。<br>
ダウンロードしたファイルを解凍する。<br>
<br>
  tar xf systemd-<バージョン>.tar.gz
  tar xf systemd-<バージョン>.tar.gz
  cd systemd-<バージョン>
  cd systemd-<バージョン>
<br>
または、<code>git clone</code> コマンドを実行して、ソースコードをダウンロードする。<br>
<br>
git clone https://github.com/systemd/systemd.git
cd systemd
<br>
<br>
Systemdをビルドおよびインストールする。<br>
Systemdをビルドおよびインストールする。<br>
meson --prefix=<Systemdのインストールディレクトリ> -Dmode=release build
ninja -C ./build -j $(nproc)
ninja -C ./build install
<br><br>
== サンプルコード ==
<code>QProcess</code>クラスを使用せずに、Systemdを利用してSSHデーモンを起動する場合は、Systemdのライブラリを使用する必要がある。<br>
<br>
<br>
以下の例では、Systemdのライブラリを使用してSSHデーモンを起動している。<br>
meson setup build -Dmode=release --prefix=<Systemdのインストールディレクトリ>
# <code>sd_bus_open_system</code>関数を使用して、システムバスへの接続を開く。
meson compile -C build
#: 戻り値が負の値の場合は、接続に失敗したことを示す。
sudo meson install -C build
# <code>sd_bus_call_method</code>関数を使用して、SystemdのD-Busインターフェイスを介してsshd.serviceを起動する。
#: 関数の引数には、バスの接続、D-Busサービス名、D-Busオブジェクトのパス名、インターフェイス名、メソッド名、入力引数、出力引数、起動するサービス名とモードを指定する。
#: 戻り値が負の値の場合は、サービスの起動に失敗したことを示す。
#: サービスの起動に成功した場合は、成功メッセージをデバッグ出力に表示している。
# <code>sd_bus_unref</code>関数を使用して、バスの接続を閉じる。
<br>
<br>
また、RAIIパターンを使用することにより、例外が発生した場合でもリソースが確実に解放されることを保証している。<br>
libsystemdの利用だけが目的であれば、Systemd本体をソースから導入するよりも、パッケージ管理システムの開発パッケージ用いる方が保守しやすい。<br>
<br>
<br>
このサンプルコードを実行するには、Systemdライブラリがインストールされている必要がある。<br>
==== CMakeファイルの設定 ====
また、適切な権限でプログラムを実行する必要がある。<br>
C++からlibsystemdを利用する場合、<code>pkg-config</code> 経由で検出する構成が最も安全である。<br>
<br>
<br>
<u>※注意</u><br>
<u>この方法は、Systemdが利用可能なシステムでのみ動作する。</u><br>
<br>
* CMakeを使用する場合
  <syntaxhighlight lang="cmake">
  <syntaxhighlight lang="cmake">
  # Package Configの使用
  cmake_minimum_required(VERSION 3.21)
project(systemd_cpp_example LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
  find_package(PkgConfig REQUIRED)
  find_package(PkgConfig REQUIRED)
# Systemdライブラリの使用
  pkg_check_modules(SYSTEMD REQUIRED libsystemd)
  pkg_check_modules(SYSTEMD REQUIRED libsystemd)
   
   
  ## Systemdライブラリのバージョンを指定する場合
  add_executable(systemd_cpp_example
  #pkg_check_modules(SYSTEMD REQUIRED libsystemd >= <バージョン>)
    main.cpp
  )
   
   
  target_include_directories(<your_target> PUBLIC
  target_include_directories(systemd_cpp_example PRIVATE
    # ...略
     ${SYSTEMD_INCLUDE_DIRS}
     ${SYSTEMD_INCLUDE_DIRS}
  )
  )
   
   
  target_link_libraries(<your_target>
  target_link_libraries(systemd_cpp_example PRIVATE
    # ...略
     ${SYSTEMD_LIBRARIES}
     ${SYSTEMD_LIBRARIES}
)
target_compile_options(systemd_cpp_example PRIVATE
    ${SYSTEMD_CFLAGS_OTHER}
  )
  )
  </syntaxhighlight>
  </syntaxhighlight>
<br>
==== 主要ヘッダと用途 ====
<center>
{| class="wikitable"
|+ libsystemdの主要ヘッダ
! ヘッダ !! 主な用途
|-
| <code>&lt;systemd/sd-bus.h&gt;</code> || Systemd Managerや他のD-Busサービスとの通信
|-
| <code>&lt;systemd/sd-event.h&gt;</code> || epollベースのイベントループ、タイマ、シグナル、I/O監視
|-
| <code>&lt;systemd/sd-journal.h&gt;</code> || journaldへのログ出力、ジャーナルの読み出し
|-
| <code>&lt;systemd/sd-daemon.h&gt;</code> || <code>sd_notify()</code>、ウォッチドッグ、ソケットアクティベーション支援
|-
| <code>&lt;systemd/sd-id128.h&gt;</code> || 128ビットIDの生成と管理
|-
| <code>&lt;systemd/sd-login.h&gt;</code> || ログインセッションやseat情報の取得
|}
</center>
<br><br>
== libsystemdの主要なAPI ==
C++からSystemdを扱う場合、最初に理解しておくべきAPI群を整理する。<br>
<br>
<center>
{| class="wikitable"
|+ 純粋なC++からよく使うlibsystemd API
! 分類 !! 主なAPI !! 用途
|-
| Systemd Manager操作 || <code>sd_bus_open_system()</code><br><code>sd_bus_call_method()</code><br><code>sd_bus_add_match()</code> || ユニットの起動・停止・プロパティ監視
|-
| イベントループ || <code>sd_event_default()</code><br><code>sd_event_run()</code><br><code>sd_bus_attach_event()</code> || 非同期イベント処理とD-Bus統合
|-
| journald出力 || <code>sd_journal_print()</code><br><code>sd_journal_send()</code> || 通常ログと構造化ログの送信
|-
| サービス通知 || <code>sd_notify()</code><br><code>sd_watchdog_enabled()</code> || <code>Type=notify</code>サービスの状態通知
|-
| ジャーナル読み出し || <code>sd_journal_open()</code><br><code>sd_journal_next()</code><br><code>sd_journal_get_data()</code> || ログの追跡と解析
|}
</center>
<br><br>
== Systemd Managerとの通信 ==
==== 基本情報 ====
Systemd Managerは、D-Bus上で以下のサービスを提供する。<br>
* サービス名
*: <code>org.freedesktop.systemd1</code>
* オブジェクトパス
*: <code>/org/freedesktop/systemd1</code>
* 主要インターフェース
*: <code>org.freedesktop.systemd1.Manager</code>
<br>
ユニットを起動する代表的なメソッドは、<code>StartUnit</code> である。<br>
停止は <code>StopUnit</code>、再起動は <code>RestartUnit</code>、リロードは <code>ReloadUnit</code> を使用する。<br>
<br>
==== ジョブモード ====
Systemd Managerのユニット操作では、しばしば「ジョブモード」を同時に指定する。<br>
<br>
* <code>replace</code>
*: 既存の競合ジョブを置き換えて実行する。通常はこの値を使う。
* <code>fail</code>
*: 競合ジョブが存在する場合は失敗する。
* <code>isolate</code>
*: 対象ユニットとその依存を残し、他を停止する。
* <code>ignore-dependencies</code>
*: 依存関係を無視する。
* <code>ignore-requirements</code>
*: requirement関係を無視する。<br>
<br>
<u><code>ignore-dependencies</code> および <code>ignore-requirements</code> は、通常のアプリケーションから安易に使うべきではない。</u><br>
<br>
==== C++によるサービス起動例 ====
以下の例では、システムバスへ接続し、sshd.serviceに対して <code>StartUnit</code> を呼び出す。<br>
<br>
この例が返す <u>ジョブオブジェクトパス</u> は、開始要求が受理されたことを示すものであり、サービスが完全に起動済みであることを意味しない。<br>
<br>
<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
#include <systemd/sd-bus.h>
#include <cstring>
  #include <iostream>
  #include <iostream>
#include <memory>
#include <stdexcept>
  #include <string>
  #include <string>
#include <cstring>
#include <system_error>
#include <stdexcept>
#include <systemd/sd-bus.h>
   
   
  // システムエラーを処理するためのカスタム例外クラス
  namespace {
  class SystemdError : public std::runtime_error
    struct SdBusDeleter {
{
      void operator()(sd_bus* bus) const {
private:
          if (bus) {
    int error_code_;
            sd_bus_flush_close_unref(bus);
          }
      }
    };
   
    struct SdBusMessageDeleter {
      void operator()(sd_bus_message* message) const {
          if (message) {
            sd_bus_message_unref(message);
          }
      }
    };
    class SdBusErrorGuard {
    public:
      SdBusErrorGuard() : error_(SD_BUS_ERROR_NULL) {}
      ~SdBusErrorGuard() { sd_bus_error_free(&error_); }
   
   
public:
      sd_bus_error* get() { return &error_; }
    explicit SystemdError(const std::string &message, int error_code) : std::runtime_error(message + ": " + std::string(strerror(-error_code)))
      const char* message() const { return error_.message ? error_.message : ""; }
      , error_code_(-error_code)
    {}
   
   
     int error_code() const { return error_code_; }
     private:
};
      sd_bus_error error_;
    };
   
   
class ScopedBusConnection
    using BusPtr = std::unique_ptr<sd_bus, SdBusDeleter>;
{
     using MessagePtr = std::unique_ptr<sd_bus_message, SdBusMessageDeleter>;
private:
     sd_bus* bus_;
   
   
public:
     BusPtr open_system_bus()
     // D-Bus接続を確立して、RAIIでリソース管理を行う
    explicit ScopedBusConnection() : bus_(nullptr)
     {
     {
       int ret = sd_bus_open_system(&bus_);
       sd_bus* raw_bus = nullptr;
      const int ret = sd_bus_open_system(&raw_bus);
       if (ret < 0) {
       if (ret < 0) {
           throw SystemdError("D-Bus接続の確立に失敗", ret);
           throw std::runtime_error("システムバスのオープンに失敗しました: " + std::string(std::strerror(-ret)));
       }
       }
      return BusPtr(raw_bus);
     }
     }
   
   
     // デストラクタでD-Bus接続を自動的に解放
     std::string start_unit(sd_bus* bus, const std::string& unit_name, const std::string& mode)
    ~ScopedBusConnection()
     {
     {
       if (bus_) {
      SdBusErrorGuard error;
           sd_bus_unref(bus_);
      sd_bus_message* raw_reply = nullptr;
      const int ret = sd_bus_call_method(bus,
                                          "org.freedesktop.systemd1",
                                          "/org/freedesktop/systemd1",
                                          "org.freedesktop.systemd1.Manager",
                                          "StartUnit",
                                          error.get(),
                                          &raw_reply,
                                          "ss",
                                          unit_name.c_str(),
                                          mode.c_str());
       if (ret < 0) {
           throw std::runtime_error("StartUnitの呼び出しに失敗しました: " + std::string(error.message()));
       }
       }
    }
   
   
    // D-Busハンドルを取得
      MessagePtr reply(raw_reply);
    sd_bus* get() { return bus_; }
   
   
    // コピーを禁止
      const char* job_path = nullptr;
    ScopedBusConnection(const ScopedBusConnection&)           = delete;
      const int read_ret = sd_bus_message_read(reply.get(), "o", &job_path);
    ScopedBusConnection& operator=(const ScopedBusConnection&) = delete;
      if (read_ret < 0) {
};
          throw std::runtime_error("ジョブパスの読み出しに失敗しました: " + std::string(std::strerror(-read_ret)));
      }
   
   
// SSHサービスを起動する関数
       return std::string(job_path);
void start_ssh_service(sd_bus* bus)
{
    if (!bus) {
       throw std::invalid_argument("無効なD-Busハンドルが渡されました");
    }
    int ret = sd_bus_call_method(bus,
                                "org.freedesktop.systemd1",          // サービス名
                                "/org/freedesktop/systemd1",          // オブジェクトパス
                                "org.freedesktop.systemd1.Manager",  // インターフェース名
                                "StartUnit",                          // メソッド名
                                nullptr,                              // エラー戻り値
                                nullptr,                              // 戻り値
                                "ss",                                // 引数の型(string, string)
                                "sshd.service",                      // 起動するサービス名
                                "replace");                          // 起動モード
    if (ret < 0) {
      throw SystemdError("SSHサービスの起動に失敗しました", ret);
     }
     }
  }
  }
   
   
  int main(int argc, char *argv[])
  int main()
  {
  {
     try {
     try {
       // RAIIを使用してD-Bus接続を管理
       BusPtr bus = open_system_bus();
      ScopedBusConnection bus_connection;
       const std::string job_path = start_unit(bus.get(), "sshd.service", "replace");
       // SSHサービスを起動
      start_ssh_service(bus_connection.get());
      std::cout << "SSHサービスの起動に成功しました" << std::endl;
   
   
      std::cout << "StartUnit request accepted" << std::endl;
      std::cout << "job path: " << job_path << std::endl;
      std::cout << "この時点ではジョブが作成された段階であり、サービスの完全起動確認は別途必要です" << std::endl;
       return 0;
       return 0;
     }
     }
    catch (const SystemdError &e) {
     catch (const std::exception& ex) {
      // システム関連のエラーを処理
       std::cerr << ex.what() << std::endl;
      std::cerr << "システムエラーが発生: " << e.what() << std::endl;
       return 1;
      std::cerr << "エラーコード: " << e.error_code() << std::endl;
      return -1;
    }
     catch (const std::exception &e) {
      // その他の一般的なエラーを処理
       std::cerr << "予期せぬエラーが発生: " << e.what() << std::endl;
       return -2;
    }
    catch (...) {
      // 未知のエラーを処理
      std::cerr << "不明なエラーが発生" << std::endl;
      return -3;
     }
     }
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
この後、実際の起動完了まで追跡したい場合は、<code>JobRemoved</code> シグナルを監視するか、対象ユニットの <code>ActiveState</code> プロパティを問い合わせる。<br>
<br>
==== ActiveStateの確認 ====
ジョブ作成後に状態を確認する場合は、対象ユニットのD-Busオブジェクトパスを取得し、<code>org.freedesktop.DBus.Properties.Get</code> で <code>ActiveState</code> を参照する。<br>
<br>
イベント駆動で監視する場合は、<code>sd_bus_add_match()</code> で <code>PropertiesChanged</code> または <code>JobRemoved</code> を購読するとよい。<br>
<br><br>
== sd-eventとの統合 ==
<code>sd-event</code> は、epollベースのイベントループであり、タイマ、シグナル、I/O、子プロセス終了、メモリ圧力等を統合的に扱える。<br>
単純な同期呼び出しだけであれば必須ではないが、非同期D-Bus呼び出しや長寿命デーモンでは非常に有用である。<br>
<br>
==== 基本的な使用方法 ====
* <code>sd_event_default()</code>
*: 既定のイベントループを作成する。
* <code>sd_bus_attach_event()</code>
*: D-Bus接続をイベントループへ接続する。
* <code>sd_event_run()</code> / <code>sd_event_loop()</code>
*: イベント処理を実行する。
<br>
==== 利用例 ====
以下のような場面で <code>sd-event</code> が有効である。<br>
<br>
* 非同期の <code>sd_bus_call_method_async()</code> を使う場合
* タイマ駆動で定期的にサービス状態を確認する場合
* シグナル受信とD-Busイベントを1つのループでまとめたい場合
* <code>sd_event_set_watchdog()</code> でウォッチドッグ通知を自動化したい場合
<br>
==== 注意点 ====
非同期処理を書き始めたら、イベントループを実際に駆動しなければコールバックは返ってこない。<br>
<br>
また、長時間ブロックする処理をイベントハンドラ内で実行すると、ウォッチドッグ通知やD-Bus応答が遅延する。<br>
<br><br>
== journaldとの連携 ==
==== ログの書き込み ====
Systemd環境では、単なる標準出力だけでなく、journaldに対して構造化ログを送ると、検索性と保守性が大きく向上する。<br>
主なAPIは、<code>sd_journal_print()</code> と <code>sd_journal_send()</code> である。<br>
<br>
* <code>sd_journal_print()</code>
*: printf風の簡易ログ出力
* <code>sd_journal_send()</code>
*: <code>MESSAGE=...</code>、<code>PRIORITY=...</code> 等のフィールドを付与した構造化ログ
* <code>sd_journal_sendv()</code>
*: <code>iovec</code>配列による柔軟な送信
<br>
==== 構造化ログの例 ====
<syntaxhighlight lang="c++">
#include <systemd/sd-journal.h>
#include <syslog.h>
void write_systemd_log(const char* unit_name, const char* detail)
{
    sd_journal_send("MESSAGE=Systemd unit operation finished",
                    "PRIORITY=%i", LOG_INFO,
                    "UNIT=%s", unit_name,
                    "DETAIL=%s", detail,
                    nullptr);
}
</syntaxhighlight>
<br>
構造化ログを使うと、<code>journalctl</code> 側でフィールド検索しやすくなる。<br>
<br>
==== ジャーナルの読み出し ====
ログを追跡する場合は、<code>sd_journal_open()</code>、<code>sd_journal_next()</code>、<code>sd_journal_get_data()</code>を使用する。<br>
<br>
ユニット名やプライオリティでフィルタを掛ける場合は、<code>sd_journal_add_match()</code> が便利である。<br>
<br><br>
== Type=notifyサービスとの連携 ==
Systemd管理下のデーモンをC++で実装する場合、<code>Type=notify</code> または <code>Type=notify-reload</code> を使用すると、起動完了やリロード状態をSystemdへ正確に伝えられる。<br>
<br>
==== 主な通知内容 ====
* <code>READY=1</code>
*: サービスの初期化完了
* <code>STATUS=...</code>
*: 現在の状態メッセージ
* <code>RELOADING=1</code>
*: リロード開始
* <code>STOPPING=1</code>
*: 終了処理の開始
* <code>WATCHDOG=1</code>
*: ウォッチドッグのキープアライブ
<br>
==== 最小構成の例 ====
<syntaxhighlight lang="c++">
#include <systemd/sd-daemon.h>
int notify_ready()
{
    return sd_notify(0, "READY=1\nSTATUS=Initialization completed");
}
</syntaxhighlight>
<br>
==== ウォッチドッグ ====
ウォッチドッグが有効な場合は、<code>sd_watchdog_enabled()</code> で間隔を取得し、その半分程度の周期で <code>WATCHDOG=1</code> を送信する構成が一般的である。<br>
<br>
<code>sd-event</code> を使用している場合は、<code>sd_event_set_watchdog()</code> を使って自動化できる。<br>
<br>
==== ユニットファイル側の注意 ====
アプリケーションが <code>sd_notify()</code> を送っても、ユニットファイルで <code>Type=notify</code> と <code>NotifyAccess=</code> が適切に設定されていなければ期待通りに動作しない。<br>
<br>
通常は <code>NotifyAccess=main</code> から検討し、必要がある場合のみ <code>all</code> を使用する。<br>
<br><br>
== Transient Unit ==
Transient Unitは、ディスク上の恒久的なunitファイルを作成せずに、実行時に動的なユニットを生成する仕組みである。<br>
<br>
これは、一時的なscopeやserviceを作成したい場合に有効である。<br>
<br>
==== 主な用途 ====
* 一時的なワーカープロセスを専用scopeで実行する。
* リソース制限付きのserviceを動的に生成する。
* コンテナやジョブ実行器から専用cgroupを切る。
<br>
==== 代表的なプロパティ ====
<center>
{| class="wikitable"
|+ StartTransientUnitでよく使うプロパティ
! プロパティ !! 用途
|-
| <code>Description</code> || 一時ユニットの説明
|-
| <code>Slice</code> || 所属するsliceの指定
|-
| <code>PIDs</code> || scopeへ初期PIDを渡す
|-
| <code>Delegate</code> || cgroupの委譲を有効化する
|-
| <code>RemainAfterExit</code> || service終了後も状態を保持する
|}
</center>
<br>
==== 注意点 ====
<code>StartTransientUnit</code> のD-Busシグネチャは複雑であり、<code>a(sv)</code> 形式のプロパティ配列を正確に構築する必要がある。<br>
<br>
また、恒久unitファイルで使用可能な全ディレクティブがTransient Unitで使えるわけではない。<br>
<br>
最新の対応状況は、[https://github.com/systemd/systemd/blob/main/docs/TRANSIENT-SETTINGS.md TRANSIENT-SETTINGS.md]を参照すること。<br>
<br><br>
== 権限とPolKit ==
Systemd Managerに対する読み取り操作は比較的容易だが、ユニットの開始、停止、再起動、ユニットファイルの有効化等は権限が必要になる。<br>
<br>
システムバスでの管理操作では、PolKit認証や適切なケーパビリティが関わる。<br>
<br>
==== 代表的な権限 ====
* <code>org.freedesktop.systemd1.manage-units</code>
*: ユニットの開始、停止、再起動、プロパティ変更
* <code>org.freedesktop.systemd1.manage-unit-files</code>
*: ユニットファイルの有効化、無効化
* <code>org.freedesktop.systemd1.set-environment</code>
*: Systemd Managerの環境変数変更
<br>
==== 実務上の注意 ====
* 一般ユーザでシステムユニットを操作すると、PolKit認証を求められることがある。
* 同じユーザのユーザユニットを扱うだけであれば、<code>sd_bus_open_user()</code> でユーザマネージャへ接続する方が簡潔である。
* デスクトップ環境が存在しないサーバでは、PolKit対話認証ダイアログを前提にしない設計が必要である。
<br>
詳しい認可制御は、[[C++の応用_-_PolKit]]のページを参照すること。<br>
<br><br>
== トラブルシューティング ==
==== StartUnitが成功したのにサービスが動いていない ====
<code>StartUnit</code> の成功は、開始要求が受理されたことを示すだけである。<br>
ジョブ終了後に失敗している可能性があるため、<code>JobRemoved</code> や <code>ActiveState</code> を確認する。<br>
<br>
==== 権限エラーになる ====
一般ユーザでシステムユニットを操作している可能性がある。<br>
対象がユーザユニットであれば <code>sd_bus_open_user()</code> へ切り替え、システムユニットであればPolKit設定や実行権限を見直す。<br>
<br>
==== sd_notify()が効かない ====
ユニットファイル側で <code>Type=notify</code> または <code>Type=notify-reload</code> が設定されているか確認する。<br>
<br>
さらに、<code>NotifyAccess=</code> が通知元プロセスを許可している必要がある。<br>
<br>
==== 非同期コールバックが返ってこない ====
<code>sd_bus_call_method_async()</code> を使う場合、イベントループを実際に回していなければコールバックは実行されない。<br>
<br>
<code>sd_bus_attach_event()</code> または <code>sd_bus_process()</code> / <code>sd_bus_wait()</code> の駆動を確認する。<br>
<br>
==== ジャーナルへログが出ない ====
journaldが利用可能な環境であること、また、ログ送信後すぐにプロセスを終了していないことを確認する。<br>
<br>
詳細な追跡には、<code>journalctl -xeu &lt;unit名&gt;</code> が有効である。<br>
<br><br>
== 関連情報 ==
* [https://www.freedesktop.org/software/systemd/man/latest/libsystemd.html libsystemd]
* [https://www.freedesktop.org/software/systemd/man/latest/sd-bus.html sd-bus]
* [https://www.freedesktop.org/software/systemd/man/latest/sd-event.html sd-event]
* [https://www.freedesktop.org/software/systemd/man/latest/sd-journal.html sd-journal]
* [https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html sd_notify]
* [https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html org.freedesktop.systemd1]
* [https://systemd.io/PORTABILITY_AND_STABILITY/ PORTABILITY_AND_STABILITY]
* [https://github.com/systemd/systemd systemd GitHub]
* [[C++の応用 - PolKit]]
<br><br>
<br><br>


221行目: 492行目:
{{#seo:
{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Podman,電気回路,電子回路,基板,プリント基板
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,systemd,libsystemd,D-Bus,journald,PolKit,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
|image=/resources/assets/MochiuLogo_Single_Blue.png

2026年6月13日 (土) 12:31時点における最新版

概要

Systemdは、Linuxにおける代表的なシステムおよびサービスマネージャであり、起動、停止、依存関係管理、ログ収集、リソース制御を一元的に扱う。

C++からSystemdを利用する場合、最も重要な入口はlibsystemdである。
libsystemdはC APIを提供するライブラリであり、純粋なC++アプリケーションから直接利用できる。

代表的なヘッダには、sd-bus.hsd-event.hsd-journal.hsd-daemon.hがある。
これらを組み合わせることで、ユニット操作、イベントループ統合、journaldへの構造化ログ出力、Type=notifyサービスとの連携を実装できる。

Systemd Managerと通信する場合、通常はD-Bus経由で org.freedesktop.systemd1 にアクセスする。
このとき、サービスの開始や停止は単純なシェルコマンド実行ではなく、StartUnitStopUnit等のメソッド呼び出しとして扱う。

特に重要なのは、StartUnit の戻り値が「サービスが完全に起動した」という意味ではなく、ジョブが作成されたことを示す点である。
実際の状態確認には、ジョブの監視や ActiveState プロパティの確認が必要になる。

また、Systemdはシステムユニットだけでなく、ユーザごとのユーザユニットも管理できる。
一般ユーザのサービスを制御する場合は sd_bus_open_user()、システム全体のユニットを制御する場合は sd_bus_open_system() を使い分ける。

近年のlibsystemdでは、sd-jsonsd-varlink 等の公開APIが追加され、イベントループや通知機能も強化されている。
一方で、実務で最も利用頻度が高いのは、依然として sd-bussd-eventsd-journalsd_notifyである。

C++でlibsystemdを使う場合は、GUIフレームワークに依存せず、std::unique_ptr や 専用デリータを使用してRAIIでリソース管理する構成が扱いやすい。
また、ビルド時は pkg-config --cflags --libs libsystemd を使用するのが最も安全である。


libsystemdの導入

ライセンス

通常のアプリケーションがリンクするlibsystemdは、主としてLGPL 2.1以降の条件で利用できる。
ただし、systemdのソースツリー全体にはGPL系コンポーネントも含まれるため、再配布方針が厳密な環境では公式ライセンス表記も確認すること。

パッケージ管理システムからインストール

# RHEL
sudo dnf install systemd-devel

# SUSE
sudo zypper install systemd-devel


ソースコードからインストール

最新のupstream機能を利用する場合は、ソースコードからビルドする。
ただし、依存ライブラリが多く、ディストリビューションのBuildRequires相当のパッケージが必要になるため、通常は配布パッケージの利用を優先する。

Systemdのビルドに必要なライブラリをインストールする。

# RHEL
sudo dnf install -y epel-release
sudo dnf config-manager --set-enabled crb

sudo dnf install meson ninja-build python3-jinja2 glib2-devel dbus-devel p11-kit-devel libarchive-devel pcre2-devel        \
                 libcurl-devel libcap-devel libmount-devel libfdisk-devel libblkid-devel elfutils-devel libpwquality-devel \
                 kmod-devel libbpf-devel zlib-ng-compat-devel lz4-devel libzstd-devel xz-devel bzip2-devel iptables-devel  \
                 pam-devel gnutls-devel openssl-devel cryptsetup-devel libgcrypt-devel libgpg-error-devel \
                 libmicrohttpd-devel libxkbcommon-devel libfido2-devel tpm2-tss-devel libseccomp-devel    \
                 libacl-devel audit-libs-devel libselinux-devel

# SUSE
sudo zypper install meson ninja python3-Jinja2 glib2-devel dbus-1-devel p11-kit-devel libarchive-devel pcre2-devel         \
                    libcurl-devel libcap-devel libmount-devel libfdisk-devel libblkid-devel libdw-devel libpwquality-devel \
                    passwdqc-devel libkmod-devel libbpf-devel zlib-devel liblz4-devel libzstd-devel xz-devel libbz2-devel  \
                    pam-devel libgnutls-devel libopenssl-devel libopenssl-1_1-devel libcryptsetup-devel libgcrypt-devel    \
                    libgpg-error-devel qrencode-devel libiptc-devel libidn2-devel libmicrohttpd-devel            \
                    libxkbcommon-devel libfido2-devel tpm2-0-tss-devel libseccomp-devel libacl-devel audit-devel \
                    libapparmor-devel  # AppArmorを使用する場合
                    libselinux-devel   # SELinuxを使用する場合
                    xen-devel          # Xenを使用する場合


次に、SystemdのGithubにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。

tar xf systemd-<バージョン>.tar.gz
cd systemd-<バージョン>


または、git clone コマンドを実行して、ソースコードをダウンロードする。

git clone https://github.com/systemd/systemd.git
cd systemd


Systemdをビルドおよびインストールする。

meson setup build -Dmode=release --prefix=<Systemdのインストールディレクトリ>
meson compile -C build
sudo meson install -C build


libsystemdの利用だけが目的であれば、Systemd本体をソースから導入するよりも、パッケージ管理システムの開発パッケージ用いる方が保守しやすい。

CMakeファイルの設定

C++からlibsystemdを利用する場合、pkg-config 経由で検出する構成が最も安全である。

 cmake_minimum_required(VERSION 3.21)
 project(systemd_cpp_example LANGUAGES CXX)
 
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 find_package(PkgConfig REQUIRED)
 pkg_check_modules(SYSTEMD REQUIRED libsystemd)
 
 add_executable(systemd_cpp_example
    main.cpp
 )
 
 target_include_directories(systemd_cpp_example PRIVATE
    ${SYSTEMD_INCLUDE_DIRS}
 )
 
 target_link_libraries(systemd_cpp_example PRIVATE
    ${SYSTEMD_LIBRARIES}
 )
 
 target_compile_options(systemd_cpp_example PRIVATE
    ${SYSTEMD_CFLAGS_OTHER}
 )


主要ヘッダと用途

libsystemdの主要ヘッダ
ヘッダ 主な用途
<systemd/sd-bus.h> Systemd Managerや他のD-Busサービスとの通信
<systemd/sd-event.h> epollベースのイベントループ、タイマ、シグナル、I/O監視
<systemd/sd-journal.h> journaldへのログ出力、ジャーナルの読み出し
<systemd/sd-daemon.h> sd_notify()、ウォッチドッグ、ソケットアクティベーション支援
<systemd/sd-id128.h> 128ビットIDの生成と管理
<systemd/sd-login.h> ログインセッションやseat情報の取得



libsystemdの主要なAPI

C++からSystemdを扱う場合、最初に理解しておくべきAPI群を整理する。

純粋なC++からよく使うlibsystemd API
分類 主なAPI 用途
Systemd Manager操作 sd_bus_open_system()
sd_bus_call_method()
sd_bus_add_match()
ユニットの起動・停止・プロパティ監視
イベントループ sd_event_default()
sd_event_run()
sd_bus_attach_event()
非同期イベント処理とD-Bus統合
journald出力 sd_journal_print()
sd_journal_send()
通常ログと構造化ログの送信
サービス通知 sd_notify()
sd_watchdog_enabled()
Type=notifyサービスの状態通知
ジャーナル読み出し sd_journal_open()
sd_journal_next()
sd_journal_get_data()
ログの追跡と解析



Systemd Managerとの通信

基本情報

Systemd Managerは、D-Bus上で以下のサービスを提供する。

  • サービス名
    org.freedesktop.systemd1
  • オブジェクトパス
    /org/freedesktop/systemd1
  • 主要インターフェース
    org.freedesktop.systemd1.Manager


ユニットを起動する代表的なメソッドは、StartUnit である。
停止は StopUnit、再起動は RestartUnit、リロードは ReloadUnit を使用する。

ジョブモード

Systemd Managerのユニット操作では、しばしば「ジョブモード」を同時に指定する。

  • replace
    既存の競合ジョブを置き換えて実行する。通常はこの値を使う。
  • fail
    競合ジョブが存在する場合は失敗する。
  • isolate
    対象ユニットとその依存を残し、他を停止する。
  • ignore-dependencies
    依存関係を無視する。
  • ignore-requirements
    requirement関係を無視する。


ignore-dependencies および ignore-requirements は、通常のアプリケーションから安易に使うべきではない。

C++によるサービス起動例

以下の例では、システムバスへ接続し、sshd.serviceに対して StartUnit を呼び出す。

この例が返す ジョブオブジェクトパス は、開始要求が受理されたことを示すものであり、サービスが完全に起動済みであることを意味しない。

 #include <systemd/sd-bus.h>
 
 #include <cstring>
 #include <iostream>
 #include <memory>
 #include <stdexcept>
 #include <string>
 
 namespace {
    struct SdBusDeleter {
       void operator()(sd_bus* bus) const {
          if (bus) {
             sd_bus_flush_close_unref(bus);
          }
       }
    };
 
    struct SdBusMessageDeleter {
       void operator()(sd_bus_message* message) const {
          if (message) {
             sd_bus_message_unref(message);
          }
       }
    };
 
    class SdBusErrorGuard {
    public:
       SdBusErrorGuard() : error_(SD_BUS_ERROR_NULL) {}
       ~SdBusErrorGuard() { sd_bus_error_free(&error_); }
 
       sd_bus_error* get() { return &error_; }
       const char* message() const { return error_.message ? error_.message : ""; }
 
    private:
       sd_bus_error error_;
    };
 
    using BusPtr = std::unique_ptr<sd_bus, SdBusDeleter>;
    using MessagePtr = std::unique_ptr<sd_bus_message, SdBusMessageDeleter>;
 
    BusPtr open_system_bus()
    {
       sd_bus* raw_bus = nullptr;
       const int ret = sd_bus_open_system(&raw_bus);
       if (ret < 0) {
          throw std::runtime_error("システムバスのオープンに失敗しました: " + std::string(std::strerror(-ret)));
       }
 
       return BusPtr(raw_bus);
    }
 
    std::string start_unit(sd_bus* bus, const std::string& unit_name, const std::string& mode)
    {
       SdBusErrorGuard error;
       sd_bus_message* raw_reply = nullptr;
 
       const int ret = sd_bus_call_method(bus,
                                          "org.freedesktop.systemd1",
                                          "/org/freedesktop/systemd1",
                                          "org.freedesktop.systemd1.Manager",
                                          "StartUnit",
                                          error.get(),
                                          &raw_reply,
                                          "ss",
                                          unit_name.c_str(),
                                          mode.c_str());
       if (ret < 0) {
          throw std::runtime_error("StartUnitの呼び出しに失敗しました: " + std::string(error.message()));
       }
 
       MessagePtr reply(raw_reply);
 
       const char* job_path = nullptr;
       const int read_ret = sd_bus_message_read(reply.get(), "o", &job_path);
       if (read_ret < 0) {
          throw std::runtime_error("ジョブパスの読み出しに失敗しました: " + std::string(std::strerror(-read_ret)));
       }
 
       return std::string(job_path);
    }
 }
 
 int main()
 {
    try {
       BusPtr bus = open_system_bus();
       const std::string job_path = start_unit(bus.get(), "sshd.service", "replace");
 
       std::cout << "StartUnit request accepted" << std::endl;
       std::cout << "job path: " << job_path << std::endl;
       std::cout << "この時点ではジョブが作成された段階であり、サービスの完全起動確認は別途必要です" << std::endl;
       return 0;
    }
    catch (const std::exception& ex) {
       std::cerr << ex.what() << std::endl;
       return 1;
    }
 }


この後、実際の起動完了まで追跡したい場合は、JobRemoved シグナルを監視するか、対象ユニットの ActiveState プロパティを問い合わせる。

ActiveStateの確認

ジョブ作成後に状態を確認する場合は、対象ユニットのD-Busオブジェクトパスを取得し、org.freedesktop.DBus.Properties.GetActiveState を参照する。

イベント駆動で監視する場合は、sd_bus_add_match()PropertiesChanged または JobRemoved を購読するとよい。


sd-eventとの統合

sd-event は、epollベースのイベントループであり、タイマ、シグナル、I/O、子プロセス終了、メモリ圧力等を統合的に扱える。
単純な同期呼び出しだけであれば必須ではないが、非同期D-Bus呼び出しや長寿命デーモンでは非常に有用である。

基本的な使用方法

  • sd_event_default()
    既定のイベントループを作成する。
  • sd_bus_attach_event()
    D-Bus接続をイベントループへ接続する。
  • sd_event_run() / sd_event_loop()
    イベント処理を実行する。


利用例

以下のような場面で sd-event が有効である。

  • 非同期の sd_bus_call_method_async() を使う場合
  • タイマ駆動で定期的にサービス状態を確認する場合
  • シグナル受信とD-Busイベントを1つのループでまとめたい場合
  • sd_event_set_watchdog() でウォッチドッグ通知を自動化したい場合


注意点

非同期処理を書き始めたら、イベントループを実際に駆動しなければコールバックは返ってこない。

また、長時間ブロックする処理をイベントハンドラ内で実行すると、ウォッチドッグ通知やD-Bus応答が遅延する。


journaldとの連携

ログの書き込み

Systemd環境では、単なる標準出力だけでなく、journaldに対して構造化ログを送ると、検索性と保守性が大きく向上する。
主なAPIは、sd_journal_print()sd_journal_send() である。

  • sd_journal_print()
    printf風の簡易ログ出力
  • sd_journal_send()
    MESSAGE=...PRIORITY=... 等のフィールドを付与した構造化ログ
  • sd_journal_sendv()
    iovec配列による柔軟な送信


構造化ログの例

 #include <systemd/sd-journal.h>
 #include <syslog.h>
 
 void write_systemd_log(const char* unit_name, const char* detail)
 {
    sd_journal_send("MESSAGE=Systemd unit operation finished",
                    "PRIORITY=%i", LOG_INFO,
                    "UNIT=%s", unit_name,
                    "DETAIL=%s", detail,
                    nullptr);
 }


構造化ログを使うと、journalctl 側でフィールド検索しやすくなる。

ジャーナルの読み出し

ログを追跡する場合は、sd_journal_open()sd_journal_next()sd_journal_get_data()を使用する。

ユニット名やプライオリティでフィルタを掛ける場合は、sd_journal_add_match() が便利である。


Type=notifyサービスとの連携

Systemd管理下のデーモンをC++で実装する場合、Type=notify または Type=notify-reload を使用すると、起動完了やリロード状態をSystemdへ正確に伝えられる。

主な通知内容

  • READY=1
    サービスの初期化完了
  • STATUS=...
    現在の状態メッセージ
  • RELOADING=1
    リロード開始
  • STOPPING=1
    終了処理の開始
  • WATCHDOG=1
    ウォッチドッグのキープアライブ


最小構成の例

 #include <systemd/sd-daemon.h>
 
 int notify_ready()
 {
    return sd_notify(0, "READY=1\nSTATUS=Initialization completed");
 }


ウォッチドッグ

ウォッチドッグが有効な場合は、sd_watchdog_enabled() で間隔を取得し、その半分程度の周期で WATCHDOG=1 を送信する構成が一般的である。

sd-event を使用している場合は、sd_event_set_watchdog() を使って自動化できる。

ユニットファイル側の注意

アプリケーションが sd_notify() を送っても、ユニットファイルで Type=notifyNotifyAccess= が適切に設定されていなければ期待通りに動作しない。

通常は NotifyAccess=main から検討し、必要がある場合のみ all を使用する。


Transient Unit

Transient Unitは、ディスク上の恒久的なunitファイルを作成せずに、実行時に動的なユニットを生成する仕組みである。

これは、一時的なscopeやserviceを作成したい場合に有効である。

主な用途

  • 一時的なワーカープロセスを専用scopeで実行する。
  • リソース制限付きのserviceを動的に生成する。
  • コンテナやジョブ実行器から専用cgroupを切る。


代表的なプロパティ

StartTransientUnitでよく使うプロパティ
プロパティ 用途
Description 一時ユニットの説明
Slice 所属するsliceの指定
PIDs scopeへ初期PIDを渡す
Delegate cgroupの委譲を有効化する
RemainAfterExit service終了後も状態を保持する


注意点

StartTransientUnit のD-Busシグネチャは複雑であり、a(sv) 形式のプロパティ配列を正確に構築する必要がある。

また、恒久unitファイルで使用可能な全ディレクティブがTransient Unitで使えるわけではない。

最新の対応状況は、TRANSIENT-SETTINGS.mdを参照すること。


権限とPolKit

Systemd Managerに対する読み取り操作は比較的容易だが、ユニットの開始、停止、再起動、ユニットファイルの有効化等は権限が必要になる。

システムバスでの管理操作では、PolKit認証や適切なケーパビリティが関わる。

代表的な権限

  • org.freedesktop.systemd1.manage-units
    ユニットの開始、停止、再起動、プロパティ変更
  • org.freedesktop.systemd1.manage-unit-files
    ユニットファイルの有効化、無効化
  • org.freedesktop.systemd1.set-environment
    Systemd Managerの環境変数変更


実務上の注意

  • 一般ユーザでシステムユニットを操作すると、PolKit認証を求められることがある。
  • 同じユーザのユーザユニットを扱うだけであれば、sd_bus_open_user() でユーザマネージャへ接続する方が簡潔である。
  • デスクトップ環境が存在しないサーバでは、PolKit対話認証ダイアログを前提にしない設計が必要である。


詳しい認可制御は、C++の応用_-_PolKitのページを参照すること。


トラブルシューティング

StartUnitが成功したのにサービスが動いていない

StartUnit の成功は、開始要求が受理されたことを示すだけである。
ジョブ終了後に失敗している可能性があるため、JobRemovedActiveState を確認する。

権限エラーになる

一般ユーザでシステムユニットを操作している可能性がある。
対象がユーザユニットであれば sd_bus_open_user() へ切り替え、システムユニットであればPolKit設定や実行権限を見直す。

sd_notify()が効かない

ユニットファイル側で Type=notify または Type=notify-reload が設定されているか確認する。

さらに、NotifyAccess= が通知元プロセスを許可している必要がある。

非同期コールバックが返ってこない

sd_bus_call_method_async() を使う場合、イベントループを実際に回していなければコールバックは実行されない。

sd_bus_attach_event() または sd_bus_process() / sd_bus_wait() の駆動を確認する。

ジャーナルへログが出ない

journaldが利用可能な環境であること、また、ログ送信後すぐにプロセスを終了していないことを確認する。

詳細な追跡には、journalctl -xeu <unit名> が有効である。


関連情報