Edited at
MackerelDay 18

Lambdaを使ってMackerelのアラートをRedmineのチケットにする

More than 1 year has passed since last update.

この記事は、Mackerel Advent Calendar 2017の18日目の記事です。

今日はMackerel移行時のお話をしようと思います。

11月にようやく旧環境からMackerel への移行が終わりました。

この移行時、MackerelとRedmineを連携してアラート管理する必要があったのでその方法をまとめます。

方法といっても、WebHookを利用するだけです。

ただLambdaと連携できればどんな用途にも応用がききます。

参考までに、アラート管理のツールとしてRedmineを選んだ理由もまとめます。


MackerelのアラートからRedmineまでの流れ

Mackerel => WebHook => CloudFront(WAF) => API GateWay => Lambda => Redmine

介在するサービスが多いですが、一つ一つの作業量はあまり多くありません。

要点は次の通りです。


  • WebHookでイベントを発生させる

  • CloudFrontにWAFをつけて、Mackerelからのアクセスに限定する

  • LambdaでRedmineのAPIを叩く


1. Lambdaの設定


1.1 LambdaのIAMロール

VPC内のRedmineにアクセスする必要があるため、

Lambda関数のロールにネットワークインタフェースの作成削除の権限を与える必要があります。

管理ポリシーのAWSLambdaVPCAccessExecutionRole をLambdaのロールに与えます。

念のため何の権限を与えているのか確認しておくと、主にNetwork Interfaceの作成のためですね。


AWSLambdaVPCAccessExecutionRole

{

"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
],
"Resource": "*"
}
]
}


1.2 Lambda Function

Lambdaで受けて、RedmineのAPIを叩くだけの関数です。

いろいろとべた書きしています。


lambda_function.py

# coding: UTF-8

import requests
import json
import os

redmine_ip = os.environ["REDMINE_IP"]
redmine_host = os.environ["REDMINE_HOST"]
redmine_apikey = os.environ["REDMINE_APIKEY"]
redmine_project = os.environ["REDMINE_PROJECT"]
redmine_assigned = os.environ["REDMINE_ASSIGNED"]
redmine_tracker = os.environ["REDMINE_TRACKER"]

redmine_url = 'http://' + redmine_ip + '/issues.json?key=' + redmine_apikey

def lambda_handler(event, context):
parsed = json.dumps(event)
event_obj = ''

# 実験中,2通りのイベントデータが存在したため以下の処理を入れた
if json.loads(parsed).has_key('body'):
event_obj = json.loads(json.loads(parsed)['body'])
else:
event_obj = json.loads(parsed)

stats = event_obj['alert']['status']
opend = event_obj['alert']['isOpen']

if stats != "ok" and opend == "True":
alarm_host = get_alarm_host(event_obj)
monitor_name = event_obj['alert']['monitorName'].decode('utf-8')
subject = stats + u': "' + alarm_host + u' ' + monitor_name
custom_fields = [
{'id': 11, 'name': u'モニター名', 'value': monitor_name},
{'id': 22, 'name': u'アラートレベル', 'value': stats},
{'id': 33, 'name': u'アラート対象ホスト名', 'value': alarm_host}
]
create_ticket(subject, json.dumps(event_obj, indent=4, separators=(',', ': ')), custom_fields)

def get_alarm_host(event_obj):
alarm_host = ""
if event_obj.has_key('service'):
alarm_host = event_obj['service']['name']
if event_obj.has_key('host'):
alarm_host = event_obj['host']['name']
return alarm_host

def create_ticket(subject, description, custom_fields):
request_headers = {
'Host': redmine_host,
'Content-Type': 'application/json'
}

payload = {
'issue': {
'project_id': redmine_project,
'tracker_id': redmine_tracker,
'assigned_to_id': redmine_assigned,
'status_id': 1,
'priority_id': 4,
'subject': subject,
'description': description,
'custom_fields': custom_fields
}
}
requests.post(redmine_url, data=json.dumps(payload), headers = request_headers)



1.3 Lambdaに渡されるMackerelのイベントオブジェクト

こんな感じのオブジェクトが渡されてきます。

詳しくはドキュメントを読みましょう。

{

"orgName": "xxxxxxxxxx",
"host": {
"status": "working",
"isRetired": false,
"name": "web1",
"roles": [
{
"roleName": "web",
"fullname": "prod: web",
"serviceName": "prod",
"roleUrl": "[role url]",
"serviceUrl": "[service url]"
}
],
"url": "[host url]",
"memo": "",
"id": "aaaaaaaaaa"
},
"event": "alert",
"alert": {
"status": "critical",
"metricValue": 100,
"warningThreshold": 70,
"isOpen": true,
"url": "[alert url]",
"criticalThreshold": 90,
"metricLabel": "CPU %",
"trigger": "monitor",
"duration": 3,
"monitorName": "CPU %",
"monitorOperator": ">",
"createdAt": 1513319978252
}
}


1.4 Lambdaのその他の設定


  • ネットワークインタフェースを作るために時間がかかるので、タイムアウト値は20秒程度に設定しました。

  • 念のためサブネットはインターネットゲートウェイを持たないルートテーブルのサブネットを設定しました。

  • requests は pip install requests -t . したものをパッケージに含めています。

  • 環境変数は以下のように設定しています。

REDMINE_APIKEY          xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

REDMINE_IP 10.0.0.4
REDMINE_ASSIGNED 999
REDMINE_HOST example.jp
REDMINE_TRACKER 111
REDMINE_PROJECT alert


2. API GateWayの設定

API GateWayは HTTPリクエストでLambdaを動かすため利用します。

設定は以下の通りです。


2.1 リソース/POSTメソッドの作成

APIを作成したら、リソースの作成で、URLのパスを定義します

パスが定義できたら、メソッドの作成ができるようになるのでPOSTメソッドを作成します。

MackerelのWebHookの仕様 でPOSTメソッドで受ければよいことがわかります。

メソッド作成する際に作成したLambda関数に紐づけます。


2.2 APIキーの作成

CloudFrontからのアクセスに限定させるためにAPIキーを作成します。

apigw_03_create_apikey.png


2.3 メソッドリクエストの設定

APIキーを持たないとリクエストが通らないようにします

apigw_04_check_require_apikey.png


2.4 APIのデプロイ

まだインターネットに公開されていない状態なので、公開する環境を用意します。

その環境にAPIをデプロイすることでインターネットに公開されます。

apigw_05_create_stage01.png

apigw_05_create_stage02.png


2.5 使用量プランを定義

APIキーを割り当てるために使用量プランを作成します。

スロットリングとクォータはお好みで設定します。

apigw_06_used_plan.png

apigw_06_used_plan2.png

apigw_06_used_plan3.png


3. CloudFrontの設定


3.1 API GateWayのAPIキーをヘッダにつける

CloudFront経由したリクエストは必ずx-api-keyヘッダを付けるようにします。

API GateWayで作成したAPIキーを値としてOriginの設定を行います。

cloudfront_01.png


3.2 WAFの設定

許可すべきIPは公式の回答があります。

このページも監視対象にしておいたほうが良いかもしれません。

cloudfront_02.png

CloudFrontの設定に戻ってWAFを設定します。

cloudfront_03.png


3.3 CloudFrontのその他の設定


  • OriginDomainNameにはAPI GateWayの呼び出しURLを設定します

  • CloudFrontでキャッシュされないように設定しています


4. WebHookの設定

あとはURLにhttps://[cloudfrontのドメイン名]/[api-gatewayのパス]を入力すればアラートが発生すればチケットが作られるようになります。

webhook_01.png


チケットができた

ということで、アラートがなると、こんな感じでチケットが登録されるようになりました。

もろもろの連絡はSlackで行い、内容をこのチケットにまとめるというような利用イメージです。

mackerel8.png


【参考】インシデント管理でやりたかったこと

連携させるツールを選定する際の要件は主に以下の通りでした。


  • 振り返りができること

  • 誰が対応するかをはっきりさせること

  • アラートの傾向と対策を行うための集計

  • 対応漏れ防止

  • 1つのインシデントにn個のアラートが発生したあと、このアラートはこのインシデント、と関連付ける仕組み欲しい


【参考】インシデント管理ツールの検討

やりたいことに対し、はじめはPagerDutyかReactioを利用しようとしていました。

Mackerelの通知チャネルの連携サービスに出てきていたので、簡単に利用できそうだったからです。

しかし、以下の理由で見送りました。

■ Reactio

調べた際、2016/07/04以降ブログの更新がなく、保守されているか不安を覚え利用をやめました。

※12/4にSSL証明書の更新があり、保守はされているようです。

■ PagerDuty

大人の事情で利用を断念しました。

残念ながら外部サービスに頼ることができなさそうであったため、

手軽に実装できそうな、Redmineを利用することにしました。

Redmineだけではエスカレーションの自動化が困難です。

通常ですと、通知のタイミングと手段と相手をあらかじめ決めたポリシーに沿って、自動的に通知する仕組みを入れたいところです。

良い代替案が見つからなかったため、いったんこの自動化は見送ることにしました。

まあ、Lambdaが利用できるので、エスカレーション機能を自作できないことはありませんね...


まとめ

AWSのサービスを駆使してMackerelのアラートをRedmineのチケットにすることができました。

まだ有用な集計ができるほど期間が経っていませんが、少なくとも振り返りの資料として役立っています。

今回はRedmineとの連携に限定しましたが、MackerelとLambdaで連携できるため、エスカレーションの自動化、アラート発生時のサーバ情報の収集、オートスケール、などにも転用できそうです。