AWS LambdaとAPI Gatewayを利用し、PagerDutyのインシデント発生時にSlackに専用チャンネルを作成する #1
こんにちは、インフラ及びシステム運用を担当している田中です。
当社ではサーバーやネットワークに障害が発生した際に、障害対応の担当者へ通知を行う手段としてPagerDutyを利用しています。
担当者が障害を認知した後は障害内容を調査し、必要に応じて開発者や責任者へエスカレーションを行うことになります。
障害の発生が平日の日中帯であれば関係者間のコミュニケーションに問題はないのですが、夜間・休日の場合は関係者がそれぞれ別の場所に居ることになるため、障害ごとにSlackの専用チャンネルを作成して関係者間で情報を共有しています。
SlackにはPagerDutyのWebhookを受け、インシデントの内容を特定のチャンネルにpostすることができる機能が用意されています。
しかし、あくまでも特定のチャンネルにpostすることができるだけで、インシデントごとに専用のチャンネルを作成したりすることはできません。
そこで、AWSのAPI Gateway経由でLambdaファンクションを呼び出し、SlackのAPIを叩いて専用チャンネルを作成するようにしています。
今回から数回に分け、この仕組について説明したいと思います。
PagerDutyとは
まずPagerDutyについてですが、個人で使用するような類のサービスではありませんし、無料で利用できるプランが無いため(無料トライアルはあります)ご存じない方が多いかと思いますので、サービスの内容から簡単に説明します。
PagerDutyは、障害の通知を行う際に障害の内容や重要度・時間帯等に応じて通知の方法や通知先を切り替えることができる障害管理サービスです。
たとえば「重大な障害であれば深夜・休日であっても電話を掛けるが、重要ではない障害は平日昼間のみ電話を掛ける」とか、「Aさんに10分間電話を掛け続けたけど出ないので、今度はBさんに掛ける」といったことができます。
PagerDutyを利用するにあたり、幾つかの専門用語を覚える必要がありますので、順番に説明します。
■ Service
PagerDutyでは障害の検知を行う受け口をServiceと言い、大きくわけて3種類の受け口(Integration Type)があります。
1種類目はZabbixやNagio、Sensuなどの監視ツールや、Amazon CloudWatch、Mackerelといった監視系SaaSなどの既存のツールと連携するもの、2種類目はメールをトリガーとするもの、3種類目はPagerDutyのAPIを直接コールするものです。

連携できる既存ツールは多数ありますので、PagerDutyのサイトを御覧ください。
当社では様々な方法で検知している障害を同一のサービスで管理するため、メールトリガーのみ利用しています。
また、Webサイトごと及び障害の重要性ごとにServiceを分けることにより、どのServiceの障害かを見るだけで、どのWebサイトでどれくらいの障害が発生しているかがすぐに判るようにしています。
■ Incident
Serviceに対して何らかのアクションがあった場合にはIncidentが作成されます。

IncidentにはTriggered, Acknowledged, Resolvedといったステータスが存在し、Web画面やスマホアプリなどにより簡単に変更することができます。
TriggeredはIncidentが発生した状態、Acknowledgedは担当者がIncidentに気がついた状態、Resolvedは問題が解決した状態を表します。
IncidentがTriggered状態にある間、次に説明するEscalation Policyに基いて、担当者への連絡を継続します。
IncidentがAcknowledged状態になると担当者へ連絡がついたとみなされ、担当者への連絡は行われなくなります。
■ Escalation PolicyとSchedule
Escalation Policyはどの担当者にどれくらいの間隔で連絡を取るかを設定する項目です。
まずはAさんに10分間電話を掛け続け、出ない場合は今度はBさんに掛け、それでも出ない場合はまたAさんに掛けるといった設定を行います。

Escalation Policyには直接担当者を指定することもできますが、次に説明するScheduleを指定することもできます。
Scheduleでは各担当者が何曜日の何時~何時まで連絡を受け取るかといった連絡可能日時の設定を行うことができます。

Escalation Policyは、Serviceごとに1つだけ指定することができ、Incidentは対象のServiceで指定されたEscalation Policyに基いて担当者へ通知が行われることになります。
■ User
Userは担当者の設定で、PagerDutyではプランごとの単価にUser数を掛けた金額が課金されます。
当社では監視担当者のうち数名が週替りで非業務時間の監視を担当することにより、特定の担当者に負荷が集中しないように工夫しています。
PagerDutyのWebhookを利用してSlackへ通知を行ってみる
ここまでPagerDutyがどのようなサービスかを説明してきましたので、次は具体的な例を見ていきましょう。
まずはPagerDutyで簡単なServiceを設定し、その内容をSlackへ通知してみることにします。
■ SlackでPagerDuty用の設定を行う
まずはSlackへログインし、左上のteam名をクリックしてください。

そうするとアカウントやチームの設定を行うメニューが表示されますので、「Apps & integrations」をクリックしてください。

検索フォームで検索すると、PagerDutyが見つかりますので選択してください。

連携するチームの「Install」ボタンをクリックしてください。

Post to Channel の項目でPagerDutyからのメッセージをどのチャンネルにポストするかを選ぶことができます。
プルダウンメニューで既存のチャンネルを選択するか、「create a new channel」をクリックして新しいチャンネルを作成してください。
チャンネルを選択するか作成した後に「Add PagerDuty Integration」ボタンをクリックしてください。

完了画面にはPagerDuty側の設定方法が記載されていますが、下の方にスクロールするとオプションの設定や、Webhook用のURLが記載されています。
「Incidents to monitor」の項目でチェックが入っているイベントのみSlackへ通知されますので、全てチェックして最下部にある「Save Integration」ボタンをクリックしておきましょう。

これでSlack側の設定は完了です。
■ PagerDutyでServiceの設定を行う
次はPagerDuty側の設定です。
Serviceの詳細画面を開くと、画面下部にWebhooksという項目があります。

「+ Add a webhook」ボタンをクリックするとWebhookの名前とURLを入力するフォームが表示されます。
Nameはラベル以上の意味は無いのでSlackとでも入力しておき、Endpoint URLにはSlackの設定時に表示されたURLを入力して「save」ボタンをクリックします。

これでPagerDuty側の設定も完了です。
■ PagerDutyでIncidentを発生させてみる
それではPagerDutyでIncidentを発生させてみましょう。
IncidentはServiceの詳細画面で右側にある「+ Create New Incident」ボタンをクリックすると手動で発生させることができます。


「Create Incident」ボタンをクリックするとIncidentが発生します。

SlackへPagerDutyで発生したIncidentの内容を通知することができました。

しかし、これでは特定のチャンネルにしか通知を行うことができません。
このままだと複数のIncidentが並行して発生した場合、Slackのチャンネル上で複数のIncidentに対するコメントが混ざり合ってしまい、混乱してしまう恐れがあります。
そこで、PagerDutyのWebhookを直接Slackで受けるのではなく、AWSのAPI GatewayとLambdaでWebhookを受けることにより、Incidentの内容に処理を加えてからSlackへ通知するようにしてみましょう。
AWS LambdaとAPI Gatewayを利用して、PagerDutyのWebhookを受けてみる
まずはAWSのAPI GatewayとLambdaでWebhookを受けるところから始めます。
■ AWS Lambdaについて
AWS LambdaはAWS内で発生したイベントをトリガーにして起動するアプリケーションを簡単に開発できるプラットフォームです。
AWS Lambdaで実行するアプリケーションはLambda関数と呼ばれ、Java、Node.js、およびPythonで記述することができます。
AWS Lambdaについてはこちら等を御覧ください。
本来であればAWSの他サービスで発生したイベントをトリガーにしてLambda関数を実行することになるのですが、次に説明するAPI Gatewayと組み合わせることにより、AWSの外でイベントが発生した場合でもHTTPS経由でLambda関数を実行することができるようになります。
■ API Gatewayについて
API GatewayはインターネットからHTTPSでアクセスできるAPIコール用のエンドポイント(URL)を作成することができる仕組みです。
Lambda関数を実行する以外にもEC2で構築したアプリケーションを呼び出したり、インターネット上にある別のAPIを呼び出したり、静的コンテンツを返したりすることもできるようですが、Lambda関数をAWS外から実行するために使われていることが多いようです。
■ API Gateway経由で呼べるLambda関数を作成してみる
それでは簡単なLambda関数を用意し、API Gateway経由で呼び出せるようにしてみましょう。
まずはAWSの管理コンソールにログインし、コンピューティングのところにあるLambdaをクリックしてください。

まだLambda関数が作成されていない場合、下記のような画面が表示されますので、「Get Started Now」ボタンをクリックしてください。

そうすると新しいLambda関数を作成するためのウィザードが始まります。
まずはblueprintと呼ばれる関数の雛形を選びます。
今回はAPI Gateway経由でアクセスできる関数を作成しますので、「microservice-http-endpoint」を選んでください。

次のステップではLambda関数そのものの設定をしていきます。

Nameはとりあえずtestとしておきます。
Descriptionは適当で大丈夫です。
RuntimeはNode.js 4.3を選択してください。

Lambda function codeは関数のソースコードそのままです。
とりあえずは下記の通り入力しておいてください。
'use strict';
console.log('Loading function');
exports.handler = (event, context, callback) => {
console.log('Received event:', JSON.stringify(event, null, 2));
callback(null, 'success!');
};

Handlerはindex.handlerのままで大丈夫です。
この設定ですと、index.jsというファイルでexport.handlerに設定されたfunctionがコールされます。
RoleはLambda関数を実行するIAMロールになります。

Roleのプルダウンから「Basic execution role」を選択するとIAMロールを作成するための画面に遷移します。
特に何も変更せず「許可」ボタンをクリックしてください。

元の画面に戻りますので、Memory (MB)を128へ変更してください。
Lambdaでは割り当てたメモリと実行時間により料金が決まります。
Lambda関数で実際に使用したメモリの量はログに出力されますので、必要な分のメモリを割り当てるようにするといいでしょう。

TimeoutとVPCの項目は変更しなくても大丈夫ですので、「Next」ボタンをクリックしてください。

次の画面ではAPI Gatewayの設定を行います。

API endpoint typeはAPI Gatewayのままにしてください。
API nameはとりあえずapiとしておいてください。
Resource nameは変更しなくても大丈夫です。
MethodはPOSTに変更してください。
SecurityをOpenに変更し、「Next」ボタンをクリックしてください。

次の画面は確認画面になります。
問題があった場合は「Previous」ボタンで前の画面に戻って変更することになりますが、その際に幾つかの設定が元に戻ってしまうようなので、気をつけてください。
問題が無ければ「Create function」ボタンをクリックしましょう。

これでAPI Gateway経由で呼び出せるLambda関数を作成することができました。
実際に使用して見る前に、関数のテストを行ってみましょう。
画面左上にある「Test」ボタンをクリックしてみてください。

モーダルダイアログが表示され、関数に渡すパラメータを設定できる画面になります。
今回は特に変更する必要がありませんので、そのまま「Save and test」ボタンをクリックしてください。

しばらく待つとテストが終わります。
テストの結果は下の方に表示されていますので、スクロールして見てください。
context.succeed()に渡した引数の文字列がExection resultに、console.log()の引数の文字列がLog outputに表示されているのが判ります。

Exection resultの「logs」と書かれたリンクをクリックするとCloudWatchに出力されたLambda関数のログを見ることができます。

■ PagerDutyのWebhooksにAPI Gatewayを登録し、CloudWatchでログを見る
それでは、API endpointsに記載されているAPI endpoint URLをPagerDutyのWebhookに登録してみましょう。
先ほどSlackのWebhookを設定したPagerDutyのServiceページを開き、「+ Add a webhook」ボタンをクリックします。
NameにはAWS、Endpoint URLにはAPI GatewayのAPI endpoint URLを入力し、「Save」ボタンをクリックしてください。

これで、Lambda関数でPagerDutyのWebhookを受けることができるようになりました。
それでは先ほどと同様にPagerDutyでIncidentを発生させてみましょう。

もう一度CloudWatchのログ画面に戻ると、PagerDutyのWebhookによってLambda関数が実行された時のログが出ているはずです。

どうやら無事Webhookを受け取ることができたようです!