Ubuntu Weekly Recipe

第506回HealthcheckでCronジョブの失敗を監視する

毎日のバックアップやログの集計などをはじめ、サーバーでは定期的に様々な処理が実行されていますよね。

当然ですが、管理者はこうした処理が失敗したことに気づけなければなりません。スクリプトの中でエラーをハンドリングしたり、あるいはスクリプトの戻り値を見てメールを送信したりSlackに発言したり……といった処理を仕込んでいるのではないでしょうか。

Cronでスクリプトの失敗時にメールを送信するありがちな例
0 0 * * * root /usr/local/bin/hogehoge.sh || echo "エラーメッセージ" | mail -s "エラーですよ" admin@example.com

しかしこのアプローチには問題があります。サーバーに意図しない問題が起きている状態では、エラーメールの送信にも失敗してしまう可能性があります。その場合管理者は、正常に終了したからアラートが来ないのか、アラートの送信にすら失敗しているのか判断がつかず、問題を見逃してしまうでしょう。その結果「毎日正常に動いていたと思っていたバックアップ処理が、実は半年前から失敗し続けていることをディスク故障後に知る」といった悲劇に見舞われることになります。

そこで逆に「ジョブの成功時に監視サーバーにPingを打ち、一定期間Pingがなかったジョブを失敗したと見なす」という考え方で作られた監視システムが、今回紹介するHealthchecksです[1]⁠。

実はHealthchecksはSaaSとして運営されておりここにサインアップするだけで監視機能を利用できます。ですがせっかくですので、今回はこれと同じ監視サーバーを自前で構築する手順から紹介します[2]⁠。

依存パッケージのインストール

まずは必要なパッケージをインストールします[3]⁠。

HealthchecksはPython製のソフトウェアで、依存ライブラリのインストールにpipを利用するため、python3-pipパッケージが必要です。バックエンドのデータベースにはSQLite(デフォルト)のほかに、MySQL、PostgreSQLが選べます。筆者はMySQLを選択したので、データベース本体であるmysql-serverと、Python3のインターフェイスであるpython3-mysqldbをインストールします。また後述するアラートをメールで送信するため、postfixも必要です。

Healthchecksの依存パッケージのインストール
$ sudo apt install python3-pip python3-mysqldb mysql-server postfix

インストール中にPostfixの構成タイプとメールサーバー名を訊かれます。⁠Internet Site」を選択し、サーバーのFQDNを入力してください。

図1 Postfixの設定。Internet Siteを選択する
画像

同様に、MySQLのrootユーザーのパスワードを訊かれるので、パスワードを設定してください。

図2 MySQLのrootパスワードの設定
画像

Postfixの設定

今回Postfixはアラートの送信にしか使用せず、外部からのSMTP送信やメール受信でのPingは受け取らないため、⁠inet_interfaces」「localhost」を指定し、外部に対して25番ポートを晒さないようにします。また25番でのsasl_authを禁止しておきます。postconfで設定を行ったら、postfixを再起動します。

Postfixの設定と再起動
$ sudo postconf -e 'inet_interfaces = localhost'
$ sudo postconf -e 'smtpd_relay_restrictions = permit_mynetworks defer_unauth_destination'
$ sudo systemctl restart postfix.service

MySQLユーザーとデータベースの作成

MySQLに、Healthchecks用のMySQLユーザーとデータベースを作成します。ここではユーザー名、データベース名ともに「healthchecks」としていますが、お好みで変えても構いません。

MySQLのユーザーとデータベースの作成
$ mysql -uroot -p
Enter password:
mysql> GRANT ALL ON healthchecks.* TO healthchecks@'localhost' IDENTIFIED BY 'パスワード';
mysql> CREATE DATABASE healthchecks DEFAULT CHARACTER SET utf8;

Healthchecks用ユーザーの作成

Healthchecks用のユーザーを作成します。Healthchecksはこのユーザーのホームディレクトリ以下にインストールし[4]⁠、後述するアラート通知用サービスも、このユーザーの権限で起動させます。以後の作業はhealthchecksユーザーで行うため、sudoを実行可能にしておきます[5]⁠。

healthcheckユーザーの作成と、sudo権限の付与
$ sudo useradd -m -s /bin/bash healthchecks
$ sudo passwd healthchecks
$ sudo gpasswd -a healthchecks sudo

ソースコードの取得と依存ライブラリのインストール

healthchecksユーザーにスイッチしたら、Healthchecksのソースコードをcloneします。

ソースコードのclone
$ sudo -i -u healthchecks
$ git clone https://github.com/healthchecks/healthchecks.git

「/home/healthcecks/healthchecks」ディレクトリが作成され、コードがcloneされました。この中にある「requirements.txt」には、依存しているPythonのライブラリが列挙されています。pip3コマンドの「-r」オプションにこのファイルを渡して、依存ライブラリをインストールしてください[6]⁠。

依存ライブラリのインストール
$ cd healthchecks/
$ sudo pip3 install -r requirements.txt

Healthchecksの設定

Healthchecksの設定ファイルは、hcディレクトリ内の「settings.py」「local_settings.py」です。settings.pyにはデフォルトの設定が書かれています。local_settings.pyに設定を記述すると、settings.pyの設定を上書きできます。ですのでsettings.pyを直接編集することはせず、サイト固有の設定はlocal_settings.pyで行いましょう。

local_settings.pyは初期状態では存在しないため、サンプルの「local_settings.py.example」をコピーして雛形を作ります。

サイト固有の設定ファイルの準備
$ cp hc/local_settings.py.example hc/local_settings.py

テキストエディタでlocal_settings.pyを開いてください。冒頭に以下の3行がコメントアウトされています。コメントを解除して、適切な値を設定してください。

サイトのURLやメールアドレスの設定
# SITE_ROOT = "https://my-monitoring-project.com"
# SITE_NAME = "My Monitoring Project"
# DEFAULT_FROM_EMAIL = "noreply@my-monitoring-project.com"

SITE_ROOTはHealthchecksのFQDNです。メール通知などの中に埋め込まれるので、ブラウザーでアクセスできる正しいURLを記述してください。 SITE_NAMEはその名の通り、テンプレート全体で利用されるサイトの名前です。特に変更しなくても動作に支障はありませんが、自社のプロジェクト名などにしておくとわかりやすいでしょう。 DEFAULT_FROM_EMAILは、Healthchecksが送信するメールのFromです。スパムと判定されないためにも、自分のドメイン名にしておきましょう。

その下に、以下の行を追記してください。

settings.pyで設定されているいくつかの値を上書きする
HOST = "このサーバーでメールを受信する場合のFQDN"
PING_EMAIL_DOMAIN = HOST
PING_ENDPOINT = SITE_ROOT + "/ping/"
ALLOWED_HOSTS = ['*']
REGISTRATION_OPEN = False
TIME_ZONE = 'Asia/Tokyo'

HOSTに設定した値は、メールでPingを送る際の(受信アドレスの)ドメイン名になります。この機能は本記事では紹介しないのですが、Web UI上での表記がデフォルトのlocalhostのままなのもかっこ悪いので、このサーバーのFQDNを設定しておきます。

PING_ENDPOINTはPingを送る際のURLになります。先ほど設定したSITE_ROOTの値を元にしています。

ALLOWED_HOSTSは、サービスにアクセスを許可するホストです。デフォルトではlocalhostのみになっているため、アクセスを許可したいIPアドレスを列挙してください。ここにはWeb UIにアクセスするホストと、Pingを送信するサーバーのすべてが含まれている必要があります。上記の例ではすべてのホストを許可しています。

REGISTRATION_OPENは、Web UIからのサインアップを許可するかどうかです。クローズドな環境下で、限られたメンバーだけで運用するのであれば、不特定多数のサインアップを許可する必要はないため、Falseに設定しておくとよいでしょう。もちろん、誰でも自由に使ってよいというポリシーであれば、この行は省略して構いません。

その下にはデータベースの設定を記述します。最初からコメントアウトされた状態で各設定の例が記述されていますので、下記のようにMySQLのセクションのコメントを解除し、NAME、USER、PASSWORDに先ほどデータベースを作成した時の値を指定してください。

データベースの接続設定
# Uncomment to use MySQL:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'DBの名前(今回の例ではhealthchecks)',
        'USER': 'MySQLのユーザー名(今回の例ではhealthchecks)',
        'PASSWORD': 'MySQLのユーザー作成時に指定したパスワード',
        'TEST': {'CHARSET': 'UTF8'}
    }
}

データベースの設定のさらに下に、メール送信の設定がコメントアウトされた状態で記述されています。今回はローカルにあるPostfixからメールを送信するため、設定を書き換える必要はありません。外部のSMTPサーバーを利用する場合は、適宜設定を書き換えてください。

マイグレーションとスーパーユーザーの作成

アプリケーションの設定が完了したら、マイグレーションとスーパーユーザーの作成を行います。Healthchecksをcloneしたディレクトリにある「manage.py migrate」「manage.py createsuperuser」を実行してください。

python3でmanage.pyを実行する
$ python3 manage.py migrate
$ python3 manage.py createsuperuser
Username (leave blank to use 'healthchecks'): ← ユーザー名を入力する。ブランクのままで構わない。
Email address: healthchecks@example.com       ← スーパーユーザーのログインに使うメールアドレスを指定する。
Password: ******                              ← スーパーユーザーのパスワードを指定する。
Password (again): ******
Superuser created successfully.

この際、インタプリタとしてpython3を明示的に指定して実行しています。というのも、manage.pyのshebangには「#!/usr/bin/env python」が指定されているのですが、最近のUbuntuサーバーではPython2がインストールされていないため、以下のようにスクリプトを実行するとエラーになってしまうためです。

スクリプトを直接実行するとpythonが見つからずエラーとなる
$ ./manage.py migrate
/usr/bin/env: ‘python’: No such file or directory

テストサーバーの起動と動作確認

ここまでの作業が完了したら、Healthchecksを動かせます。以下のコマンドを実行し、manage.pyでテスト用Webサーバーを起動してください[7]⁠。

$ python3 manage.py runserver 0.0.0.0:8080

Webブラウザーからサーバーの8080番ポートに接続してください。Healthchecksのトップページが表示されていれば成功です。

図3 Healthchecksのトップページ
画像

Ctrl+Cでテストサーバーを終了できます。

WSGIで動作させる

runserverはあくまでテスト用のサーバーですので、本番での利用は推奨されません。そこでアプリケーションが動作することが確認できたら、Apacheでアプリケーションをホストするようにしましょう。ApacheとWSGIモジュールをインストールします。

Apache WebサーバーとWSGIモジュールのインストール
$ sudo apt install apache2 libapache2-mod-wsgi-py3

「/etc/apache2/sites-enabled/000-default.conf」を、以下の内容に置き換えます。もしもインストールしているパスが異なる場合は、適宜変更してください。

WSGIでHealthchecksを動かすバーチャルホストの設定
WSGIPythonPath /home/healthchecks/healthchecks
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    WSGIScriptAlias / /home/healthchecks/healthchecks/hc/wsgi.py
    WSGIPassAuthorization on
    Alias /favicon.ico /home/healthchecks/healthchecks/static/img/favicon.ico
    Alias /static /home/healthchecks/healthchecks/static
    <Directory /home/healthchecks/healthchecks>
        Require all granted
    </Directory>
</VirtualHost>

Apacheを再起動します。

Apache Webサーバーの再起動
$ sudo systemctl restart apache2.service

今度はWebブラウザーから、サーバーの80番ポートに接続してください。先ほどと同じトップページが表示されていれば成功です。

Healthchecksの使い方

それでは実際にHealthchecksを使ってみましょう。

先ほどcreatesuperuserで作成したユーザーでログインします。まずページ右上の「Log In」をクリックしてください。スーパーユーザー作成時に登録したメールアドレスを入力して「Log In」ボタンをクリックすると、そのアドレスにログイン用のURLが送信されます。リンクをクリックしてログインしてください。

なお「I want to use a password」にチェックを入れると、パスワード入力欄が表示されます。メール経由でなく、パスワードでログインしたい場合はこちらを使います。

図4 スーパーユーザーのメールアドレスを入力してログインする
画像

「Add Check」ボタンをクリックすると、Pingを送信するユニークなURLが生成されます。これが「チェック」です。

図5 新しいチェックを作成する
画像

「Name」の部分をクリックすると、チェックに名前とタグをつけられます。わかりやすい名前をつけておきましょう。タグはWeb UI上でチェックのフィルタリングに使います。スペースで区切ることで複数のタグをつけられるので、⁠web」⁠mail」⁠backup」等々、整理しやすいタグをつけておくと便利かもしれません。

生成されたばかりでPingを受信していないチェックは、ステータスが「New」となっています。ためしにこのチェックに対してPingを送信してみましょう。チェックの右端にある歯車のアイコンから「Usage Examples」をクリックすると、各種ツールや言語でPingを送信する例が表示されます。ここの「Bash」のタブにはcurlコマンドやwgetコマンドを使った例がありますので、これをコピペするのが楽でしょう。正常にPingが送信できると、チェックのステータスが「Up」になり、アイコンが緑色に変化します。

curlコマンドでPingを送信する例
$ curl --retry 3 (Ping URL)
図6 生成した直後で、まだPingを受け取っていないチェック
画像
図7 Pingを受け取った状態。指定された時間内にコマンドが正常に終了したことを表している
画像

「Period」⁠Grace」は、ステータスが変化してアラートが発生するまでの時間です。⁠Period」にはPingが送信されるインターバルを指定します。1日1回、決まった時間に実行されるジョブが対象であれば「1 day」に設定します。前回のPing送信からPeriodに設定した時間が経過すると、チェックのステータスが「Up」から「Late」になり、アイコンが黄色くなります。

「Grace」には、ステータスが「Late」になってから実際にアラートが送信されるまでの猶予時間を設定します。たとえば日によって処理にかかる時間が異なるジョブの場合、単純に「24時間以上経過したらアラート」という設定だと、処理は正常に行われているのにアラートが鳴る日と鳴らない日が出てきてしまうからです。Periodを「1 day⁠⁠、Graceを「3 hours」に設定した場合、前回Pingを受け取ってから27時間経過した時点でステータスが「Late」から「Down」になり、このタイミングでアラートが送信されます。

図8 アラートが送信されるまでの時間の設定
画像
図9 最後のPing送信からの時間経過によるステータスの変化。赤いベルのアイコンがDownで、アラートとなる
画像

インテグレーションの設定

アラートが送信される条件は前述の通りなのですが、チェックを設定しただけでは実際にアラートは飛びません。まずアラートの送信先を登録し、チェックと関連づける必要があります。このアラートの送信先をインテグレーションと呼び、インテグレーションにはメールのほか、SlackやWebhook、HipChatなどが用意されています。ここではよく使うであろう、メールのインテグレーションの設定を例に紹介します。

「INTEGRATIONS」ページにある、Emailの「Add Integration」ボタンをクリックします。

図10 インテグレーションの管理画面
画像

アラートを送信するメールアドレスを入力して「Save Integration」ボタンをクリックします。

図11 アラートを送信するメールアドレスを入力する
画像

インテグレーションが追加されますが、この状態ではまだ(unconfirmed)になっています。不正なメール送信を防止するため、メールインテグレーションでは受信者の同意が必要なためです。

図12 受信者の確認待ちの状態
画像

設定したメールアドレス宛てに確認のメールが送信されていますので、その中に記載されているURLを開いてください。⁠Email Address Verified」のページが表示されれば確認は完了です。以後、このアドレス宛てにアラートの送信ができます。

図13 メールに記載されたURLを開くと、この画面が表示されてメールアドレスの確認は完了
画像

各インテグレーションの「Assigned Checks」をクリックすると、そのインテグレーションをどのチェックと関連づけるかを選択できます。チェックごとに送信先を変えたい場合に便利です。

図14 インテグレーションとチェックの関連づけを設定する
画像

さて、実はこれだけではまだアラートは送信されません。アラートの送信は別プロセスによって行われるため、このプロセスをバックグラウンド起動しておかないと、インテグレーションを設定してもアラートは送信されないのです。インテグレーションを設定したら以下のコマンドを実行して、アラートが送信されることを確認してください。テストのためにはチェックのPeriodとGraceを極端に短くした上で、curlでPingを送ってみるとよいでしょう。

アラートを送信するプロセスを起動する
$ python3 manage.py sendalerts

sendalertsをsystemdのサービスとして動作させる

sendalertsを毎回手で起動するのは面倒ですので、正常にアラートが飛ぶことが確認できたら、systemdのサービスとして起動させましょう。 ⁠/etc/systemd/system/sendalerts.service」というファイルを、下記の内容で作成してください(例によってインストールしたパスが違う場合は適宜読み替えてください⁠⁠。

/etc/systemd/system/sendalerts.serviceの内容
[Unit]
Description = Sending Status Notifications

[Service]
ExecStart = /usr/bin/python3 /home/healthchecks/healthchecks/manage.py sendalerts
Restart = always
Type = simple
User = healthchecks

[Install]
WantedBy = multi-user.target

サービスを起動します。

作成したサービスの有効化と起動
$ sudo systemctl daemon-reload
$ sudo systemctl enable sendalerts.service
$ sudo systemctl start sendalerts.service

いかがでしょうか。Cronモニタリングシステムと名乗っているHealthchecksですが、一定時間内に応答があったかどうかを見張るという仕組みは、他にも色々と応用できそうです。インストールも簡単ですので、お手軽なプロセス監視システムがほしい方は導入を検討してみてもいいかもしれませんね。

おすすめ記事

記事・ニュース一覧