systemd でのリソース管理と失敗の通知
systemd でのリソース管理と失敗の通知
— MemoryMax
, OnFailure
, Slack 通知ユニット連携、起動順序制御の実践例 —
📝 概要
本記事では、Linux の初期化システムである systemd を使って、サービスの起動制御・リソース制限・障害通知といった運用に使えそうな構成例を紹介します。
systemd は単なるサービスの起動管理にとどまらず、
-
MemoryMax
による メモリ制限 -
After
/Requires
を使った 起動順序の制御 -
@.service
を活用した テンプレートユニットによる柔軟なサービス定義
など、非常に表現力の高い仕組みを提供しています。
この記事ではそれらの機能を組み合わせて、以下のような運用に役立つ構成を構築していきます:
- メモリ超過(OOM)や起動失敗を自動検知し Slack に通知
- 起動完了をログ出力で判定して dependent サービスを順に起動
- 成功・失敗の内容に応じた通知メッセージの分岐
これらを使って、実践で使えそうな処理を試してみようと思います。
ここでは、AWS の EC2 上でUbuntuを使ったテストをやります。
🔍 この記事で試すこと
- systemd によるメモリ制限の使い方 (
MemoryMax
) - 起動完了の定義をログ出力で柔軟に判定する方法
-
OnFailure
と Slack 通知ユニットの連携 - 成功と失敗を判別してメッセージを分ける仕組み
- systemd の依存関係 (起動順序)の制御
🌟 テストパターンは3通り
パターン | 内容 | slack への通知内容 |
---|---|---|
1. 正常起動 | ログ出力ありで成功 | ✅ 起動成功 |
2. タイムアウト | ログを出さずに失敗 | ⏱️ 起動ログが出ないため失敗と判定 |
3. OOM | メモリ消費量が多すぎて終了 | 🔥 OOM による終了 |
systemd ユニットファイル
以下は、必要な systemd ユニットファイルの例です。
-
/etc/systemd/system/backend.service
バックエンドのユニット
[Unit]
Description=Backend App Service
After=network.target
# 失敗時に notify-slack ユニットを引数 failure-backend.service で起動
OnFailure=notify-slack@failure-backend.service
[Service]
Type=simple
# 起動スクリプト
ExecStart=/opt/backend/start.sh
# wait-for-log.sh が /var/log/backend.log に出力される文字列 "Backend started" を監視し、起動完了とみなす
ExecStartPost=/usr/local/bin/wait-for-log.sh "Backend started" /var/log/backend.log
# メモリの最大量 30MB
MemoryMax=30M
# OOM が発生した場合停止する
OOMPolicy=stop
# ExecStartPost の完了を最大 30s 待つ。30s 経過した場合タイムアウトで失敗とみなす。
TimeoutStartSec=30
# Restart=on-failure
# RestartSec=3
StandardOutput=journal
StandardError=journal
# 起動成功したら notify-slack の呼び出し
ExecStartPost=/bin/systemctl start notify-slack@ssuccess-backend.service
[Install]
WantedBy=multi-user.target
-
/etc/systemd/system/dependent.service
バックエンドユニットが先に起動している必要がある依存先ユニット
[Unit]
Description=Dependent Service
After=backend.service
Requires=backend.service
[Service]
ExecStart=/opt/dependent/start.sh
StandardOutput=journal
StandardError=journal
# 起動成功したら notify-slack の呼び出し
ExecStartPost=/bin/systemctl start notify-slack@success-dependent.service
-
/etc/systemd/system/notify-slack@.service
Slack への通知ユニット。テンプレートユニット化して、挙動の違いを引数(インスタンス名)で分離する。
[Unit]
Description=Notify Slack on service status
[Service]
ExecStart=/usr/local/bin/slack-notify.sh %i
StandardOutput=journal
StandardError=journal
💡
notify-slack@.service
の@
は「テンプレートユニット」を意味します。
systemctl start notify-slack@failure-backend.service
のように使うことで、
%i
にfailure-backend
が渡され、Slack 通知スクリプトに動的にパラメータを渡せます。
複数のサービス通知をテンプレートで簡潔に管理できるのが利点です。
📁 バックエンドスクリプト(/opt/backend)
事前に3パターンのスクリプトを /opt/backend
に設置し、start.sh
へのリンクを切り替えて使います。
/opt/backend/start-ok.sh
#!/bin/bash
sleep 2
# 起動が成功するスクリプト。起動成功を /var/log/backend.log へ Backend started を書き込むことにしている。
echo "Backend started" >> /var/log/backend.log
/opt/backend/start-fail.sh
#!/bin/bash
sleep 40
/opt/backend/start-oom.sh
#!/bin/bash
sleep 2
# 起動直後にログを出し、数秒後にメモリ爆発で OOM を狙う
echo "Backend started" >> /var/log/backend.log
echo "Simulating workload..."
# 少し待ってから OOM を発生させる
sleep 5
mem=()
for i in {1..100}; do
mem+=("$(head -c 1048576 </dev/zero | tr '\0' 'A')")
echo "$(date '+%T') - Allocated $((i))MB"
sleep 1
done
-
/opt/backend/start.sh
このファイルは/opt/backend/*.sh
の切り替えのためのシンボリックリンクとして使用します。
📁 dependent スクリプト(/opt/dependent)
バックエンドに依存するアプリの開始スクリプト例です
-
/opt/dependent/start.sh
このファイル自体は何も実行しない。単なる依存先のためのスクリプト。
#!/bin/bash
sleep 2
echo "Dependent service started successfully"
🔧 その他スクリプト(/usr/local/bin/)
-
/usr/local/bin/wait-for-log.sh
引数に指定される監視ファイルに監視文字列が書き込まれた場合に成功とするスクリプトです
#!/bin/bash
# 監視文字列
needle="$1"
# 監視ファイル
logfile="$2"
sed "/$needle/q" <(tail -F -n0 "$logfile")
-
/usr/local/bin/slack-notify.sh
Slack への通知スクリプトです。失敗内容による通知の分岐を含みます。
#!/bin/bash
unit="$1"
if [[ "$unit" == "failure-backend" ]]; then
# バックエンドの失敗系処理
status=$(systemctl show backend.service -p Result -p ExecMainStatus -p ExecMainCode)
echo "failure-backend status: $status"
result=$(echo "$status" | grep '^Result=' | cut -d= -f2)
ExecMainStatus=$(echo "$status" | grep '^ExecMainStatus=' | cut -d= -f2)
ExecMainCode=$(echo "$status" | grep '^ExecMainCode=' | cut -d= -f2)
if [[ "$result" == "oom-kill" ]]; then
msg="🔥 backend.service failed due to OOM (Out Of Memory)"
elif [[ "$result" == "timeout" ]]; then
msg="⏱️ backend.service failed due to startup timeout (log not detected)"
else
msg="⚠️ backend.service failed (unknown reason): result=$result ExecMainCode=$ExecMainCode status=$ExecMainStatus"
fi
elif [[ "$unit" == "success-backend" ]]; then
# バックエンドの起動成功
msg="✅ backend.service started successfully."
elif [[ "$unit" == "success-dependent" ]]; then
# 依存ユニットの起動成功
msg="✅ dependent.service started successfully."
elif [[ "$unit" == "failure-dependent" ]]; then
# 依存ユニットの起動失敗
msg="❌ dependent.service failed because backend.service failed."
else
msg="ℹ️ Service event: $unit"
fi
# Slack webhook
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"$msg\"}" \
https://hooks.slack.com/services/XXXX/YYYY/ZZZ
✅ 実行手順
実行権限の付与
各ファイルを指定パスに配置して、実行権限を付与してください
sudo chmod +x /opt/backend/start-*.sh
sudo chmod +x /usr/local/bin/*.sh
systemd 再読み込み
ユニットファイルを配置後、 daemon-reload
を実行してください。
sudo systemctl daemon-reload
テストパターン切り替え
各テストパターンは、バックエンドのスタートスクリプトのリンクの切り替えによって行います。
sudo ln -sf /opt/backend/start-ok.sh /opt/backend/start.sh
sudo ln -sf /opt/backend/start-fail.sh /opt/backend/start.sh
sudo ln -sf /opt/backend/start-oom.sh /opt/backend/start.sh
🔎 確認コマンド
# dependent.service を起動(backend.service も自動で起動される)
sudo systemctl start dependent.service
# サービス状態確認(両方の状態を確認)
sudo systemctl status dependent.service
sudo systemctl status backend.service
# ログ確認
# このログの取り方は、notify-slack@xx の xx が過去に起動済みでないとうまくログが取れないので注意
journalctl -u backend.service -u dependent.service -u notify-slack* -f
🧪 実行結果
OK
backend のリンクの切り替え
sudo ln -sf /opt/backend/start-ok.sh /opt/backend/start.sh
実験開始
sudo systemctl start dependent.service
ログ
$ journalctl -u backend.service -u dependent.service -u notify-slack* -f
Apr 13 02:03:55 xxx systemd[1]: Starting Backend App Service...
Apr 13 02:03:57 xxx wait-for-log.sh[4189]: Backend started
# backend の起動OK
Apr 13 02:03:57 xxx systemd[1]: Starting Slack Notify for ssuccess-backend...
# backend 起動成功通知を notify-slack@ssuccess-backend.service の起動で実行
Apr 13 02:03:57 xxx slack-notify.sh[4194]: % Total % Received % Xferd Average Speed Time Time Time Current
Apr 13 02:03:57 xxx slack-notify.sh[4194]: Dload Upload Total Spent Left Speed
Apr 13 02:03:57 xxx slack-notify.sh[4194]: [237B blob data]
Apr 13 02:03:57 xxx slack-notify.sh[4194]: ok
Apr 13 02:03:57 xxx systemd[1]: notify-slack@ssuccess-backend.service: Deactivated successfully.
Apr 13 02:03:57 xxx systemd[1]: Finished Slack Notify for ssuccess-backend.
Apr 13 02:03:57 xxx systemd[1]: Started Backend App Service.
Apr 13 02:03:57 xxx systemd[1]: Starting Service that depends on backend being ready...
Apr 13 02:03:57 xxx start.sh[4196]: Dependent service started successfully
# dependent の起動OK
Apr 13 02:03:57 xxx systemd[1]: Starting Slack Notify for success-dependent...
Apr 13 02:03:57 xxx slack-notify.sh[4200]: % Total % Received % Xferd Average Speed Time Time Time Current
Apr 13 02:03:57 xxx slack-notify.sh[4200]: Dload Upload Total Spent Left Speed
Apr 13 02:03:57 xxx slack-notify.sh[4200]: [158B blob data]
Apr 13 02:03:57 xxx slack-notify.sh[4200]: ok
Apr 13 02:03:57 xxx systemd[1]: notify-slack@success-dependent.service: Deactivated successfully.
# dependent 起動成功通知を notify-slack@ssuccess-dependent.service の起動で実行
Apr 13 02:03:57 xxx systemd[1]: Finished Slack Notify for success-dependent.
Apr 13 02:03:57 xxx systemd[1]: Started Service that depends on backend being ready.
Apr 13 02:03:59 xxx systemd[1]: backend.service: Deactivated successfully.
Apr 13 02:03:59 xxx systemd[1]: dependent.service: Deactivated successfully.
slack への通知
タイムアウト
backend のリンクの切り替え
sudo ln -sf /opt/backend/start-fail.sh /opt/backend/start.sh
実験開始
sudo systemctl start dependent.service
ログ
Apr 13 02:22:11 xxx systemd[1]: Starting Backend App Service...
Apr 13 02:22:41 xxx systemd[1]: backend.service: start-post operation timed out. Terminating.
# backend のログ待ちが backend ユニットのタイムアウトを超えた
Apr 13 02:22:41 xxx systemd[1]: backend.service: Control process exited, code=killed, status=15/TERM
Apr 13 02:22:41 xxx systemd[1]: backend.service: Failed with result 'timeout'.
Apr 13 02:22:41 xxx systemd[1]: Failed to start Backend App Service.
Apr 13 02:22:41 xxx systemd[1]: Dependency failed for Service that depends on backend being ready.
Apr 13 02:22:41 xxx systemd[1]: dependent.service: Job dependent.service/start failed with result 'dependency'.
# backend が起動失敗したため、連鎖的に dependent の起動も失敗
Apr 13 02:22:41 xxx systemd[1]: dependent.service: Triggering OnFailure= dependencies.
Apr 13 02:22:41 xxx systemd[1]: backend.service: Triggering OnFailure= dependencies.
# slack への通知
Apr 13 02:22:41 xxx systemd[1]: Starting Slack Notify for failure-backend...
Apr 13 02:22:41 xxx systemd[1]: Starting Slack Notify for failure-dependent...
Apr 13 02:22:41 xxx slack-notify.sh[4391]: failure-backend status: Result=timeout
Apr 13 02:22:41 xxx slack-notify.sh[4391]: ExecMainCode=2
Apr 13 02:22:41 xxx slack-notify.sh[4391]: ExecMainStatus=15
Apr 13 02:22:41 xxx slack-notify.sh[4398]: % Total % Received % Xferd Average Speed Time Time Time Current
Apr 13 02:22:41 xxx slack-notify.sh[4398]: Dload Upload Total Spent Left Speed
Apr 13 02:22:41 xxx slack-notify.sh[4412]: % Total % Received % Xferd Average Speed Time Time Time Current
Apr 13 02:22:41 xxx slack-notify.sh[4412]: Dload Upload Total Spent Left Speed
Apr 13 02:22:41 xxx slack-notify.sh[4412]: [158B blob data]
Apr 13 02:22:41 xxx slack-notify.sh[4412]: ok
Apr 13 02:22:41 xxx systemd[1]: notify-slack@failure-backend.service: Deactivated successfully.
Apr 13 02:22:41 xxx systemd[1]: Finished Slack Notify for failure-backend.
Apr 13 02:22:41 xxx slack-notify.sh[4398]: [158B blob data]
Apr 13 02:22:41 xxx slack-notify.sh[4398]: ok
Apr 13 02:22:41 xxx systemd[1]: notify-slack@failure-dependent.service: Deactivated successfully.
Apr 13 02:22:41 xxx systemd[1]: Finished Slack Notify for failure-dependent.
slack への通知
OOM
backend のリンクの切り替え
sudo ln -sf /opt/backend/start-oom.sh /opt/backend/start.sh
実験開始
sudo systemctl start dependent.service
ログ
Apr 13 02:26:39 xxx systemd[1]: Starting Backend App Service...
Apr 13 02:26:41 xxx start.sh[4428]: Simulating workload...
Apr 13 02:26:41 xxx wait-for-log.sh[4431]: Backend started
# backend の起動OK
Apr 13 02:26:41 xxx systemd[1]: Starting Slack Notify for ssuccess-backend...
Apr 13 02:26:41 xxx slack-notify.sh[4437]: % Total % Received % Xferd Average Speed Time Time Time Current
Apr 13 02:26:41 xxx slack-notify.sh[4437]: Dload Upload Total Spent Left Speed
Apr 13 02:26:41 xxx slack-notify.sh[4437]: [158B blob data]
Apr 13 02:26:41 xxx slack-notify.sh[4437]: ok
Apr 13 02:26:41 xxx systemd[1]: notify-slack@ssuccess-backend.service: Deactivated successfully.
Apr 13 02:26:41 xxx systemd[1]: Finished Slack Notify for ssuccess-backend.
Apr 13 02:26:41 xxx systemd[1]: Started Backend App Service.
Apr 13 02:26:41 xxx systemd[1]: Starting Service that depends on backend being ready...
Apr 13 02:26:41 xxx start.sh[4439]: Dependent service started successfully
# dependent の起動OK
Apr 13 02:26:41 xxx systemd[1]: Starting Slack Notify for success-dependent...
Apr 13 02:26:41 xxx slack-notify.sh[4443]: % Total % Received % Xferd Average Speed Time Time Time Current
Apr 13 02:26:41 xxx slack-notify.sh[4443]: Dload Upload Total Spent Left Speed
Apr 13 02:26:41 xxx slack-notify.sh[4443]: [158B blob data]
Apr 13 02:26:42 xxx slack-notify.sh[4443]: ok
Apr 13 02:26:42 xxx systemd[1]: notify-slack@success-dependent.service: Deactivated successfully.
Apr 13 02:26:42 xxx systemd[1]: Finished Slack Notify for success-dependent.
Apr 13 02:26:42 xxx systemd[1]: Started Service that depends on backend being ready.
Apr 13 02:26:43 xxx systemd[1]: dependent.service: Deactivated successfully.
Apr 13 02:26:46 xxx start.sh[4428]: 02:26:46 - Allocated 1MB
Apr 13 02:26:47 xxx start.sh[4428]: 02:26:47 - Allocated 2MB
Apr 13 02:26:48 xxx start.sh[4428]: 02:26:48 - Allocated 3MB
Apr 13 02:26:49 xxx start.sh[4428]: 02:26:49 - Allocated 4MB
Apr 13 02:26:50 xxx start.sh[4428]: 02:26:50 - Allocated 5MB
Apr 13 02:26:51 xxx start.sh[4428]: 02:26:51 - Allocated 6MB
Apr 13 02:26:52 xxx start.sh[4428]: 02:26:52 - Allocated 7MB
Apr 13 02:26:53 xxx start.sh[4428]: 02:26:53 - Allocated 8MB
Apr 13 02:26:54 xxx start.sh[4428]: 02:26:54 - Allocated 9MB
Apr 13 02:26:55 xxx start.sh[4428]: 02:26:55 - Allocated 10MB
Apr 13 02:26:56 xxx start.sh[4428]: 02:26:56 - Allocated 11MB
Apr 13 02:26:57 xxx start.sh[4428]: 02:26:57 - Allocated 12MB
Apr 13 02:26:58 xxx start.sh[4428]: 02:26:58 - Allocated 13MB
# backend が OOM で killされた
Apr 13 02:26:59 xxx systemd[1]: backend.service: A process of this unit has been killed by the OOM killer.
Apr 13 02:26:59 xxx systemd[1]: backend.service: Main process exited, code=killed, status=9/KILL
Apr 13 02:26:59 xxx systemd[1]: backend.service: Failed with result 'oom-kill'.
Apr 13 02:26:59 xxx systemd[1]: backend.service: Triggering OnFailure= dependencies.
Apr 13 02:26:59 xxx systemd[1]: Starting Slack Notify for failure-backend...
Apr 13 02:26:59 xxx slack-notify.sh[4513]: failure-backend status: Result=oom-kill
Apr 13 02:26:59 xxx slack-notify.sh[4513]: ExecMainCode=2
Apr 13 02:26:59 xxx slack-notify.sh[4513]: ExecMainStatus=9
Apr 13 02:26:59 xxx slack-notify.sh[4527]: % Total % Received % Xferd Average Speed Time Time Time Current
Apr 13 02:26:59 xxx slack-notify.sh[4527]: Dload Upload Total Spent Left Speed
Apr 13 02:27:00 xxx slack-notify.sh[4527]: [158B blob data]
Apr 13 02:27:00 xxx slack-notify.sh[4527]: ok
# slack への通知
Apr 13 02:27:00 xxx systemd[1]: notify-slack@failure-backend.service: Deactivated successfully.
Apr 13 02:27:00 xxx systemd[1]: Finished Slack Notify for failure-backend.
slack への通知
Discussion