カンシを止めるな！ 【GCP】Stackdriver Logging と Cloud Functions で hostError を自動投稿

[グレンジ Advent Calendar 2018](https://qiita.com/advent-calendar/2018/grenge) 7日目担当のs.1na9ak1です。

## __概要__

#### __概要が長いので、3行で__

概要が長くなってしまったので、まとめました。

* たまにホストエラーが起きる
* 調査が億劫
* 自動投稿

以下、概要が長いので、内容まで読み飛ばしても大丈夫です。

####  __インスタンスが再起動！原因はなに？__

弊社サービス「[ポコロンダンジョンズ](https://pocodun.grenge.jp/)」をGCPに移設した当初、インスタンスの再起動がごく稀にあり、調査を行うと原因はホストエラーとわかりました。

#### __ホストエラーってなに？__

「Google Compute Engine のよくある質問」に記載がありました。
 [仮想マシンでホストエラーが発生して再起動しました。何が起こったのですか？](https://cloud.google.com/compute/docs/faq?hl=ja#hosterror)

>ホストエラーは、仮想マシンをホストしている物理マシンで、仮想マシンがクラッシュするようなハードウェアまたはソフトウェアの問題が発生したことを意味します。Compute Engine でこのようなイベントが検出されると、オペレーション ログに compute.instances.hostError というエントリが追加されます。仮想マシンが自動的に再起動するように設定されている場合は（デフォルト設定）、別の物理マシンで仮想マシンが再起動します。

#### __ホストエラーは起きたのか？__

ホストエラーを調査する場合、

* [gcloud command](https://cloud.google.com/sdk/gcloud/)

`gcloud compute operations list --filter "hostError" --project [プロジェクト名]`

* [Stackdriver Logging](https://cloud.google.com/logging/docs/)

`compute.instances.hostError` のエントリから検索

の２つがありました。

#### __自動投稿しよう__

上記の調査方法の場合、過去（1か月程度は参照できる）に遡って調査を行うことができないということと、
なにより、たまに起きたときに調査する手間がかかります。
ですので、ホストエラーが起きたら、__[Chatwork](https://go.chatwork.com/)__と__[Datadog](https://www.datadoghq.com/)__に自動投稿するようにしました。

## __内容__

素晴らしいことに、ホストエラーのエントリが__Stackdriver Logging__に出力されている！なら、エクスポートして、__Cloud Functions__ で自動投稿できる！って内容です。

#### __Stackdriver Loggingのエクスポート作成__

Google Cloud Platfrom のコンソールから
Logging > エクスポート > エクスポートの作成 に移動します。

* フィルタの設定

`jsonPayload.event_subtype` に `compute.instances.automaticRestart` または、`compute.instances.hostError` のフィルタを設定します。

hostErrorの場合は、`compute.instances.hostError`
自動再起動の場合は、`compute.instances.automaticRestart`

以下のフィルタを設定します。

```
resource.type="gce_instance"
jsonPayload.event_subtype=("compute.instances.automaticRestart" OR "compute.instances.hostError")
```

* シンクサービスの設定

エクスポート先に __Cloud Pub/Sub__ を設定します。

* シンクのエクスポート先の作成

__Cloud Pub/Sub__ のトピックを新規作成します。

![qiita001.PNG](https://qiita-image-store.s3.amazonaws.com/0/217081/eaf94bce-0fb1-87e6-524f-aba2d6d2921f.png)

#### __[Cloud Functions](https://cloud.google.com/functions/docs/) の作成__

Chatwork 投稿と、Datadogにメトリックを送信します。
以下のコードを用意します。Node.jsで書いています。

Chatwork投稿は、[Chatwork API](https://github.com/ledsun/post-chatwork-message)。
Datadogのメトリック送信に [dogapi](https://brettlangdon.github.io/node-dogapi/) を利用しています。

index.js

```js
var async = require('async');
var dogapi = require('dogapi');
var postChatworkMessage = require('post-chatwork-message');
var _ = require('underscore');
var dateformat = require('dateformat');
var moment = require('moment-timezone');

// https://app.datadoghq.com/account/settings#api
var options = {
    api_key: '*****************************',
};

dogapi.initialize(options);

// ID：******************@grenge.co.jp
const CHATWORK_API_KEY = '*****************************';
// chat work room id
const RID = '*********';

const POST_TITLE_PREFIX = '[GCE_OPERATION]';

/**
 * Background Cloud Function to be triggered by Pub/Sub. 
 *
 * @param {object} event The Cloud Functions event.
 * @param {function} callback The callback function.
 */
exports.triggerTopicComputeOperationsHostError = (event, callback) => {
    var pubsubMessage = event.data;
    var data = pubsubMessage.data ? Buffer.from(pubsubMessage.data, 'base64').toString() : null;

    if (!data) {
        console.log('ERROR: data is null.')
        callback();
        return;
    }

    var obj = JSON.parse(data);
    var chatwork_post_message = '[info][title]' + POST_TITLE_PREFIX + ' ' + obj.jsonPayload.event_subtype + '[/title]';
    chatwork_post_message += moment(Number(obj.jsonPayload.event_timestamp_us/1000)).tz("Asia/Tokyo").format("YYYY-MM-DD HH:mm:ssZ") + ' ' + obj.resource.labels.project_id + ' ' + obj.resource.labels.zone + ' ' + obj.jsonPayload.resource.name;
    chatwork_post_message += '[/info]';
    chatwork_post_message += 'https://console.cloud.google.com/logs/viewer?project=' + obj.resource.labels.project_id + '&resource=gce_instance%2Finstance_id%2F' + obj.resource.labels.instance_id;

    // post
    postChatworkMessage(CHATWORK_API_KEY, RID, chatwork_post_message);

    // Datadog post event
    dogapi.event.create(POST_TITLE_PREFIX, data, {
        host: obj.jsonPayload.resource.name,
        alert_type: 'error',
        tags: ['project:' + obj.resource.labels.project_id, 'event_subtype:' + obj.jsonPayload.event_subtype, 'env:' + obj.resource.labels.project_id.replace('popolo-', '')]
    }, callback);
};
```

#### __Cloud Functions のデプロイ__

[gcloud functions deploy](https://cloud.google.com/sdk/gcloud/reference/functions/deploy) コマンドでデプロイを行います。

以下がデプロイコマンドです。
特に重要なのは、2点です。

* __--trigger-resource__

Stackdriver Logging で作成したエクスポート先のトピックを指定します。

* __--trigger-event__

Cloud Pub/Sub を利用するので、`google.pubsub.topic.publish` を指定します。

```
gcloud beta functions deploy triggerTopicComputeOperationsHostError --trigger-resource topic-compute_operations_host_error --trigger-event google.pubsub.topic.publish --project [プロジェクト名]
```

デプロイが完了で、ホストエラーの自動投稿は完了です。

#### __こんな感じです。__

* Chatwork
![qiita002.png](https://qiita-image-store.s3.amazonaws.com/0/217081/970fe285-19eb-5d2a-25c8-effeb688bdf4.png)

* Datadog
![qiita003.png](https://qiita-image-store.s3.amazonaws.com/0/217081/0ef30c30-3d6b-bee9-d895-95d065719997.png)

## __最後に__

これを実装してからホストエラーは激減しました。（が、たまに自動投稿されるので、個人的に重宝してます。）
あるあるですね。
[カメラ](https://qiita.com/flankids/items/21a8e900e8e9f6329767)もカンシも止めずにがんばっていきましょう！

ありがとうございました。