systemd を使ってサービスを運用しているとき、突然サービスが停止してしまうことがありました。サービスが停止したら slack に通知するようにしたので備忘録。
systemd/Timers As_a_cron_replacement - ArchWiki で紹介されている方法を参考に、メール通知の部分を slack 通知に置き換えた形になります。
目次
適当なサービスを作る
適当な常駐プログラムを作ります。今回は 2 秒ごとにログを出力するだけの簡単なプログラムを作りました。
/usr/local/bin/my-service
#!/usr/bin/env bash
while sleep 2; do
echo "my-service is working"
done
実行権限を与えます。
chmod +x /usr/local/bin/my-service
作ったプログラムを systemd でサービスにします。
/etc/systemd/system/my-service.service
[Unit]
Description=my-service
[Service]
Type=simple
ExecStart=/usr/local/bin/my-service
[Install]
WantedBy=multi-user.target
systemd をリロードして作成した unit ファイルを読み込みます。
systemctl daemon-reload
サービスを起動します。
systemctl start my-service
起動しているか確認します。
systemctl status my-service
active (running)
と表示されていれば起動しています。
my-service.service - my-service
Loaded: loaded (/etc/systemd/system/my-service.service; disabled; vendor preset: disabled)
Active: active (running) since Thu 2019-11-28 12:12:12 UTC; 3min 6s ago
Main PID: 12576 (bash)
CGroup: /system.slice/my-service.service
├─12576 bash /usr/local/bin/my-service
└─12682 sleep 2
ここまでで、システムに常駐するサービスができました。次は、このサービスが異常停止したときに Slack に通知するようにします。
Slack に通知するためのサービスを作る
以下のように、Slack に通知するプログラムを作ります。Slack の Incoming webhook URL が必要になりますので、取得しておきます。取得方法は他サイトをご確認ください。
/usr/local/bin/send-systemd-status-to-slack
#!/usr/bin/env bash
channel=hoge-channel
text=$(systemctl status --full "$1")
webhook=https://hooks.slack.com/services/XXXX/XXXX/XXXX
curl -X POST --data-urlencode "payload={\"channel\": \"${channel}\", \"text\": \"${text}\" }" "${webhook}"
実行権限を与えておきます。
chmod +x /usr/local/bin/send-systemd-status-to-slack
テスト実行してみて、目的のチャンネルに通知が送信されるか確認しておきます。
/usr/local/bin/send-systemd-status-to-slack sshd.service
このプログラムをサービスにします。
/etc/systemd/system/send-systemd-status-to-slack@.service
[Unit]
Description=Send systemd %i unit status to slack
[Service]
Type=oneshot
ExecStart=/usr/local/bin/send-systemd-status-to-slack %i
User=nobody
Group=systemd-journal
上記のユニットのファイル名に @ が含まれていて通常のユニットファイルと異なります。後ほど解説しますが、ユニットにパラメータとして文字列を渡すためにこのようにします。
次に、my-service.service に OnFaulure の定義を追加し、サービスが失敗したときに Slack に通知するサービスを呼ぶようにします。
/etc/systemd/system/my-service.service
[Unit]
Description=my-service
OnFailure=send-systemd-status-to-slack@%n.service
[Service]
Type=simple
ExecStart=/usr/local/bin/my-service
[Install]
WantedBy=multi-user.target
systemd をリロードして変更を反映させます。
systemctl daemon-reload
サービスを強制終了します。
systemctl kill my-service -s SIGKILL
Slack に通知されれば成功です。
OnFailure と %n について
/etc/systemd/system/my-service.service
OnFailure=send-systemd-status-to-slack@%n.service
OnFailure
はユニットのステータスが failed になったときに、記載したサービスを起動します。
%n
は Full unit name という意味の変数で、実行時にユニット名 my-service.service が入ります。
まとめると、my-service.service が failed になったときに、send-systemd-status-to-slack@my-service.service.service ユニットを開始するという意味になります。
こんな感じで、OnFailure に指定したサービスのテストをすることもできます。
systemctl start send-systemd-status-to-slack@my-service.service.service
詳しくは、man systemd.unit
コマンドを実行して OnFailure と %n の説明をご確認ください。
ユニット名の @ と %i について
/etc/systemd/system/send-systemd-status-to-slack@.service
ExecStart=/usr/local/bin/send-systemd-status-to-slack %i
%i
は Instance name という意味の変数で、実行しているユニット名の @ と .service の間の文字、my-service.service が入ります。
今回の例をまとめると OnFailure で send-systemd-status-to-slack@my-service.service.service のユニットが開始されます。%i には @ と .service の間の文字、my-service.service が入り、シェルスクリプト send-systemd-status-to-slack の引数に my-service.service が渡され、Slack に通知される流れになります。ややこしいですね。
%i
についても詳しくは man systemd.unit
コマンドに詳しく書いてあります。
参考
man systemd.unit
コマンドの内容を転記しておきます。
項目 | 説明 |
---|---|
OnFailure | A space-separated list of one or more units that are activated when this unit enters the "failed" state. |
%n | Full unit name |
%i | Instance name. For instantiated units: this is the string between the "@" character and the suffix of the unit name. |