「C++の応用 - Systemd」の版間の差分
編集の要約なし |
|||
| 1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
Systemdは、Linuxにおける代表的なシステムおよびサービスマネージャであり、起動、停止、依存関係管理、ログ収集、リソース制御を一元的に扱う。<br> | |||
<br> | <br> | ||
C++からSystemdを利用する場合、最も重要な入口は<u>libsystemd</u>である。<br> | |||
libsystemdはC APIを提供するライブラリであり、純粋なC++アプリケーションから直接利用できる。<br> | |||
<br> | <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> | ||
Systemd Managerと通信する場合、通常はD-Bus経由で <u>org.freedesktop.systemd1</u> にアクセスする。<br> | |||
このとき、サービスの開始や停止は単純なシェルコマンド実行ではなく、<code>StartUnit</code> や <code>StopUnit</code>等のメソッド呼び出しとして扱う。<br> | |||
<br> | <br> | ||
特に重要なのは、<code>StartUnit</code> の戻り値が「サービスが完全に起動した」という意味ではなく、ジョブが作成されたことを示す点である。<br> | |||
実際の状態確認には、ジョブの監視や <code>ActiveState</code> プロパティの確認が必要になる。<br> | |||
<br> | <br> | ||
また、Systemdはシステムユニットだけでなく、ユーザごとのユーザユニットも管理できる。<br> | |||
一般ユーザのサービスを制御する場合は <code>sd_bus_open_user()</code>、システム全体のユニットを制御する場合は <code>sd_bus_open_system()</code> を使い分ける。<br> | |||
<br> | <br> | ||
近年のlibsystemdでは、<code>sd-json</code> や <code>sd-varlink</code> 等の公開APIが追加され、イベントループや通知機能も強化されている。<br> | |||
一方で、実務で最も利用頻度が高いのは、依然として <code>sd-bus</code>、<code>sd-event</code>、<code>sd-journal</code>、<code>sd_notify</code>である。<br> | |||
<br> | <br> | ||
C++でlibsystemdを使う場合は、GUIフレームワークに依存せず、<code>std::unique_ptr</code> や 専用デリータを使用してRAIIでリソース管理する構成が扱いやすい。<br> | |||
また、ビルド時は <code>pkg-config --cflags --libs libsystemd</code> を使用するのが最も安全である。<br> | |||
< | |||
< | |||
<br><br> | <br><br> | ||
== | == libsystemdの導入 == | ||
==== ライセンス ==== | ==== ライセンス ==== | ||
通常のアプリケーションがリンクする<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> | ||
# RHEL | |||
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 | ||
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> | ||
<br> | <br> | ||
meson setup build -Dmode=release --prefix=<Systemdのインストールディレクトリ> | |||
meson compile -C build | |||
sudo meson install -C build | |||
<br> | <br> | ||
libsystemdの利用だけが目的であれば、Systemd本体をソースから導入するよりも、パッケージ管理システムの開発パッケージ用いる方が保守しやすい。<br> | |||
<br> | <br> | ||
==== CMakeファイルの設定 ==== | |||
C++からlibsystemdを利用する場合、<code>pkg-config</code> 経由で検出する構成が最も安全である。<br> | |||
<br> | <br> | ||
<syntaxhighlight lang="cmake"> | <syntaxhighlight lang="cmake"> | ||
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) | ||
pkg_check_modules(SYSTEMD REQUIRED libsystemd) | pkg_check_modules(SYSTEMD REQUIRED libsystemd) | ||
add_executable(systemd_cpp_example | |||
main.cpp | |||
) | |||
target_include_directories( | target_include_directories(systemd_cpp_example PRIVATE | ||
${SYSTEMD_INCLUDE_DIRS} | ${SYSTEMD_INCLUDE_DIRS} | ||
) | ) | ||
target_link_libraries( | 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><systemd/sd-bus.h></code> || Systemd Managerや他のD-Busサービスとの通信 | |||
|- | |||
| <code><systemd/sd-event.h></code> || epollベースのイベントループ、タイマ、シグナル、I/O監視 | |||
|- | |||
| <code><systemd/sd-journal.h></code> || journaldへのログ出力、ジャーナルの読み出し | |||
|- | |||
| <code><systemd/sd-daemon.h></code> || <code>sd_notify()</code>、ウォッチドッグ、ソケットアクティベーション支援 | |||
|- | |||
| <code><systemd/sd-id128.h></code> || 128ビットIDの生成と管理 | |||
|- | |||
| <code><systemd/sd-login.h></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> | ||
namespace { | |||
class | 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() | |||
{ | { | ||
int ret = sd_bus_open_system(& | sd_bus* raw_bus = nullptr; | ||
const int ret = sd_bus_open_system(&raw_bus); | |||
if (ret < 0) { | if (ret < 0) { | ||
throw | 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) | |||
{ | { | ||
if ( | 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( | int main() | ||
{ | { | ||
try { | 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; | return 0; | ||
} | } | ||
catch (const std::exception& ex) { | |||
std::cerr << ex.what() << std::endl; | |||
return 1; | |||
catch (const std::exception & | |||
std::cerr << | |||
return | |||
} | } | ||
} | } | ||
</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 <unit名></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# | |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.h、sd-event.h、sd-journal.h、sd-daemon.hがある。
これらを組み合わせることで、ユニット操作、イベントループ統合、journaldへの構造化ログ出力、Type=notifyサービスとの連携を実装できる。
Systemd Managerと通信する場合、通常はD-Bus経由で org.freedesktop.systemd1 にアクセスする。
このとき、サービスの開始や停止は単純なシェルコマンド実行ではなく、StartUnit や StopUnit等のメソッド呼び出しとして扱う。
特に重要なのは、StartUnit の戻り値が「サービスが完全に起動した」という意味ではなく、ジョブが作成されたことを示す点である。
実際の状態確認には、ジョブの監視や ActiveState プロパティの確認が必要になる。
また、Systemdはシステムユニットだけでなく、ユーザごとのユーザユニットも管理できる。
一般ユーザのサービスを制御する場合は sd_bus_open_user()、システム全体のユニットを制御する場合は sd_bus_open_system() を使い分ける。
近年のlibsystemdでは、sd-json や sd-varlink 等の公開APIが追加され、イベントループや通知機能も強化されている。
一方で、実務で最も利用頻度が高いのは、依然として sd-bus、sd-event、sd-journal、sd_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}
)
主要ヘッダと用途
| ヘッダ | 主な用途 |
|---|---|
<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群を整理する。
| 分類 | 主な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関係を無視する。
- 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.Get で ActiveState を参照する。
イベント駆動で監視する場合は、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=notify と NotifyAccess= が適切に設定されていなければ期待通りに動作しない。
通常は NotifyAccess=main から検討し、必要がある場合のみ all を使用する。
Transient Unit
Transient Unitは、ディスク上の恒久的なunitファイルを作成せずに、実行時に動的なユニットを生成する仕組みである。
これは、一時的なscopeやserviceを作成したい場合に有効である。
主な用途
- 一時的なワーカープロセスを専用scopeで実行する。
- リソース制限付きのserviceを動的に生成する。
- コンテナやジョブ実行器から専用cgroupを切る。
代表的なプロパティ
| プロパティ | 用途 |
|---|---|
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 の成功は、開始要求が受理されたことを示すだけである。
ジョブ終了後に失敗している可能性があるため、JobRemoved や ActiveState を確認する。
権限エラーになる
一般ユーザでシステムユニットを操作している可能性がある。
対象がユーザユニットであれば 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名> が有効である。
関連情報
- libsystemd
- sd-bus
- sd-event
- sd-journal
- sd_notify
- org.freedesktop.systemd1
- PORTABILITY_AND_STABILITY
- systemd GitHub
- C++の応用 - PolKit