Grafana, Alertmanagerのアラートをwebhookを使って、Google Apps Scriptを経由してChatworkに送信

やりたいこと

  • Grafanaで発生したアラートをチャットワークに送信したいが、標準では対応していないため、Webhookを用いて、Chatworkに通知したい

  • GrafanaのWebhookと、AlertmanagerWebhookの構造が同じなため、Alertmanagerでも使用したい

  • スクリプトはGoogle Apps Scriptに設置する

  • フロー図

    [Grafana] → [Google Apps Script] → [Chatwork]
    [Alertmanager] ↑
    

構成技術

流れ

  1. Chatwork側の準備

    1. APIトークンを発行

      チャットワークAPIで必要になるので発行する

    2. グループチャットを作成

      通知先となるグループチャットを作成する。既存のものを使用する場合、省略可

    3. ルームIDを確認

      チャットワークAPIで必要になるので取得する

  2. Google Apps Scriptの準備

    1. Google Apps Script APIの有効化

      Claspで外部からGoogle Apps Scriptを操作できるように、ここを参考に、Apps Scriptの設定画面を開きGoogle Apps Script APIのアクセスを有効にする

    2. Claspをインストール

      プロジェクトディレクトリ(projdirは適当な名前)を作成して、パッケージを追加する

      $ mkdir <projdir>
      $ cd <projdir>
      $ npm init -f
      $ npm install -D @google/clasp
      
  3. clasp loginでGoogle Apps Scriptと関連付け

    $ npx clasp login
    

    出力されているURLをブラウザで開いてGoogleアカウントでログイン、claspによるGoogleアカウントへのサクセスを許可する

  4. clasp createでスクリプトプロジェクトを新規作成

    $ npx clasp create --type webapp --title <title>
    

    <title>はプロジェクトのタイトルを任意に設定 自分のプロジェクトに設定したタイトルでプロジェクトが作成される

  5. Chatwork Client for Google Apps Scriptをインストール

    1. 自分のプロジェクトから作成したプロジェクトを開く

    2. ライブラリ横のを選択 1771-1

    3. ライブラリの追加ダイアログが開くので、Chatwork Client for Google Apps ScriptにあるGoogle App Script スクリプト IDを入力して、検索ボタンを押下 1771-2

    4. ChatWorkClientライブラリがヒットするので、最新バージョンを選択して、追加ボタンを押下 1771-3

    5. 変更を反映するため、clasp pullを実行

      $ npx clasp pull
      

      appsscript.jsonが更新されて以下のようになる

      {
        "timeZone": "America/New_York",
        "dependencies": {
          "libraries": [
            {
              "userSymbol": "ChatWorkClient",
              "version": "18",
              "libraryId": "1nf253qsOnZ-RcdcFu1Y2v4pGwTuuDxN5EbuvKEZprBWg764tjwA5fLav"
            }
          ]
        },
        "exceptionLogging": "STACKDRIVER",
        "runtimeVersion": "V8"
      }
      
  6. appsscript.jsonを編集してタイムゾーンの変更とWEBアプリ用の設定を追加

    @@ -1,5 +1,5 @@
     {
    -  "timeZone": "America/New_York",
    +  "timeZone": "Asia/Tokyo",
       "dependencies": {
         "libraries": [
           {
    @@ -10,5 +10,9 @@
         ]
       },
       "exceptionLogging": "STACKDRIVER",
    -  "runtimeVersion": "V8"
    -}
    \ No newline at end of file
    +  "runtimeVersion": "V8",
    +  "webapp": {
    +    "access": "ANYONE_ANONYMOUS",
    +    "executeAs": "USER_DEPLOYING"
    +  }
    +}
    
  7. TypeScriptで開発を行いたいので、ここを参考に、必要な設定を追加

    1. Apps Script用の型定義をインストール

      $ npm install -D @types/google-apps-script
      
    2. TypeScriptの機能を有効にするためにtsconfig.jsonの作成

      {
        "compilerOptions": {
          "lib": ["esnext"],
          "experimentalDecorators": true
        }
      }
      
  8. index.tsを作成

    function doPost(e: GoogleAppsScript.Events.DoPost): void {
    // Chatwork APIトークン
    const token = "<Chatwork APIトークン>";
    // 通知先ルームID
    const room_id = "<Chatwork ルームID>";
    // チャットワークAPI
    const client = ChatWorkClient.factory({ token: token });
    // 通知内容データ
    const grafanaAlert = JSON.parse(e.postData.contents);
    // 通知本文
    let body = "[toall]";
    grafanaAlert.alerts.forEach((alert) => {
    body += `[info][title][${alert.status}] ${alert.labels.alertname}[/title]\n`;
    body += "Labels:\n";
    for (const property in alert.labels) {
    body += ` ${property}: ${alert.labels[property]}\n`;
    }
    if (alert.annotations) {
    body += "Annotations:\n";
    for (const property in alert.annotations) {
    body += ` ${property}: ${alert.annotations[property]}\n`;
    }
    }
    if (alert.silenceURL) {
    body += `Silence alert: ${alert.silenceURL}\n`;
    }
    if (alert.dashboardURL) {
    body += `Go to dashboard: ${alert.dashboardURL}\n`;
    }
    body += "[/info]\n";
    });
    // Chatworkへ送信
    client.sendMessage({
    room_id: room_id,
    body: body,
    });
    }

    body部は、Message templating | Grafana LabsCustom template examplesChatworkのメッセージ記法を組み合わせた

  9. clasp pushでコードをプッシュ

    $ npx clasp push
    ? Manifest file has been updated. Do you want to push and overwrite? Yes
    └─ /<projdir>/appsscript.json
    └─ /<projdir>/grafanToChatwork/index.ts
    Pushed 2 files.
    
  10. clasp deployでコードをデプロイ

    $ npx clasp deploy
    Created version 1.
    - XXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @1.
    

    このWEBアプリは、appsscript.jsonで定義したとおり、ユーザー「自分」として実行し、URLへのアクセスは全員で公開される

  11. プロジェクトを開き、デバッグボタンを押下して、ChatWorkClientライブラリへ権限を与える

    1771-4

    1771 (5)

    1771 (6)

    デバッグは、doPost()のパラメータeがundefinedのためエラーになる

  12. テスト

    Contact points | Grafana LabsのJSONファイルをテストデータとしてアクセスする

    1. JSONファイルをsample.jsonとして保存

    2. curlでPOSTリクエストを発生させる

      $ curl -X POST -v -H "Content-Type: application/json" --data @sample.json https://script.google.com/macros/s/<デプロイ ID>/exec
      

      エラーがなければ、「スクリプトが完了しましたが、何も返されませんでした。」と返る

      <!DOCTYPE html><html><head><link rel="shortcut icon" href="//ssl.gstatic.com/docs/script/images/favicon.ico"><title>エラー</title><style type="text/css" nonce="ag2ugnBDNHytzn8ZSzwMNg">body {background-color: #fff; margin: 0; padding: 0;}.errorMessage {font-family: Arial,sans-serif; font-size: 12pt; font-weight: bold; line-height: 150%; padding-top: 25px;}</style></head><body style="margin:20px"><div><img alt="Google Apps Script" src="//ssl.gstatic.com/docs/script/images/logo.png"></div><div style="text-align:center;font-family:monospace;margin:50px auto 0;max-width:600px">スクリプトが完了しましたが、何も返されませんでした。</div></body></html>
      
    3. Chatworkに投稿されていることを確認

      1771 (7)

  13. GrafanaのWebhookに登録

    1. Grafanaを開き、AlertingContant pointsに移動
    2. New contact pointを押下
    3. Contant point typeWebhookに設定
    4. UrlをWEBアプリのURLに設定 1771 (12)
    5. Testを押下してChatworkに通知が行くことを確認 1771 (8)
    6. Save contact pointを押下して保存
    7. AlertingNotification policiesで先ほど追加したContact pointを使って通知するように設定
  14. AlertmanagerのWebhookに登録

    1. alertmanager.ymlに設定を追加

      route:
        receiver: 'chatwork'
      receivers:
      - name: 'chatwork'
        webhook_configs:
        - send_resolved: true
          url: 'https://script.google.com/macros/s/<デプロイ ID>/exec'
      
    2. Alertmanagerを再起動

    3. テストアラートを発生させて通知をテスト

      <alertmanager_bin>/amtool --alertmanager.url=<alertmanager_url> alert add foo node=bar \
      --annotation=runbook='http://runbook.biz' \
      --annotation=summary='summary of the alert' \
      --annotation=description='description of the alert' \
      --end=$(date -Iseconds --date "5 minutes")
      
      

      1771 (9)

  15. 動作と通知が確認できたら、通知用のChatworkアカウントを別で作成し、チャットグループに加え、そのアカウントで発行したAPIトークンに変更しpush・再デプロイ

    作成者と通知者が同じでも問題ないが、Chatwork上で自分自身がチャットすることになるため、通知がないので、アカウントを分ける

再デプロイについて

コードを変更するたび、GASサーバにpushしてデプロイする必要があるが、デプロイするたびにURLが変わってしまうため、デプロイする際にはデプロイIDをパラメータに加える

$ npx clasp redeploy -i XXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

デプロイIDがわからない場合は、

  1. プロジェクトを開いて、デプロイボタンを押下

    1771 (10)

  2. デプロイを管理ダイアログの、アクティブにあるデプロイIDの下のコピーをクリック 1771 (11)

  3. もしくは、clasp deploymentを使う

    $ npx clasp deployments
    2 Deployments.
    - XXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @HEAD
    - XXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @1
    

    の@数字の方を選択。数字はバージョン番号なので、一番大きいのを選択

その他

デバッグについて

制作中にデバッグ出力として、Loggerクラスやconsole.log()を使用したかったが、どちらもcurlで実行した際には何も出力されなかった。対処方法として、スプレッドシートに出力、メール、Apps Script プロジェクトからGoogle Cloud Platform(GCP)プロジェクトの使用に切り替えるなどがある

APIトークンなどの保存について

今回は、便宜上APIトークンや、ルームIDをコードに直接書いているが、プロパティに保存して取り出す形にしたほうがいい

NPMモジュールの利用

今回は、1つのファイルで完結しているが、アラートテンプレート(Goテンプレート製)をそのまま使いたいとかだとnpmライブラリを用いたほうが便利です。その際にはrollupやwebappなどでnode_modulesなども含めて1つのgsファイルにする必要があります