監視をしてみよう（運用）

この記事は、「[架空プロジェクトを通してシステム開発とドキュメント作成を体験してみる（2022 Late）](https://qiita.com/minimumskills/items/aedf6a93658db3f74747)」の記事の一部です。

# 概要

Webの運用には死活監視や改ざん監視を行う場合があります。ここでは、GASの定期実行機能を利用して1分毎にウェブサイトを監視する機能を実装します。
ウェブサイトに異常（サイトのダウン と サイトが改ざんされた可能性）をチェックし、もし異常がある場合は、指定したメールアドレスへアラートメールを飛ばすように設定をします。

なお、検知ルールは以下のようにしたいと思います。

- サイトダウンの判断は、ウェブサイトのhttpレスポンスのステータスが200かそうでないかで判断をしています。
- 改ざん可能性については、ウェブサイトのタイトルタグの文字が期待値と一致するかどうかで判断する簡単なものになっています。

# Google Apps Script 

[Google Apps Script Page](https://script.google.com/home?pli=1) の設定画面から「新しいプロジェクト」をクリックします。

![000001.jpg](https://drive.google.com/uc?export=view&id=13l-hQHdBb3qkVAK9Jh02Z80nSVJHUUnf)

## プロジェクト名変更

上部の「無題のプロジェクト」をクリックしてプロジェクト名をわかりやすいものに変更します。

![000010.jpg](https://drive.google.com/uc?export=view&id=1p44f3rBJT1PBjxZBcs7JRAXwtegky3v5)

ここでは「WebSite監視」として「名前を変更」をクリックすると変更が完了します。

![000020.jpg](https://drive.google.com/uc?export=view&id=1Ga2g44MEKeG3lpF5XeIkxnIxks1G9IAc)

# ウェブサイトを監視するfunctionを作成する

## httpレスポンスのステータスチェック（死活監視）

デフォルト記述されている`function myFunction() {}`を削除して以下の通り設定します。

監視先のURL（DriveToWebから確認したウェブサイトのURL）を指定します。
実際にAPIを叩くため、スプレッドシートにはデータが入ります。
設定してリクエストを投げます。
投げたら、httpResponseを受け取って、レスポンスコードが200であれば正常なのでコンソールに「正常」、200以外は「WebSiteダウン」と表示するように設定します。

```javascript:コード.gs
function checkWeb() {
  //チェック対象のサイト
  const check_url = "https://xxxxxx.on.drv.tw/website";

  //リクエストを投げる
  const response = UrlFetchApp.fetch(check_url, {
    muteHttpExceptions: true,
  });

  //ステータスをチェック
  if (response.getResponseCode() === 200) {
      console.log("正常");  
  } else {
    console.log("WebSiteダウン");
  }
}
```

## 確認

### 正常系

保存したら上部の「実行」をクリックします。

![000030.jpg](https://drive.google.com/uc?export=view&id=1m3jH5qV9kds1ZjMIYa5osC9FaDBie-zp)

アクセス権限に関してのポップアップウィンドウが出てくるので「権限を確認」をクリックします。

> 警告が表示された場合は[詳細設定]をクリックし、[xxxxx（安全でない）に移動]をクリックします。
<img width="400" alt="000040.jpg" src="https://drive.google.com/uc?export=view&id=1zV9dRNkGJxmuroQSa4tNzjUfI1q8MB7L"><img width="400" alt="000050.jpg" src="https://drive.google.com/uc?export=view&id=18GzRkWaYlL8ZSrUURGnyC3WHLCdGVTGp">

実行されると画面下部に実行ログが表示され、APIが正常に動作していて、URLが正しく設定されていれば「正常」と表示されるはずです。

![000060.jpg](https://drive.google.com/uc?export=view&id=1572RImf5Nwz18bgoOouRYB56qXY4sGlv)

### 異常系

次に異常を検知できてるか確認したいので、check_urlを一旦正しくないものにしてコンソールを確認します。

> 正しいURLはコメントアウトしておき、エラー確認用をその下に記述すると切り替えが簡単です。

```diff_javascript:コード.gs
  function checkWeb() {
    //チェック対象のサイト
+   //const check_url = "https://oz89n3tinyjmyu6xb7e79g.on.drv.tw/website";
+   //エラー確認用（サーバダウン）
+   const check_url = "https://xxxxxxx.on.drv.tw/website";
```

ログに「WebSiteダウン」が表示されて実行完了が表示されていれば検知できています。

![000070.jpg](https://drive.google.com/uc?export=view&id=18khHopjnwTwXpUJhf2QCKBgx7cEDnHht)

確認できたら、check_urlは正しいものに戻します。

## タイトルチェック（改ざん監視）

ウェブサイトのステータスを確認してダウンしていなかった場合、次のチェックに進みます。
titleタグの値を取得して正しい値が入っているかをチェックします。
titleタグには「WebSite」を設定しているので、返ってくる期待値もWebSiteとします。
異なる値が返ってきた場合、ウェブサイトが改ざんされている可能性があるので検知できるようにします。

```diff_javascript:コード.gs
  function checkWeb() {
    //チェック対象のサイト
    //const check_url = "https://oz89n3tinyjmyu6xb7e79g.on.drv.tw/website";
    //エラー確認用（サーバダウン）
    const check_url = "https://xxxxxxx.on.drv.tw/website";

    //リクエストを投げる
    const response = UrlFetchApp.fetch(check_url, {
      muteHttpExceptions: true,
    });

    //ステータスをチェック
    if (response.getResponseCode() === 200) {
-     console.log("正常");
+     //内容チェック
+     const title_exp = /<title>(.*?)<\/title>/;
+     const titleArray = response.getContentText().match(title_exp);
+     const title = titleArray[1];
+      if (title === "WebSite") {
+       console.log("正常");
+     }else{
+       console.log("改ざんされているかも");
+     }
    } else {
      console.log("WebSiteダウン");
    }
  }
```

```javascript:コピペ用
const title_exp = /<title>(.*?)<\/title>/;
const titleArray = response.getContentText().match(title_exp);
const title = titleArray[1];
if (title === "WebSite") {
  console.log("正常");
}else{
  console.log("改ざんされているかも");
}
```

## 確認

### 正常系

保存して確認します。
「正常」とコンソールに表示されれば順調です。

![000080.jpg](https://drive.google.com/uc?export=view&id=1udWe-RmtLRTwqcA-fZkHJmmjLppgXQmu)

### 異常系

異常系を確認するため、タイトルタグの期待値を変更して確認したいと思います。

> 本来はウェブサイト側のレスポンスを変えるべきですが、ここでは一旦期待値を変えてエラーを出現させてみます。

```diff_javascript:コード.gs
- if (title === "WebSite") {
+ if (title === "一時的に変更") {
```

保存して実行してみます。
期待値が変更されたため、一致せず「改ざんされているかも」というログが表示されました。

![000090.jpg](https://drive.google.com/uc?export=view&id=1bboO9Djf3oipFyFP6RJPixsjPlh2bfBv)


## メールを送信する

これでチェック機構はできたので、異常があった場合はメールを送信するように変更します。
上部でメールを受け取るメールアドレスを設定します。
エラーを検知した場合は、コンソールで表示ではなくてメール送信するため、送信先、タイトル、内容を設定してsendEmailを設定します。

```diff_javascript:コード.gs
  function checkWeb() {
    //チェック対象のサイト
    const check_url = "https://oz89n3tinyjmyu6xb7e79g.on.drv.tw/website";
    //エラー確認用（サーバダウン）
    // const check_url = "https://xxxxxxx.on.drv.tw/website";

+   const monitoring_email = "{監視用メールアドレス}";

    //リクエストを投げる
    const response = UrlFetchApp.fetch(check_url, {
      muteHttpExceptions: true,
    });

    //ステータスをチェック
    if (response.getResponseCode() === 200) {
      //内容チェック
      const title_exp = /<title>(.*?)<\/title>/;
      const titleArray = response.getContentText().match(title_exp);
      const title = titleArray[1];
      if (title === "一時的に変更") {
        console.log("正常");
      }else{
-       console.log("改ざんされているかも");
+       MailApp.sendEmail({
+         to: monitoring_email,
+         subject: "警告：WebSiteエラー検知",
+         htmlBody: `WebSiteの異常を検知しました。改ざんの可能性があります。確認してください。`,
+       });
      }
    } else {
-     console.log("WebSiteダウン");
+     MailApp.sendEmail({
+         to: monitoring_email,
+         subject: "警告：WebSiteダウン検知",
+         htmlBody: `WebSiteのダウンを検知しました。確認してください。`,
+       });
    }
  }
```

```javascript:コピペ用（改ざん）
MailApp.sendEmail({
  to: monitoring_email,
  subject: "警告：WebSiteエラー検知",
  htmlBody: `WebSiteの異常を検知しました。改ざんの可能性があります。確認してください。`,
});
```

```javascript:コピペ用（ダウン）
MailApp.sendEmail({
  to: monitoring_email,
  subject: "警告：WebSiteダウン検知",
  htmlBody: `WebSiteのダウンを検知しました。確認してください。`,
});
```

## 確認

### 改ざん検知

この状態で保存すると、titleタグの期待値が「一時的に変更」となっているため「WebSiteエラー検知」のメールが送られます。

<img width="500" alt="000100.jpg" src="https://drive.google.com/uc?export=view&id=1Qo-CLPgfL9dzr5diyK0Xc4RkFRY5lUrt">

### ダウン検知

check_urlを不正なURLに変更して実行すると、「WebSiteダウン検知」のメールが送信されます。

<img width="500" alt="000110.jpg" src="https://drive.google.com/uc?export=view&id=1iURIxdZ5g6Ugzexx8XECJcTxnaylsEPp">

:::note warn
メールが届かない場合、「すべてのメール」を確認してみてください。（Gmailの場合）
そこにも無ければメールアドレスが間違っているか、その他の部分でコードが間違っている可能性があります。
:::

# トリガーを作成してスケジュール実行

監視スクリプトができたのでこれを1分毎にスケジュール実行します。
左にある時計のマークをクリックしてトリガーに移動します。
右下の「トリガーを追加」をクリックします。

![000120.jpg](https://drive.google.com/uc?export=view&id=1eRM22L359X4OJB2zC1vkWKdMGHuKS9gZ)
ポップアップウィンドウが開くので監視を実行したいスケジュールを設定します。
今回は1分おきに実行するようにしてみます。
以下の通り設定をしたら「保存」をします。

- 実行する関数
    - 「checkWeb」を選択。
- 実行するデプロイは、
    - 「HEAD」を選択。
（デプロイをバージョン管理することで、実行するバージョンを選択することができます。HEADだと最新のバージョンが実行されるようになります）
- イベントのソース
    - 「時間主導型」を選択。
- 時間ベースのトリガーのタイプ
    - 「分ベースのタイマー」を選択。
- 時間の感覚を選択（分）
    - 「1分おき」を選択。
- エラー通知設定
    - 「今すぐ通知を受け取る」を選択。
（このエラー通知は、この関数実行にエラーがあった場合の通知です）

<img width="500" alt="000130.jpg" src="https://drive.google.com/uc?export=view&id=1aT5aYD_JSexpHrGn-etzT6hHnjgHMLPr">


保存ができると、一覧にトリガーが追加されました。

![000140.jpg](https://drive.google.com/uc?export=view&id=1uYZD3QBmavGKOB_GvrNf5YcsLiMg2kOg)


しばらくしてページを更新すると、「前回の実行」に日時が表示されます。
スケジュールに従って監視が実行されていることがわかります。

![000150.jpg](https://drive.google.com/uc?export=view&id=1Y0vS1mGS5CdzwMUxCTvqBbmV4ZilECT6)


:::note warn
check_urlやAPIレスポンスの期待値を正常にしていないと1分毎にメールが届き続けます。
正しい状態に変更しておきます。
:::

# トリガーの停止方法
トリガーを停止するには、設定したトリガー削除をします。

![000160.jpg](https://drive.google.com/uc?export=view&id=1IXJ4auhop8MvDebPU_8d9DReEvJTkPGL)

# まとめ

* Webシステムはリリース後、死活監視や改ざん監視を行うことが一般的
  * 多くの場合専用ツール（Zabbixとか）やサービスを利用する
* 簡易チェックなら自分で実装することも可能

# ドキュメント作成視点での考察

* 監視の有無、種類、方法などはどこにどう記述すべきか？

# 関連コンテンツ

* 次：[表紙ページ](https://qiita.com/minimumskills/items/aedf6a93658db3f74747)
* 前：[テストをしてみよう（セキュリティーテスト）](https://qiita.com/minimumskills/items/bb4feb7445c866e6f464)