Linuxでsystemdの仕組みとユニットファイルの作り方

Linuxでsystemdの仕組みとユニットファイルの作り方

Linuxで常駐プロセスや自動起動を安定して管理するうえで、systemdの理解は避けて通りにくい。単にアプリを起動するだけなら ./app &nohup でも動くことはあるが、OS起動時の自動起動、停止順序、再起動制御、ログ管理、依存関係、異常終了時の復旧まで含めて考えると、systemdで管理する方が圧倒的に整理しやすい。特にサーバー運用では「起動したかどうか」だけでなく、「落ちたらどうするか」「どのタイミングで起動するか」「ログをどこで見るか」まで含めて設計する必要があり、その中心になるのがユニットファイルになる。

systemdとは何か

systemdは、Linuxでサービスや各種リソースを管理する仕組みの中心になることが多い。
OS起動時に各サービスを立ち上げたり、依存関係に基づいて順序を制御したり、停止や再起動を統一的に扱ったりできる。
つまり、単なる「起動コマンドの実行役」ではなく、
・サービス管理
・自動起動管理
・依存関係管理
・ログ確認
・障害時の再起動制御
まで含めて扱う仕組みと考えると分かりやすい。

なぜsystemdを使うのか

Linuxでアプリをバックグラウンド実行する方法は複数あるが、本番運用ではsystemdが特に扱いやすい。
理由は次のような点にある。
・OS起動時に自動起動できる
・落ちたら再起動させられる
・依存するサービスの後に起動できる
・停止や再起動を systemctl で統一できる
・ログを journalctl で追いやすい
たとえば、Webアプリ、ワーカー、キュー処理、バッチ常駐などは、単なる nohup より systemd 管理の方が再現性と保守性が高くなりやすい。

ユニットとは何か

systemdでは、管理対象を「ユニット」という単位で扱う。
ユニットにはいくつか種類があり、代表的なのは次のようなもの。
・service
・socket
・target
・timer
・mount
この記事で主に扱うのは service ユニット。
これは、あるプロセスや常駐アプリを systemd が管理するための定義ファイルと考えると分かりやすい。

ユニットファイルの基本構造

service ユニットファイルは、基本的に ini 形式のテキストファイルで書く。
最も基本になるのは次の3セクション。
[Unit]
[Service]
[Install]
それぞれの役割は次の通り。
[Unit] は説明や依存関係
[Service] は実際の実行方法
[Install] は enable 時の紐付け先
この3つを押さえるだけで、かなりの service は作れる。

[Unit] セクションで書くこと

[Unit] では、そのサービスの説明や、他ユニットとの関係を書く。
たとえばよく使うのは次のような項目。
Description=
After=
Wants=
最小例は次のようになる。

[Unit]
Description=My Application Service
After=network.target

After=network.target は、「少なくともネットワーク関連の基本起動より後に動かしたい」といった意図でよく使われる。
ただし、After= は起動順序の指定であって、「必ずそのユニットが起動する」という意味ではない点に注意が必要。

[Service] セクションで書くこと

[Service] はユニットファイルの中心になる部分で、実際に何をどう実行するかを定義する。
よく使う項目は次の通り。
Type=
User=
WorkingDirectory=
ExecStart=
Restart=
最小例は次のようになる。

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/myapp
ExecStart=/usr/bin/python3 /home/ubuntu/myapp/app.py
Restart=always

ここで重要なのは、ExecStart= には「実際に実行する絶対パス」を書くこと。
相対パスや曖昧なコマンド名にすると、環境によって失敗しやすい。

[Install] セクションで書くこと

[Install] は、そのユニットを enable したとき、どの target に結び付けるかを書く。
最もよく使うのは次の形。

[Install]
WantedBy=multi-user.target

これは、通常のマルチユーザー動作レベル相当で起動したときに、このサービスを有効化対象へ入れるという意味で使われることが多い。
単に start するだけなら必須ではないが、OS起動時の自動起動まで考えるならほぼセットで使うことになる。

最小のserviceユニットファイル例

まずは最小構成で動かす例を見ると分かりやすい。
たとえば、Pythonスクリプトを常駐実行する service は次のように書ける。

[Unit]
Description=My Python App
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/myapp
ExecStart=/usr/bin/python3 /home/ubuntu/myapp/app.py
Restart=always

[Install]
WantedBy=multi-user.target

この形だけでも、
・起動
・停止
・再起動
・自動起動
の土台としては十分使いやすい。

ユニットファイルの配置場所

自分で作るユニットファイルは、一般的には次のような場所へ置くことが多い。

/etc/systemd/system/

たとえば、myapp.service という名前で作るなら次のようにする。

sudo nano /etc/systemd/system/myapp.service

ここへ置いたあとで systemd に再読込させる必要がある。
systemd は勝手に即時反映されるわけではないため、ファイルを置いただけでは不十分。

作成後に必要なコマンド

ユニットファイルを作成・修正したあとは、まず設定を再読み込みする。
その後、起動、有効化、状態確認へ進むのが基本。

sudo systemctl daemon-reload
sudo systemctl start myapp
sudo systemctl enable myapp
sudo systemctl status myapp

この順番を覚えておくと扱いやすい。
特に daemon-reload を忘れると、ファイルを直したのに systemd 側が古い定義を見ていることがある。

start / stop / restart / enable / disable の違い

systemd でよく使うコマンドは意味を分けて理解した方がよい。
start は今すぐ起動
stop は今すぐ停止
restart は再起動
enable はOS起動時に自動起動するよう登録
disable は自動起動登録を外す
つまり、startenable は別物。
「今起動する」ことと「次回起動時にも自動で立ち上がる」ことを混同しない方が運用でミスしにくい。

sudo systemctl stop myapp
sudo systemctl restart myapp
sudo systemctl disable myapp

ログ確認は journalctl を使う

systemd で管理しているサービスは、標準出力や標準エラーを journald 側で確認しやすい。
そのため、ログ確認では journalctl が非常に重要になる。

sudo journalctl -u myapp

リアルタイムで追うなら次。

sudo journalctl -u myapp -f

起動失敗時や突然落ちるサービスでは、まず systemctl statusjournalctl をセットで見ると切り分けしやすい。
「起動しない」「すぐ落ちる」の大半は、ここを見るだけでかなり原因に近づける。

よく使う Restart の考え方

常駐サービスでは Restart= をどうするかが重要。
代表的には次のような値がある。
no
always
on-failure
たとえば、
・常に落ちたら戻したい → always
・異常終了時だけ戻したい → on-failure
という使い分けがしやすい。
無条件で再起動し続けると不具合の隠蔽になることもあるため、用途次第で選ぶ必要がある。

Restart=on-failure
RestartSec=5

RestartSec= を入れると、再起動までの待機秒数を調整できる。

ExecStart で起きやすいミス

unit ファイル作成で最も多い失敗の1つが ExecStart= まわり。
よくある問題は次の通り。
・実行ファイルの絶対パスが違う
・仮想環境のPythonを使うべきなのに system Python を書いている
・引数の書き方が間違っている
・シェルの書き方をそのまま入れて失敗する
たとえば Python 仮想環境を使うなら、次のように実行ファイル自体を明示する方が安全。

ExecStart=/home/ubuntu/myapp/venv/bin/python /home/ubuntu/myapp/app.py

普段ターミナルで動くコマンドでも、systemd環境ではPATHやシェル展開が違うため、そのままでは失敗することがある。

WorkingDirectory と User の重要性

WorkingDirectory=User= は省略されがちだがかなり重要。
WorkingDirectory= が無いと、相対パスを使うアプリが想定外の場所を基準に動いてしまうことがある。
User= を省略すると root 権限で動くケースがあり、権限が強すぎて危険になることがある。
そのため、基本的には、
・どのディレクトリで
・どのユーザーとして
動かすかを明示した方が安全。

User=www-data
WorkingDirectory=/var/www/myapp

用途に応じて実行ユーザーは変わるが、「何も書かない」は避けた方が運用しやすい。

Environment を使って環境変数を渡す

アプリによっては環境変数が必要になる。
その場合は Environment= を使って渡せる。

Environment=APP_ENV=production
Environment=PORT=8080

複数行でもよいし、専用の環境ファイルを読み込む方法もある。
アプリが手動起動では動くのに systemd では動かない場合、環境変数不足が原因になっていることはかなり多い。

よくある起動失敗の原因

systemdのユニットファイル作成でよくある失敗はかなり典型的。
ExecStart のパス間違い
・仮想環境や依存ライブラリを見つけられない
daemon-reload 忘れ
User や権限設定の不整合
・ポート競合
・WorkingDirectory の不足
・サービス自体は起動したが、すぐ終了するタイプなのに Type が合っていない
こうしたときは、まず次の2つを見るのが基本。

sudo systemctl status myapp
sudo journalctl -u myapp -n 100

これで大半の初歩的なミスはかなり見つけやすい。

systemdで管理するべきものと、そうでないもの

systemd は非常に便利だが、何でも service 化すればよいわけではない。
向いているのは、
・常駐アプリ
・ワーカー
・バックグラウンドジョブ
・サーバープロセス
・定期的に動くものと連携する常駐機能
逆に、単発で一度だけ実行する確認用コマンドまで全部 service 化すると管理が重くなる。
「継続的に起動管理したいかどうか」で判断する方が分かりやすい。

まとめ

Linuxでsystemdの仕組みとユニットファイルを理解するうえで重要なのは、
・systemdはサービス管理の中心であること
・service ユニットは [Unit][Service][Install] が基本であること
ExecStartUserWorkingDirectoryRestart が実務で特に重要であること
・作成後は daemon-reloadstartenablestatus を順番に使うこと
・ログ確認には journalctl が基本になること
この流れを押さえることになる。
最小構成のユニットファイルを1つ自分で作って動かせるようになると、バックグラウンドジョブや常駐サービスの運用がかなり整理しやすくなる。