【GCP】SV死活監視、アラート通知にPub/Subを設定する方法とチャットワーク通知の実装

# 目的

・顧客へのサービス提供

・何らかの理由で鯖落ちしてたときなどに、ユーザーからの問い合わせより前に
こちら側が先に気づける仕組みを作りたい。

# 手段

サーバーの死活監視
（GCPのUptime Checkを活用します。）

# 実現するための構想

Stackdriver（Monitoring）→Pub/Sub→Cloudfunctions→Chatwork

StackdriverでGCEの異常を検知し、Pub/Subを経由しCloudfunctionsからChatworkへ通知
という流れで構築していきます。

GCE（Google Compute Engine）のStackdriver-Monitoringの通知チャンネルを設定し
GCEに異常があればStackdriverからインシデントアラートをPub/Subに送り、
Pub/SubからCloudfunctionsで取得してチャットワークへ通知する。
（Chatworkトークンなどは準備済のため、今回は割愛する。）

---

あと、通知アラートを直接Pub/Subに送る機能（通知チャンネルにPub/Subを設定すること）は
まだベータ版のようで、導入するのにGcloudコマンドラインツールが必要になるため、
一旦アラート通知はEmailを設定して動作を確認し、後ほどPub/Subに載せ替える流れにしています。

日本語版にはドキュメントがなく英語版でしか確認できない機能だったので、
ちょっと苦労したんですが、無事に導入できたので、ご紹介します。

---

# では実際にやってみる。

無事に顧客への導入も行いましたので、同様の業務が必要になったときは、参考になれば嬉しいです。

## GCEインスタンスを立ち上げる。

### GCPに登録

メールアドレスとかパスワードとか入力して登録。
クレジットカード番号も必要だけど無料で使えます。（約12ヶ月分の無料枠アリ）

### プロジェクトを作る

プロジェクト名とか入れるぐらい。

### 左上ナビゲーションメニューからVMインスタンスのページへ飛ぶ

**コンピューティング→Compute Engine→VM インスタンス**
（それぞれ名前の横にピン留めできるボタンがあるので、よく使うものはピン留めすると良いと思います。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/6fd9f1e6-cc05-5621-0e94-b2d631fec41e.png)

- インスタンスを作成
  - インスタンス名
  - リージョン（色々あるけどasia-northeast1で良いかと。）
  - ゾーン
  - マシンタイプ（今回はf1-microに設定。）
  - ブートディスク（CentOS 7に設定。）
  - HTTPトラフィックを許可する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/fabd53cb-f87a-0616-13dc-f32b822c0ce3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/1528be69-57a4-23f0-0180-23cc8a5f7779.png)


ページ下部の「作成」を押下しちょっと待つとインスタンスが立ち上がる。

## StackDriverでMonitoringする。

### 左メニューからStackdriver Monitoringのページへ飛ぶ

**OPERATIONS→Monitoring→Overview**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/b46b57d8-652b-6e61-b64a-5d9586cf3464.png)


### アラートポリシーを追加

**Create Alert PoliciesのCREATE POLICYをクリックし、ポリシー追加ページへ**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/087116e3-accf-0489-1a5c-934a790c3d7a.png)


- アラートポリシーを設定
    - 名前
    - Condition
        - Target（アラートを上げるトリガー・条件について設定）
        - Aggregator
        - Configuration（確認頻度などの設定）
    - Notification（通知方法）
    - Documentation（通知内容）

例えば…
> 自前で立ち上げたGCEインスタンスのCPU使用率が80％を閾値に監視したい、
> かつ、アラート対象状態になった場合にメールで通知してほしい

- Condition
    - Target：GCE VM Instance、CPU Utilization
    - Aggregator：mean
    - Configuration：Any Time、is above、80、1minute

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/99f29c07-5c79-95fb-caf0-cc475ff960d0.png)

- Notification
    - Email（自分のメールアドレスを指定）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/d03310ac-9e85-1ed9-d608-8fa38cbb9c68.png)


## GCEインスタンス内でWebサーバーを立ち上げる

アラートを設定し終わったら、VMインスタンスのダッシュボードページに戻る。
先ほど設定したインスタンスが表示されているはず。

**画面右側のSSHをクリックすると、コンソールが立ち上がり、インスタンス内に接続した状態になっている。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/738f6aee-c299-bc7a-fc61-103aee819694.png)


```console
$ sudo su
$ yum install httpd -y
$ systemctl start httpd.service
```

問題なくサーバーが立ち上がったようであれば

```console
$ echo '<!doctype html><html><body><h1>Hello World!</h1></body></html>' | sudo tee /var/www/html/index.html
```

その後Chromeとかブラウザのアドレスバーに

`http://外部IP`

を入力して、ページにHello World!が表示されていればOK

## 負荷をかけてみる。

サーバーが立ち上がっている状態で、

```console
$ yes > /dev/null &
```

このコマンドでCPU使用率はバチバチに跳ね上がっていきます。
インスタンスの設定でCPU増やしてたりする場合は、その分実行すればOK？

ちなみにCPU使用率を確認する際は、

```console
$ top
```
topコマンドを実行すると立ち上がっているプロセスそれぞれのCPU使用率が一覧される。

しばらく放っておくと、アラートポリシーで登録したメールアドレス宛にAlertのメールが飛んでくる。
まずはこのメールが届けばOK。

## Health Checkで死活管理してみる。

### MonitoringのページからUptime Checksの設定を行う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/9b8861f8-94ff-d05d-3679-9f0c61a9ba59.png)

- 項目
    - Title
    - Check Type（HTTP、HTTPS、TCP）
    - Resource Type（URL、App Engine、Elastic Load Balancer、Instance）
    - Host Name
    - Path
    - Check Every

例えば。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/fadac814-f819-f2ec-cf37-39e9f354cfb2.png)


ResourceTypeをURLに設定し、HostNameにgoogle.co.jpと入力し、
TESTをクリックすると、 Responded with 200 (OK) in 〜〜　となるはず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/1f98bb8b-a29b-485d-e299-093128c71d39.png)

- ということで
    - ResourceTypeをInstance
    - Instanceのフォームが出てくるので、上記で設定したインスタンスを選んでみる。

同じくTESTを押すとレスポンスが得られたことがわかるはず。
仮にここでエラーが出る場合は、インスタンスorWebサーバーが立ち上がっているか、今一度確認。

問題なくレスポンスが帰ってきていることがわかった場合は、そのままSAVE。

### Uptime Check用のPolicies設定

先ほどSAVEを押下したところでPolicies設定する？みたいなモーダルが出てくるはず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/426caf1c-8a0b-a297-6986-b970c11b6ccf.png)

先ほどのアラートポリシー設定とは違い、大半の項目は入力済となっている。
モーダル下部にあるConfiguration -> Forの項目を編集するだけでOK。
※この項目は、何分間サーバーへの接続試行に失敗したらアラートを出す、という設定。
※※テスト段階は1（1分間）で良いかと。

### Webサーバーを落としてEメールが来るかを確認

```console
root権限
$ systemctl stop httpd.service
```

これでWebサーバーは落ちるので、そのうちメールが来ればOK。
来ないようであれば上の設定たちを今一度見返す。

メールが来たらサーバーを立ち上げておこう。
（不用意にメールが来ても鬱陶しいと思うので。w）

```console
$ systemctl start httpd.service
```

## CloudFunctionsを設定する

自分はこの辺が俄然詰まった箇所。という前置きは置いといて…。

Cloudfunctionsのページを開き、諸々設定していくが、
以下の「トリガー」を「Cloud Pub/Sub」に設定することを忘れないように。

トピックはまだ作ってないので、新しく作るみたいなのをチョイス。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/ea6510fc-be37-1bed-ea55-b982bcfa0e60.png)


管理しやすいよう名前をつけて、トピック設定はOK。

ソースコードはインラインエディタのまま、ランタイム（使用言語）はお好きなものを。
（私はPython使いましたので、参考ソースもPythonで記載します。）

言語を切り替えると、画面下部のソースコードも切り替わるが、一旦そのままで。

その後、「環境変数、ネットワーキング、タイムアウト」などを押下しメニューを開くと、
リージョンの設定があるので、asia-northeast1に切り替えておく。

## Pub/Sub通知チャンネルを作成する

ベータ版ということもあり、英語ドキュメントしかない。
（日本語にするとPub/Subの項目なくなるw）
https://cloud.google.com/monitoring/support/notification-options

英語に強いわけではないが、概ねこんな感じで理解してます。

1. Pub/Subのトピック作る（今回は省略）
2. 作ったPub/SubトピックをAlert-NotificationChannelに追加する
3. 作ったPub/Subトピックにメンバー追加及び権限設定

という感じで進めていくが、1についてはさっきCloudFunctionsの設定の際に
トピックも作っているので、今回は省略してOK。

### 2.作ったPub/SubトピックをStackdriverの通知チャンネルに追加する

ローカルに`pubsub-channel.json`を作成し、
以下の内容を入力。（適宜置き換えて）

```json:pubsub-channel.json 
{
    "type": "pubsub",
    "displayName": "test notifications",
    "description": "Pub/Sub channel for test notifications",
    "labels": {
        "topic": "projects/[PROJECT_ID]/topics/[PUBSUB-TOPIC-NAME]"
    },
}
```

作成したJsonファイルをGcloudコマンドラインツールを導入したローカルのコンソール内で、
以下のコマンドを実行

```console
$ gcloud beta monitoring channels create --channel-content-from-file="pubsub-channel.json"
```

```console
Created notification channel ~~~~~~
```

となっていれば成功。

### 3.作ったPub/Subトピックにメンバー追加及び権限設定

[PROJECT_NUMBER]を2箇所置き換える必要がある。
これはGCPトップページに載っているのでそこからコピペでOK。

```console
$ gcloud pubsub topics add-iam-policy-binding \
projects/[PROJECT_NUMBER]/topics/[PUB/SUB_TOPIC] --role=roles/pubsub.publisher \
--member=serviceAccount:service-[PROJECT_NUMBER]@gcp-sa-monitoring-notification.iam.gserviceaccount.com
```

以下のような返却があればOK。（XXXにも色々書いてある）
```
Updated IAM policy for topic [PUBSUB_NAME].
xxxxxxxxxxx
xxxxxxxxxxx
```

### Stackdriver-Monitoringのアラート設定を確認する

Monitoring -> 設定 -> Edit Notification Channelから通知チャンネルの確認画面へいくと、
Cloud Pub/Sub[ベータ版]のところに先ほどjsonファイルにて追加した情報が載っているはず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/96fe5dd5-7887-71de-5003-2d5da609ec98.png)

## 実際にアラートに設定する

Monitoring -> アラート -> Policiesと見ていくと、
先ほど設定したアラートポリシーが存在しているはず。

それを開いて、Notification Channelの編集をしていく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/3543bee6-6c58-64e0-0bcd-f1d830deb0c6.png)

こんな感じで設定できればOK。

あとはもう一回サーバーを落として、アラートが上がるのを待とう。

## ログを確認する

ログは【Logging】で確認できる。

設定が諸々できていれば、アラートが起きたタイミングで、
CloudFunctionsにログが飛んでくるはず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/0c50902c-9fd0-58f6-8cd5-713b25f17c81.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/e80c00b4-7017-c2d8-96e7-f3308aa2d06b.png)


インシデントログはJSONで飛んでくるようになっており、

どのインスタンスに問題が起きたか？
いつ問題が起きたか？

などなど様々しっかりと情報が載っている。

なのでこれらを取り分けて、それっぽく見えるようにする。

## CloudFunctionsのソースを書き直そう。

今のままでは内容が読みづらいので、アウトプットをそれっぽく修正しよう。
実際に使ってるコードではなく、とりあえず動くよ！って段階のコードを転記してるため、
クオリティはお察し。。

とはいえ、ここはこうした方が見やすい！ってのがあったら教えてほしいです。
 

```python:main.py
from datetime import datetime
from pytz import timezone
import base64
import json
import requests
import os

def hello_pubsub(event, context):
    """Triggered from a message on a Cloud Pub/Sub topic.
    Args:
         event (dict): Event payload.
         context (google.cloud.functions.Context): Metadata for the event.
    """
    pubsub_message = json.loads(base64.b64decode(event['data']).decode('utf-8'))
    print(pubsub_message)

    message = pubsub_message['incident']
    incident_flag = message['state']
    summary = message['summary']

    # incident.summaryにはアラート内容の概要が飛んできているので、その内容で条件分岐。
    if summary == 'An uptime check on [PROJECT_ID] [INSTANCE_NAME] is failing.':
        summary = 'サーバーダウンを検知しました。'
    elif summary == 'The uptime check for [PROJECT_ID] [INSTANCE_NAME] has returned to a normal state.':
        summary = 'サーバー回復を確認しました。'

    if incident_flag == 'open':
        incident_flag = '障害発生'
        start_date = datetime.fromtimestamp(message['started_at']) # UNIX時間→yyyy-MM-dd HH:mm:ss
        jst = start_date.astimezone(timezone('Asia/Tokyo')) # UTC -> JST
    elif incident_flag == 'closed':
        incident_flag = '回復'
        end_date = datetime.fromtimestamp(message['ended_at']) # UNIX時間→yyyy-MM-dd HH:mm:ss
        jst = end_date.astimezone(timezone('Asia/Tokyo')) # UTC -> JST

    mes = """
        [info][title]検知: % s[/title]
        発生時刻: % s
        監視項目名: % s
        対象リソース名: % s
        エラー詳細URL: % s[/info]
        """ % (
            incident_flag,
            jst,
            summary,
            message['resource_display_name'],
            message['url']
        )
    # [info][title]はチャットワークへの通知で使うための記載

    data = {
        'body': mes
    }

    headers = {
        'X-ChatWorkToken': TOKEN
    }

    requests.post(POST, headers=headers, data=data)
```

実際のログはこんな感じで変化する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/55b5fbe6-fc52-6283-3344-b201a01c7ca0.png)

## チャットワークへの通知を行おう。

チャットワークへの通知を行う場合、通知を行うROOMIDやTOKENが必要になるが、
これらはCloudFunctionsの環境変数に忍ばせよう。
（間違ってもソースに書いちゃダメだぞ☆）

### CloudFunctionsの環境変数の設定

作成してあるFunctionsの設定を開き、画面下部にスクロールすると
[環境変数、ネットワーキング、タイムアウトなど]があるので、
そこから環境変数の設定ができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/80b67363-f28f-ec7f-34da-229f5d77ab9f.png)

こんな感じで環境変数を設定したら、ソースの編集を行い、
環境変数を受け取るよう実装。

hello_pubsubの関数の前に記載しておけばOK。

```python:main.py
TOKEN = os.environ.get('CW_TOKEN')
ROOMID = os.environ.get('CW_ROOMID')
URL = 'https://api.chatwork.com/v2/rooms'
POST = '{0}/{1}/messages'.format(URL, ROOMID)
```

これらを実装し終わったらデプロイを実行。

## 改めてアラートを出してみる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191382/a5f3e5d6-bde8-d1a4-fcdd-6a80f5acf1d6.png)


チャットワークへ通知が飛びました。


# まとめ

実際に自分でやりながら作っていったので、おそらくミスはないはず。。
今後この記事は自分にとっての手順書にします。笑

main.pyの実装については突っ込みどころが多いと思います。
そもそもの設定などこうしたほうがいいよ！ってのがあったら知りたいです。

# 最後に

[株式会社ネコカリ](https://www.nekokari.co.jp/)にて従事しています。
[株式会社ネコカリ](https://www.nekokari.co.jp/)では猫の手も借りたい🔥炎上中🔥なお仕事を募集しています！
一緒に働くメンバーも募集していますので、よかったら是非！