ドメイン死活監視 （curlでステータスチェック＋メール通知＋Slack通知）

## 前書き

前に、curlでドメインの死活監視をしたら良いんじゃないか。
といった記事を書きました。
https://qiita.com/masashi-sawai/items/2661c2a392d737ca4b4d


上記は色々と中途半端でしたので、やりたいことを整理し直して再投稿してみました。


## 何がしたいのか

ドメインを運用している最中で、
ある日突然、特定のページだけ死んでいた！といった事態を、
なる早で検知できる仕組みが欲しいです。

外部サービスとかで有償でできるものは多いみたいですが、
とりあえずコストをかけずにプレで走らせれる仕組みにしたい。


##  何をやるのか

以下３点、やってみます。
１：ドメインの死活監視を行う
２：監視の結果は結果に関わらずメールで配信する
３：まずい結果の時はメールに加えてSlackでも配信する


## １：ドメインの死活監視を行う

死活監視はリクエストしたいURLをcurlで叩いて、
ステータスコードを見て判断してみます。

### ステータスコード

| ステータスコード       |概要 |    死 or 活    |
|:-----------------|:------------------|:------------------:|
| 2xx系 | リクエストは通った。 | 活 |
| 3xx系 | リダイレクトして、期待するURLへリダイレクトした。 | 活 | 
| 3xx系 | リダイレクトして、期待しないURLへリダイレクトした。 | 死 |
| 4xx系 | リクエストした先は存在しないページだった。 | 活 |
| 4xx系 | リクエストした先は存在するはずのページだった。 | 死 |
| 5xx系 Server Error | リクエストをサーバが通せなかった。 | 死 |



Webアプリケーションの組み方にもよリますが、
一般的なステータスの振る舞いは上記の様な形になると思います。

これを踏まえて、リクエストしたいURLを以下フォーマットでCSVにしました。


### url.csv

``````
https://www.yahoo.co.jp/,200,
http://www.microsoft.com/ja-jp/,301,https://www.microsoft.com/ja-jp/
https://www.yahoo.co.jp/a/,404,
``````

1列目：リクエストURL
2列目：期待するステータスコード
3列目：3xx系専用でリダイレクト先URL

このCSVを１行ずつ読ませて、
期待するステータスコードが返ってくるかを見ていく処理を書きます。

### シェルスクリプト

``````shell
# ドメイン死活監視処理
csv=./url.csv
logs=""
chk=""
cnt_ok=0
cnt_warning=0
cnt_alert=0

## ドメイン死活監視対象の定義ファイルを1行ずつ読みこむ
for urls in `cat ${csvfile} | grep -v ^#`
do
    ## ドメイン死活監視対象の定義ファイルの内容を変数化
    request_url=`echo ${urls} | cut -d ',' -f 1`
    ans_status=`echo ${urls} | cut -d ',' -f 2`
    ans_redirect_url=`echo ${urls} | cut -d ',' -f 3`

    ## リクエストURLをCURLで叩いた結果を変数化
    get_url=$(curl -skL "${request_url}" -o /dev/null -w '%{url_effective}\n')
    get_status=$(curl -sk "${request_url}" -o /dev/null -w '%{http_code}\n')

    # 結果チェック
    if [ ${get_status} = ${ans_status} ]; then
        if [ ! ${ans_status:0:1} = 3 ]; then
            # 実行結果と期待結果のステータスが一致していればOK
            chk="OK"
            cnt_ok=$(( cnt_ok + 1 ))
        else
            # 期待結果のステータスが3xxの場合はリダイレクト先URLも確認
            if [ ${get_url} = ${ans_redirect_url} ]; then
                # 実行結果と期待結果のリダイレクトURLが一致していればOK
                chk="OK"
                cnt_ok=$(( cnt_ok + 1 ))
            else
                # 実行結果と期待結果のリダイレクトURLが一致していなければWARNING_REDIRECT
                chk="WARNING_REDIRECT"
                cnt_warning=$(( cnt_warning + 1 ))
            fi
        fi
    elif [ ${get_status:0:1} = 5 ]; then
        # 実行結果が5xxの場合はALERT
        chk="ALERT"
        cnt_alert=$(( cnt_alert + 1 ))
    else
        # 実行結果と期待結果のステータスが一致していなければWARNING_STATUS
        chk="WARNING_STATUS"
        cnt_warning=$(( cnt_warning + 1 ))
    fi

    # ログ出力
    logs+="[${chk}] REQ: ${ans_status}, ${request_url} --> GET: ${get_status}, ${get_url}\n"
    #printf "[${chk}] REQ: %s, %s --> GET: %s, %s\n" $chk $ans_status $request_url $get_status $get_url
    if [ ${chk} = "WARNING_REDIRECT" ]; then
        logs+=" (Assumed RedirectUrl = ${ans_redirect_url})\n"
        #echo " (Assumed RedirectUrl = ${ans_redirect_url})\n"
    elif [ ${chk} = "WARNING_STATUS" ]; then
        logs+=" (Assumed Status = ${ans_status})\n"
        #echo " (Assumed Status = ${ans_status})\n"
    elif [ ${chk} = "ALERT" ]; then
        logs+=" (Server Error)\n"
        #echo " (Server Error)\n"
    fi

    #sleep 1s
done < ./url.csv
echo ${logs}
``````

実行結果は以下の様になりました。

### 実行結果

``````
[OK] REQ: 200, https://www.yahoo.co.jp/ --> GET: 200, https://www.yahoo.co.jp/
[OK] REQ: 301, http://www.microsoft.com/ja-jp/ --> GET: 301, https://www.microsoft.com/ja-jp/
[OK] REQ: 404, https://www.yahoo.co.jp/a/ --> GET: 404, https://www.yahoo.co.jp/a/
``````
期待動作になっているかを確認する為、
実行時の状態を$logsという変数に残しておきました。

ひとまず期待動作かと思います。


## ２：監視の結果はメールで配信する

上記結果だけでは、
シェルスクリプトの実行者にしか結果が伝わらないので、
結果をメール配信する様にします。

まずはシェルスクリプトの実行環境で、メール配信が可能かを確認します。
自分はpostfixが入っているかを確認してみました。

### postfixのバージョン確認

```````
postconf | grep mail_version
mail_version = 3.2.2
milter_macro_v = $mail_name $mail_version
```````

バージョンが返ってきたので問題なさそうです。
もし入っていない場合は、postfix等をインストールできる環境かを確認の上、
インストールを行っておいてください。

続いてシェルスクリプトのメール配信です。

### シェルスクリプト

```````shell
# 実行結果のメール配信
export PATH=$PATH:/usr/sbin
MAIL_TO=“送信元メアド”
MAIL_FROM=“送信先メアド”
subject="Domain Check Test”
alert_subject="** Alert ** "
warning_subject="* Warning * "

## ALERT, WARNING発生時はメール件名を更新
if [ ${cnt_alert} > 0 ]; then
    subject=${alert_subject}${subject}
elif [ ${cnt_warning} > 0 ]; then
    subject=${warning_subject}${subject}
fi

## メール配信
mail_send () {
cat << EOD | nkf -j -m0 | sendmail -t
From: ${MAIL_FROM}
To: ${MAIL_TO}
Subject: ${subject}
MIME-Version: 1.0
Content-Type: text/plain; charset="ISO-2022-JP"
Content-Transfer-Encoding: 7bit
■ドメイン死活監視結果
- OK:`echo -e ${cnt_ok}`
- WARNING:`echo -e ${cnt_warning}`
- ALERT:`echo -e ${cnt_alert}`

■結果ログ
`echo -e ${logs}`
EOD
}
mail_send

```````

上記処理をドメイン死活の後に続けて実行し、
以下のメールが配信されることが確認できました。

### 配信メール

```````
件名：Domain Check Test
■ドメイン死活監視結果
- OK:3
- WARNING:0
- ALERT:0

■結果ログ
[OK] REQ: 200, https://www.yahoo.co.jp/ --> GET: 200, https://www.yahoo.co.jp/
[OK] REQ: 301, http://www.microsoft.com/ja-jp/ --> GET: 301, https://www.microsoft.com/ja-jp/
[OK] REQ: 404, https://www.yahoo.co.jp/a/ --> GET: 404, https://www.yahoo.co.jp/a/
```````

なお、WARNINGやALERTがある場合は、
件名の見出しに ** WARNING **、** ALERT **  が付いてきます。


## ３：まずい結果の時はメールに加えてSlackでも配信する

まずいことがおこっている時は、
メール通知だけでは初動が遅れてしまいがちです。
初動が早く取れるように、まずい時限定でSlackにも通知する様にしてみます。

シェルスクリプトの実行結果をSlackへ通知したい場合は、SlackのWebhookを使います。
通知したいチャンネルでログインしている状態で以下のURLを踏めばOKです。
https://slack.com/services/new/incoming-webhook

（詳しくはこちら。とてもわかりやすくまとめておられる記事です。
https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008
）

なお、いきなり general とかに流すと、
他のメンバにも通知が飛んでしまいますので、
テスト完了までは「自分」にだけ飛ばすようにしておきましょう。

Webhook URL が取得できたら、通知用の処理を書いていきます。

### シェルスクリプト

``````shell
#Slack通知準備
SLACK_URL=（WebhookURLを定義）
FALLBACK="ドメイン死活監視"
PRETEXT=$pretext_work
TITLE="実行ログは以下の通りです"
TITLE_LINK=“Slackタイトル文字列にリンクURLをつけたい時はこちら”
TEXT=$logs
COLOR_BAR="#FF0000"
EMOJI=:ghost:_BAR
USER="m.sawai.bot"
MESSAGE="*** Domain Check Message ***"

slack_push=0
pretext_work="ドメイン死活監視でWARNINGが発生しました"
if [ $cnt_warning -gt 0 ]; then
    slack_push=1
    pretext_work="ドメイン死活監視でWARNINGが発生しました"
fi
if [ $cnt_alert -gt 0 ]; then
    slack_push=1
    pretext_work="ドメイン死活監視でALERTが発生しました"
fi

ATTACHMENTS='{'\
'"fallback": "'$FALLBACK'",'\
'"pretext": "'$PRETEXT'",'\
'"title": "'$TITLE'",'\
'"title_link": "'$TITLE_LINK'",'\
'"text": "'$TEXT'",'\
'"color": "'$COLOR_BAR'"'\
'}'

PAYLOAD=''\
'payload={"text": "'$MESSAGE'", '\
'"icon_emoji": "'$EMOJI'", '\
'"username": "'$USER'", '\
'"attachments":['$ATTACHMENTS']}'

# WARNING, ALERT時はSlack通知用準備
if [ $slack_push -eq 1 ]; then
    curl -X POST --data-urlencode "$PAYLOAD" $SLACK_URL
fi
``````

実行してみました。
この時点では、url.csv 通りの動作が確認できているので、
Slackへの通知は届きません。

Slackへの通知を確認するために、url.csv にわざとNGパターンを加えます

### url.csv

```````
https://www.yahoo.co.jp/,200,
http://www.microsoft.com/ja-jp/,301,https://www.microsoft.com/ja-jp/
https://www.yahoo.co.jp/a/,404,
https://www.yahoo.co.jp/b/,200,
```````

4行目に、本来404を返すべきURLに200を期待値とした定義を追加しました。
これで動作を見てみます。

### 実行結果
![スクリーンショット 2019-07-07 18.45.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379795/36026097-1d9b-6bed-c7e5-dc11a9e5e0e8.png)


Slackに通知が来ました。
あとはこれを、然るべきサーバで cron に入れて回していけば、
死活監視としては最低限利用できそうです。


## まとめ

ドメイン死活監視対象のWebアプリケーションによっては、
上記対応だけでは監視には不十分なケースが多いかと思いますが、
ある程度の状態をチェックしていくには利用価値があるかと思います。

回すのにコストがほとんどかかってないのは嬉しい点かもですね。

最後に、今回書いたシェルスクリプトをそのまま載せておきます。
（ダダ書きでとてもかっこ悪いので関数化なり別ファイル化なりは近々やっておこう。。。）

``````shell
# ドメイン死活監視処理
csv=./url.csv
logs=""
chk=""
cnt_ok=0
cnt_warning=0
cnt_alert=0

## ドメイン死活監視対象の定義ファイルを1行ずつ読みこむ
for urls in `cat ${csvfile} | grep -v ^#`
do
    ## ドメイン死活監視対象の定義ファイルの内容を変数化
    request_url=`echo ${urls} | cut -d ',' -f 1`
    ans_status=`echo ${urls} | cut -d ',' -f 2`
    ans_redirect_url=`echo ${urls} | cut -d ',' -f 3`

    ## リクエストURLをCURLで叩いた結果を変数化
    get_url=$(curl -skL "${request_url}" -o /dev/null -w '%{url_effective}\n')
    get_status=$(curl -sk "${request_url}" -o /dev/null -w '%{http_code}\n')

    # 結果チェック
    if [ ${get_status} = ${ans_status} ]; then
        if [ ! ${ans_status:0:1} = 3 ]; then
            # 実行結果と期待結果のステータスが一致していればOK
            chk="OK"
            cnt_ok=$(( cnt_ok + 1 ))
        else
            # 期待結果のステータスが3xxの場合はリダイレクト先URLも確認
            if [ ${get_url} = ${ans_redirect_url} ]; then
                # 実行結果と期待結果のリダイレクトURLが一致していればOK
                chk="OK"
                cnt_ok=$(( cnt_ok + 1 ))
            else
                # 実行結果と期待結果のリダイレクトURLが一致していなければWARNING_REDIRECT
                chk="WARNING_REDIRECT"
                cnt_warning=$(( cnt_warning + 1 ))
            fi
        fi
    elif [ ${get_status:0:1} = 5 ]; then
        # 実行結果が5xxの場合はALERT
        chk="ALERT"
        cnt_alert=$(( cnt_alert + 1 ))
    else
        # 実行結果と期待結果のステータスが一致していなければWARNING_STATUS
        chk="WARNING_STATUS"
        cnt_warning=$(( cnt_warning + 1 ))
    fi

    # ログ出力
    logs+="[${chk}] REQ: ${ans_status}, ${request_url} --> GET: ${get_status}, ${get_url}\n"
    #printf "[${chk}] REQ: %s, %s --> GET: %s, %s\n" $chk $ans_status $request_url $get_status $get_url
    if [ ${chk} = "WARNING_REDIRECT" ]; then
        logs+=" (Assumed RedirectUrl = ${ans_redirect_url})\n"
        #echo " (Assumed RedirectUrl = ${ans_redirect_url})\n"
    elif [ ${chk} = "WARNING_STATUS" ]; then
        logs+=" (Assumed Status = ${ans_status})\n"
        #echo " (Assumed Status = ${ans_status})\n"
    elif [ ${chk} = "ALERT" ]; then
        logs+=" (Server Error)\n"
        #echo " (Server Error)\n"
    fi

    #sleep 1s
done < ./url.csv
echo ${logs}

# 実行結果のメール配信
export PATH=$PATH:/usr/sbin
MAIL_TO=“送信元メアド”
MAIL_FROM=“送信先メアド”
subject="Domain Check Test”
alert_subject="** Alert ** "
warning_subject="* Warning * "

## ALERT, WARNING発生時はメール件名を更新
if [ ${cnt_alert} > 0 ]; then
    subject=${alert_subject}${subject}
elif [ ${cnt_warning} > 0 ]; then
    subject=${warning_subject}${subject}
fi

## メール配信
mail_send () {
cat << EOD | nkf -j -m0 | sendmail -t
From: ${MAIL_FROM}
To: ${MAIL_TO}
Subject: ${subject}
MIME-Version: 1.0
Content-Type: text/plain; charset="ISO-2022-JP"
Content-Transfer-Encoding: 7bit
■ドメイン死活監視結果
- OK:`echo -e ${cnt_ok}`
- WARNING:`echo -e ${cnt_warning}`
- ALERT:`echo -e ${cnt_alert}`

■結果ログ
`echo -e ${logs}`
EOD
}
#mail_send

#Slack通知準備
SLACK_URL=（WebhookURLを定義）
FALLBACK="ドメイン死活監視"
PRETEXT=$pretext_work
TITLE="実行ログは以下の通りです"
TITLE_LINK=“Slackタイトル文字列にリンクURLをつけたい時はこちら”
TEXT=$logs
COLOR_BAR="#FF0000"
EMOJI=:ghost:_BAR
USER="m.sawai.bot"
MESSAGE="*** Domain Check Message ***"

slack_push=0
pretext_work="ドメイン死活監視でWARNINGが発生しました"
if [ $cnt_warning -gt 0 ]; then
    slack_push=1
    pretext_work="ドメイン死活監視でWARNINGが発生しました"
fi
if [ $cnt_alert -gt 0 ]; then
    slack_push=1
    pretext_work="ドメイン死活監視でALERTが発生しました"
fi

ATTACHMENTS='{'\
'"fallback": "'$FALLBACK'",'\
'"pretext": "'$PRETEXT'",'\
'"title": "'$TITLE'",'\
'"title_link": "'$TITLE_LINK'",'\
'"text": "'$TEXT'",'\
'"color": "'$COLOR_BAR'"'\
'}'

PAYLOAD=''\
'payload={"text": "'$MESSAGE'", '\
'"icon_emoji": "'$EMOJI'", '\
'"username": "'$USER'", '\
'"attachments":['$ATTACHMENTS']}'

# WARNING, ALERT時はSlack通知用準備
if [ $slack_push -eq 1 ]; then
    curl -X POST --data-urlencode "$PAYLOAD" $SLACK_URL
fi
``````