systemdで動かしているサービスが停止したときslackに通知する

2019年11月28日

systemd

systemd を使ってサービスを運用しているとき、突然サービスが停止してしまうことがありました。サービスが停止したら slack に通知するようにしたので備忘録。

systemd/Timers As_a_cron_replacement - ArchWiki で紹介されている方法を参考に、メール通知の部分を slack 通知に置き換えた形になります。

目次

  1. 適当なサービスを作る
  2. Slack に通知するためのサービスを作る
  3. OnFailure と %n について
  4. ユニット名の @ と %i について
  5. 参考

適当なサービスを作る

適当な常駐プログラムを作ります。今回は 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 コマンドの内容を転記しておきます。

項目説明
OnFailureA space-separated list of one or more units that are activated when this unit enters the "failed" state.
%nFull unit name
%iInstance name. For instantiated units: this is the string between the "@" character and the suffix of the unit name.

-技術ブログ
-